OneSignal is built to react to user behavior across messaging channels, while Volanea is built to deliver application email. To send email from OneSignal with Volanea, use a OneSignal Journey triggered by a Custom Event, send that Journey’s webhook to your backend, and let the backend make the authenticated Volanea REST API request.

This is deliberately not a marketplace-installation guide: Volanea does not provide a native OneSignal app or OneSignal marketplace plugin. The practical integration is an HTTP handoff. That extra server-side hop is useful, not incidental: it keeps your sending credential private, gives you a durable place to validate data, and lets you make retries idempotent before an email is created.

What this integration does

The pattern is straightforward:

  1. Your application sends a Custom Event to OneSignal when something meaningful happens, such as receipt_ready, trial_ending, or application_status_changed.
  2. A OneSignal Journey uses that Custom Event as its entry condition.
  3. A Webhook step in the Journey POSTs event and recipient data to an endpoint you operate.
  4. The endpoint validates the request, builds an email payload, and calls Volanea’s email REST API.
  5. Your endpoint records the event identifier before returning a successful response, preventing a delivery retry from creating another email.

This makes OneSignal the orchestration layer and Volanea the email-delivery layer. It is especially useful when a product team already uses OneSignal Journeys for behavioral timing, waits, branching, and multi-channel coordination, but engineering wants transactional email to remain on a dedicated sending platform.

There is an important distinction between a Journey-triggered webhook and a browser-to-email API call. The browser should never contact Volanea with a live API key. Your webhook receiver is a trusted server component, so it can enforce your own rules around recipients, templates, sender identities, consent, and duplicate prevention.

The OneSignal trigger: a Custom Event enters a Journey

The concrete OneSignal trigger in this setup is a Custom Event. A Custom Event is an event your application records against a OneSignal user, optionally with properties. A Journey can use that event as an entry trigger, then run actions for the user who generated it.

For example, an order service might record receipt_ready only after payment capture and invoice creation have completed. The event properties carry the data needed to identify the order and recipient context; they should not be treated as blindly trusted email content.

A representative event sent from your backend to OneSignal might look like this conceptually:

{
  "name": "receipt_ready",
  "user": {
    "external_id": "user_4821"
  },
  "properties": {
    "order_id": "ord_80419",
    "total": 49.0,
    "currency": "USD",
    "locale": "en-US"
  }
}

The exact endpoint and authentication used to record Custom Events belong in your OneSignal server-side integration, not in a mobile app or public web bundle. The event name and property names are yours to define. What matters is consistency: the Journey entry condition, webhook body, and middleware validation must all agree on receipt_ready, order_id, and any other fields you use.

Configure the Journey around a business event

In OneSignal, create a Journey whose entry condition is the receipt_ready Custom Event. Add a Webhook action as the next step. A short wait can be useful if another system needs time to finish generating a receipt, but do not add a delay merely to compensate for unreliable data. The preferred design is to emit the event only when the record is actually ready.

Use event properties for identifiers and routing signals, not as the source of truth for billing details or sensitive content. For a receipt email, order_id is safer than including a complete order line-item list in OneSignal. Your middleware can look up the current order from your database and render a canonical email from server-owned data.

That approach provides a second-order benefit: if an administrator changes a Journey later, they cannot accidentally cause a webhook to send stale or client-supplied monetary data. OneSignal decides when the workflow advances; your application remains responsible for what the email says.

Choose an event name that can survive change

A generic event such as email_send becomes hard to govern as a product grows. Prefer names that describe the business fact that occurred:

  • receipt_ready
  • password_reset_requested
  • document_available
  • subscription_payment_failed
  • application_status_changed

Do not use a single event with a free-form email_type property unless you have strong schema validation. Separate event names make Journey entry rules easier to inspect and reduce the chance that a new email type inherits the wrong wait, branch, or audience rule.

Build the Webhook action payload deliberately

The Webhook action is the outbound HTTP capability that connects the Journey to your middleware. Configure it to POST to an HTTPS endpoint you control, such as https://app.example.com/webhooks/onesignal/receipt-ready.

The safest webhook body is a small, explicit JSON envelope. Rather than forwarding every available OneSignal field, send only what the receiver needs to correlate the Journey action with a server-side record. The body below is a concrete payload shape to configure in the Webhook action for this use case:

{
  "event_name": "receipt_ready",
  "onesignal_user_id": "{{ user.onesignal_id }}",
  "external_id": "{{ user.external_id }}",
  "event": {
    "order_id": "{{ event.properties.order_id }}",
    "locale": "{{ event.properties.locale }}"
  },
  "journey": {
    "name": "Receipt email",
    "step": "send-volanea-email"
  }
}

The values in double braces are Journey personalization expressions. Before publishing, use OneSignal’s preview or test-user facilities to confirm the expressions available in your account resolve as expected. The precise personalization attributes exposed to a Journey can depend on the data associated with the user and event; a missing value must not silently become an invalid recipient or an empty order lookup.

This body intentionally does not include a Volanea key, recipient email address, HTML, or invoice total. It identifies the user and order. The backend then performs the authoritative lookup.

Why the webhook should not call Volanea directly

A direct Journey-to-Volanea request can seem attractive because it removes one service. In practice, it is usually the wrong boundary for production transactional email.

First, Journey variables are designed for personalization, not for full application authorization. They cannot reliably decide whether the order still belongs to the user, whether the email has already been issued, or whether an address is currently suppressed. Second, a webhook retry needs a stable idempotency strategy. Third, a payment receipt or status-change message should generally use data fetched from the originating system at send time.

A middleware endpoint also makes it possible to gradually improve the implementation without rebuilding a Journey. For example, you can add localization, migrate templates, reject an old event schema, or change the Volanea sender identity in code while preserving the same OneSignal workflow.

Keep the Volanea API key on the server

The Volanea API key should live in your middleware’s secret manager or encrypted server-side environment variable, for example VOLANEA_API_KEY. It does not belong in a Journey body, a Journey query string, a client-side JavaScript variable, a mobile application, a public repository, or a customer-visible page.

Because this integration uses a middleware endpoint, there is no Volanea API key “on the OneSignal side” at all. OneSignal only needs the HTTPS destination for your webhook and, if you choose to protect the endpoint with a shared header, a separate inbound webhook secret. Store that webhook secret in OneSignal’s protected webhook-header configuration and in your server secret store; it is still not a substitute for Volanea’s sending credential.

Keep the two credentials separate:

  • OneSignal webhook secret: proves, within your integration design, that the inbound request is intended for your webhook receiver.
  • Volanea API key: authorizes your server to create email sends through Volanea.

This separation limits blast radius. If you rotate the webhook secret, you do not need to rotate your email-sending credential. If a Volanea key is rotated, your Journey configuration remains unchanged.

Add an inbound authentication check

Configure a custom header on the OneSignal Webhook action, such as X-Integration-Secret, and compare it on the server using a timing-safe comparison. Restrict the endpoint to HTTPS, log rejected requests without logging secrets, and rate-limit it. If your security model requires stronger request verification than a static header, place the receiver behind an API gateway or use a narrowly scoped authentication mechanism managed by your infrastructure.

Do not assume a header alone solves every security concern. Treat webhook content as untrusted input even after the header is accepted. Validate schema, enforce expected event names, and retrieve sensitive business data from your own database.

For details about the supported sending request and authentication format, consult the email API reference and setup guides before deploying. Use a restricted key where your Volanea account supports it, and rotate a key immediately if it has appeared in source control, build logs, screenshots, or client code.

Working middleware: map OneSignal data to a Volanea email

The following Node.js example receives the configured Journey webhook, validates a shared secret, looks up the order and user, and makes a Volanea REST request. The route and mail fields are intentionally isolated in one function so that your team can keep the Volanea request aligned with the exact endpoint and schema in your account’s API reference.

import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.json({ limit: "100kb" }));

function safeEqual(a = "", b = "") {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}

app.post("/webhooks/onesignal/receipt-ready", async (req, res) => {
  // OneSignal Webhook action header: X-Integration-Secret: <secret>
  if (!safeEqual(req.get("X-Integration-Secret"), process.env.ONESIGNAL_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "unauthorized" });
  }

  const { event_name, external_id, event = {}, journey = {} } = req.body;
  if (event_name !== "receipt_ready" || !external_id || !event.order_id) {
    return res.status(400).json({ error: "invalid webhook payload" });
  }

  // Replace these with database queries. Never trust an email address or price from the webhook.
  const user = await db.users.findByExternalId(external_id);
  const order = await db.orders.findById(event.order_id);
  if (!user || !order || order.userId !== user.id || order.status !== "paid") {
    return res.status(422).json({ error: "order is not eligible for a receipt" });
  }

  // Persist this unique business key before sending. A duplicate webhook returns 200 below.
  const idempotencyKey = `receipt:${order.id}`;
  const created = await db.emailJobs.insertIfAbsent({
    idempotencyKey,
    provider: "volanea",
    journey: journey.name || "unknown",
    status: "pending"
  });
  if (!created) return res.status(200).json({ status: "already_processed" });

  const html = renderReceiptHtml({ order, user, locale: event.locale || user.locale });

  // Volanea REST request: keep the API key server-side only.
  const volaneaResponse = await fetch(process.env.VOLANEA_EMAIL_API_URL, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: JSON.stringify({
      from: process.env.VOLANEA_FROM_EMAIL,
      to: [user.email],
      subject: `Receipt for order ${order.number}`,
      html,
      text: renderReceiptText({ order, user }),
      tags: ["receipt", "onesignal-journey"]
    })
  });

  if (!volaneaResponse.ok) {
    await db.emailJobs.markFailed(idempotencyKey, await volaneaResponse.text());
    return res.status(502).json({ error: "volanea_send_failed" });
  }

  const result = await volaneaResponse.json();
  await db.emailJobs.markSent(idempotencyKey, result);
  return res.status(200).json({ status: "sent", idempotencyKey });
});

The field mapping is explicit:

OneSignal webhook fieldMiddleware useVolanea email field
external_idFinds the authoritative application userto[0] becomes user.email
event.order_idFinds and authorizes the paid orderSubject and rendered content
event.localeSelects language or formatting fallbackRendered html and text
journey.nameOperational attributionJob log or tags
event_nameSchema and routing guardNot sent as recipient content

Set VOLANEA_EMAIL_API_URL to the current email-send endpoint documented for your Volanea account. Keeping it as a deployment secret/configuration value prevents an endpoint change from being scattered through application code. Confirm whether your account’s REST API supports the idempotency header shown above; retain the database uniqueness check even if it does, because that check protects the OneSignal-to-middleware hop before the outbound request is made.

Design recipient and template rules before launch

A Journey can be changed by marketing, lifecycle, or product operators. A transaction email should still obey application-level rules. The middleware is the place to establish those rules once.

For transactional receipts, the recipient should be the current email address on the user record associated with the order. For a document notification, the recipient should be someone authorized to access that document. Never accept an arbitrary to property from a Journey event unless your use case explicitly supports delegated sending and you have authorization controls around it.

Use identifiers, not full email bodies

Sending an order_id over the webhook and rendering the receipt on your server reduces data exposure and makes the result repeatable. It also means a later delivery retry uses the same canonical business record. If the event needs a snapshot—for example, the exact legal wording accepted at checkout—save that snapshot in your application database and retrieve it by identifier.

Keep HTML generation in a tested template system. Produce a useful text alternative, escape variable content, and avoid inserting user-generated HTML into an email without sanitization. If the email includes links, create signed or authenticated links from your backend rather than putting raw access tokens into OneSignal event properties.

Separate transactional and campaign intent

OneSignal Journeys can coordinate lifecycle messaging, but a message triggered by a purchase or security action must be classified correctly in your own sending and consent model. Do not turn a receipt into a promotional email just because it travels through a Journey. Conversely, do not use a transactional sender identity to evade marketing consent requirements.

Volanea can handle both transactional and campaign infrastructure, but the integration should preserve intent in tags, template selection, and internal reporting. That makes it easier to investigate a complaint, explain a send to support, and apply different suppression policies where appropriate.

When this breaks: failures specific to this hop

Every webhook integration needs a failure plan. The key issue is that OneSignal, your receiver, your database, and Volanea are separate systems. A successful HTTP response does not always mean the final email has been accepted; a timeout does not always mean the receiver did nothing.

OneSignal retries can create duplicate sends

A webhook sender may retry when it receives a non-success response or cannot determine whether the request completed. The most dangerous case is: your receiver sends an email, then times out or crashes before returning 200. OneSignal can send the same webhook again.

Solve this with idempotency at the business level. receipt:${order.id} is better than a random request ID because it describes the one receipt you intend to send. Insert it into a database table with a unique constraint before making the Volanea request. On a duplicate, return a success response without sending again.

Do not rely on a process-local memory cache for this. It fails across deployments, multiple instances, restarts, and long retry windows. If an email is genuinely allowed to be sent more than once, model that explicitly with a version or sequence number, such as status-change:${application.id}:${application.statusVersion}.

Webhook timeouts and slow dependencies

A Journey webhook action expects a timely HTTP response. If your endpoint has to wait on a database, template renderer, fraud service, and email provider synchronously, latency becomes a source of retries.

For higher-volume flows, return a successful response after durably queueing a job rather than after the Volanea request finishes. The worker can then send the email, record the provider response, and retry failures under your own controlled policy. The queue record must still use the same unique idempotency key.

Avoid responding 200 before the job is durable. An acknowledgement without a database or queue write can lose an event permanently if the process exits immediately afterward. The correct order is validate, persist a unique job, acknowledge, then process.

Missing personalization or event properties

A Custom Event property may be absent because an older app version emitted a different schema, a server deployment had a bug, or a Journey test user does not have the expected data. Some OneSignal account capabilities and personalization data availability can also differ by product configuration, so test the exact Journey and user data model available in your workspace before relying on a variable.

Validate required fields at the receiver and return a clear non-success response for malformed payloads. Send that failure to your error monitoring with the event name, Journey name, and a redacted payload shape. Do not log full email addresses, API keys, payment details, or rendered HTML by default.

Use safe fallbacks only where they are genuinely safe. A missing locale can fall back to en-US; a missing order ID should reject the request. A missing external ID should reject the request. An empty email address from a database lookup should move the job to an actionable failed state rather than producing a malformed Volanea request.

Volanea rejects the request or accepts it but delivery fails

A 4xx response usually indicates a request, authentication, sender, or recipient problem that should be corrected rather than blindly retried. A 5xx or network error may be transient, but apply bounded retries with exponential backoff and alert when the job exhausts its attempts.

After Volanea accepts a send request, delivery is still a separate lifecycle. Track the provider message identifier returned by the API, correlate it to the job key, and use your usual delivery-event handling for bounces, complaints, and suppressions. This is where a dedicated email platform is valuable: acceptance, deliverability, and recipient health need visibility beyond the original Journey step.

Test the complete path before publishing

A Journey that looks correct in a visual builder can still fail because event data, webhook authorization, or sender verification differs from your assumptions. Test with a non-production user and a non-production Volanea sender or controlled inbox where possible.

Use this checklist:

  1. Record the exact receipt_ready Custom Event for a test external ID.
  2. Confirm the user enters the intended Journey once.
  3. Inspect the webhook receiver’s redacted log and confirm event_name, external_id, and order_id are present.
  4. Confirm the server authorizes the order against that user.
  5. Verify the idempotency row is created before the Volanea request.
  6. Confirm the Volanea response is saved with the application job.
  7. Repeat the same event intentionally and confirm no second email is created.
  8. Test a missing order ID, a mismatched user/order pair, and an invalid webhook secret.
  9. Verify HTML, text content, links, sender, reply-to behavior, and unsubscribe treatment for the message type.

Do not treat a test inbox delivery as the end of testing. Trigger the failure paths too. Simulate a Volanea network timeout after the job is stored, restart the worker, and ensure the send is retried safely. Simulate a duplicate webhook. These tests reveal whether the architecture is actually idempotent rather than merely appearing so in a happy-path demo.

Operational visibility and ownership

This integration crosses lifecycle automation and email infrastructure, so assign ownership clearly. The team managing OneSignal should own Journey logic, event naming coordination, and change review. The engineering or platform team should own the receiver, job queue, credentials, database constraints, and Volanea configuration.

At minimum, capture these fields in structured logs or a job table:

  • business idempotency key
  • OneSignal event name and Journey name
  • OneSignal user or external identifier, stored according to your privacy policy
  • application record identifier such as order_id
  • Volanea request timestamp and provider message identifier
  • attempt count, failure category, and final status

Build a small dashboard or query that answers practical support questions: Was a webhook received? Was it rejected for schema or authorization? Was a job queued? Did Volanea accept it? Was it later bounced or suppressed? Without this chain, teams often blame the Journey for a failure that actually occurred in data lookup, sender setup, or recipient policy.

Alternatives to a direct Journey webhook

If your OneSignal configuration does not expose the Webhook action you need, or if you do not want to run a receiver, use an automation middleware such as Zapier or Make as an intermediary only after confirming it supports the OneSignal event and authenticated HTTP request you require. The trade-off is reduced control over idempotency, data retrieval, and secrets compared with a backend endpoint.

For low-risk internal alerts, a no-code flow can be acceptable. For receipts, account security notices, application status changes, or anything with financial or personal data, server-side middleware is the stronger choice. It can verify the user-record relationship, render from canonical data, and retain an audit trail.

You can also bypass OneSignal for strictly transactional sends: have the service that creates the business record call Volanea directly. That is simpler when there is no need for Journey waits, branches, or multi-channel orchestration. Use the OneSignal-to-middleware pattern when the Journey is adding a real product or lifecycle value, not merely because it is available.

Conclusion

The reliable way to send email from OneSignal with Volanea is not a native plugin installation. It is a controlled workflow: a OneSignal Custom Event enters a Journey, the Journey’s Webhook action calls your server, and your server creates the Volanea email using a private API key.

Keep the webhook body small, use identifiers to retrieve authoritative data, persist an idempotency key before sending, and make malformed data visible rather than silently filling it in. Those choices turn an attractive automation into an operationally safe email path.

FAQ

Does Volanea have a native OneSignal integration?

No. This setup uses OneSignal’s Journey Webhook action and middleware you operate; it is not a Volanea app, marketplace listing, or native OneSignal plugin.

What starts the email send in OneSignal?

A Custom Event starts the flow. Configure a Journey to use an event such as receipt_ready as its entry condition, then add a Webhook action that calls your middleware endpoint.

Where should the Volanea API key be stored?

Store it only in server-side secret storage or an encrypted environment variable used by your middleware. Do not place it in OneSignal event properties, webhook bodies, browser code, mobile apps, or source repositories.

How do I stop duplicate emails when OneSignal retries a webhook?

Create a durable, unique idempotency key based on the business action, such as receipt:ord_80419, before calling Volanea. If the same webhook arrives again, return success without creating another send.

Can I send the recipient email address directly in the Journey webhook?

You can technically pass data available to the Journey, but it is safer to pass a user identifier and retrieve the current, authorized address from your application database. That prevents stale, malformed, or untrusted recipient data from becoming an email send.