OneSignal Email
Transports

OneSignal Email

Send emails via the OneSignal HTTP API

The OneSignal Email transport is part of @betternotify/onesignal. It delivers email through the OneSignal HTTP API. It uses plain fetch() with zero external dependencies, so it works in Node.js, Bun, Cloudflare Workers, and any runtime with a global fetch.

The package also ships push and SMS transports — each is independent, pick only what you need.

Getting your API key

  1. Sign up or log in at onesignal.com.
  2. Open your app in the dashboard and copy the App ID from Settings → Keys & IDs.
  3. From the same screen, copy the REST API Key.

Store them as environment variables:

ONESIGNAL_APP_ID=00000000-0000-0000-0000-000000000000
ONESIGNAL_API_KEY=os_v2_app_xxxxxxxxxxxxxxxxxxxxxxxxx

Install

npm install @betternotify/onesignal @betternotify/core @betternotify/email

Usage

import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { onesignalEmailTransport } from '@betternotify/onesignal';

const email = emailChannel({
  defaults: { from: { name: 'My App', email: 'noreply@example.com' } },
});

const rpc = createNotify({ channels: { email } });
const catalog = rpc.catalog({
  /* routes */
});

const notify = createClient({
  catalog,
  transportsByChannel: {
    email: onesignalEmailTransport({
      appId: process.env.ONESIGNAL_APP_ID!,
      apiKey: process.env.ONESIGNAL_API_KEY!,
    }),
  },
});

Targeting

Email addresses are passed through the to field on RenderedMessage and mapped to OneSignal's email_to field.

Endpoint: POST /notifications?c=email

OneSignal is a notification platform first — its email API is oriented toward broadcast and campaigns, not deep transactional metadata. The following RenderedMessage fields are silently dropped: cc, bcc, custom headers, attachments, inlineAssets, tags, and priority. If you need them, prefer a dedicated email provider transport (e.g. Resend, Mailchimp).

Options

Prop

Type

Custom OneSignal fields

The transport maps RenderedMessage fields to the OneSignal API automatically. For any OneSignal-specific field not covered by the rendered message, use body (applied to every send) and bodyFor (per-route overrides).

Per-send overrides

You can also pass OneSignal fields per-send via the transport key in .send():

await notify.welcome.send({
  to: 'user@example.com',
  input: { name: 'Alice' },
  transport: {
    onesignal: { email_preheader: 'Welcome aboard!' },
  },
});

Merge order

body (global) → bodyFor[route] (per-route) → transport.onesignal (per-send) → mapped rendered fields (always win).

Common email fields

template_id, email_preheader, included_segments, filters, include_aliases, send_after, idempotency_key

See the OneSignal API reference for the full list of supported fields.

Error handling

The transport maps HTTP status codes to Better-Notify error codes using the shared mapHttpStatus utility:

HTTP statusBetter-Notify codeRetriable
400VALIDATIONNo
422VALIDATIONNo
401CONFIGNo
403CONFIGNo
429RATE_LIMITEDYes
5xxPROVIDERYes

Network failures are wrapped as PROVIDER errors and timeouts as TIMEOUT errors.

Empty-id responses

OneSignal returns HTTP 200 with id: "" when every targeted subscription, address, or phone number is invalid or unsubscribed. The transport surfaces this as a non-retriable VALIDATION error so failover (via multiTransport) and queue retry layers don't waste attempts on impossible sends.

On this page