Skip to main content
Technology & EngineeringMessaging Services407 lines

OneSignal

"OneSignal: push notifications, in-app messaging, email, SMS, segments, journeys, REST API"

Quick Summary18 lines
OneSignal is a multi-channel customer engagement platform focused on push notifications, in-app messaging, email, and SMS. The platform manages device registration, audience segmentation, and delivery optimization so you focus on when and what to send rather than how to deliver it. OneSignal's strength is its segment-based targeting: define audience segments using tags, user behavior, and device properties, then target notifications at segments rather than individual users. Use external user IDs to link OneSignal players to your user system. Design your notification strategy around segments and journeys (automated sequences), using the REST API for programmatic triggers and the dashboard for marketing campaigns.

## Key Points

- Always link OneSignal players to your user system with `login(externalUserId)` so you can target by your own user IDs rather than OneSignal player IDs
- Use segments for recurring campaigns and `include_external_user_ids` for transactional notifications triggered by user actions
- Set a TTL (time-to-live) on push notifications so stale messages are not delivered hours or days later
- Use the `delayed_option: "timezone"` feature to send campaigns at a consistent local time across time zones
- Tag users with behavioral and demographic data to enable precise segmentation without querying your own database
- Test notifications on small segments before sending to your full audience — OneSignal supports A/B testing built in
- Implement notification click handlers to track engagement and route users to the right content
- Use in-app messages for non-urgent contextual messaging; reserve push notifications for time-sensitive or high-value alerts
- **Sending push notifications without user opt-in context** — Prompting for push permission immediately on first visit leads to low opt-in rates; explain the value first, then prompt
- **Over-segmenting with too many tags** — Hundreds of tags per user creates management complexity; use a few well-chosen tags and let OneSignal's built-in filters handle the rest
- **Using segments for transactional notifications** — Segments are for bulk targeting; for individual user notifications (order updates, password resets), use `include_external_user_ids` directly
- **Ignoring failed deliveries** — Check the `failed` and `errored` counts in notification outcomes; persistent failures indicate stale subscriptions or provider issues
skilldb get messaging-services-skills/OneSignalFull skill: 407 lines
Paste into your CLAUDE.md or agent config

OneSignal Notifications Platform

Core Philosophy

OneSignal is a multi-channel customer engagement platform focused on push notifications, in-app messaging, email, and SMS. The platform manages device registration, audience segmentation, and delivery optimization so you focus on when and what to send rather than how to deliver it. OneSignal's strength is its segment-based targeting: define audience segments using tags, user behavior, and device properties, then target notifications at segments rather than individual users. Use external user IDs to link OneSignal players to your user system. Design your notification strategy around segments and journeys (automated sequences), using the REST API for programmatic triggers and the dashboard for marketing campaigns.

Setup

Server-Side REST API Client

const ONESIGNAL_APP_ID = process.env.ONESIGNAL_APP_ID!;
const ONESIGNAL_REST_API_KEY = process.env.ONESIGNAL_REST_API_KEY!;

interface OneSignalNotification {
  app_id: string;
  contents?: Record<string, string>;
  headings?: Record<string, string>;
  include_external_user_ids?: string[];
  included_segments?: string[];
  excluded_segments?: string[];
  data?: Record<string, unknown>;
  url?: string;
  web_url?: string;
  app_url?: string;
  filters?: OneSignalFilter[];
  send_after?: string;
  delayed_option?: "timezone" | "last-active";
  ttl?: number;
}

interface OneSignalFilter {
  field: string;
  key?: string;
  relation: string;
  value: string;
}

async function sendOneSignalRequest(endpoint: string, body: unknown): Promise<unknown> {
  const response = await fetch(`https://onesignal.com/api/v1/${endpoint}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Basic ${ONESIGNAL_REST_API_KEY}`,
    },
    body: JSON.stringify(body),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`OneSignal API error: ${JSON.stringify(error)}`);
  }
  return response.json();
}

Client-Side Web SDK Setup

// Initialize in your app's entry point
async function initOneSignal() {
  await window.OneSignalDeferred?.push(async (OneSignal) => {
    await OneSignal.init({
      appId: process.env.NEXT_PUBLIC_ONESIGNAL_APP_ID!,
      allowLocalhostAsSecureOrigin: process.env.NODE_ENV === "development",
      notifyButton: { enable: true },
      serviceWorkerParam: { scope: "/push/onesignal/" },
      serviceWorkerPath: "/push/onesignal/OneSignalSDKWorker.js",
    });
  });
}

// React hook for OneSignal
function useOneSignal(userId?: string) {
  useEffect(() => {
    if (!userId) return;

    window.OneSignalDeferred?.push(async (OneSignal) => {
      await OneSignal.login(userId);
    });

    return () => {
      window.OneSignalDeferred?.push(async (OneSignal) => {
        await OneSignal.logout();
      });
    };
  }, [userId]);
}

Key Techniques

Sending Push Notifications

// Send to specific users by external ID
async function sendPushToUsers(
  userIds: string[],
  title: string,
  message: string,
  data?: Record<string, unknown>,
  url?: string
) {
  const notification: OneSignalNotification = {
    app_id: ONESIGNAL_APP_ID,
    include_external_user_ids: userIds,
    headings: { en: title },
    contents: { en: message },
    data,
    url,
    ttl: 86400, // expire after 24 hours
  };

  return sendOneSignalRequest("notifications", notification);
}

// Send to a segment
async function sendPushToSegment(
  segments: string[],
  title: string,
  message: string,
  options?: { excludeSegments?: string[]; sendAfter?: Date; imageUrl?: string }
) {
  const notification: OneSignalNotification = {
    app_id: ONESIGNAL_APP_ID,
    included_segments: segments,
    excluded_segments: options?.excludeSegments,
    headings: { en: title },
    contents: { en: message },
    send_after: options?.sendAfter?.toISOString(),
  };

  if (options?.imageUrl) {
    (notification as Record<string, unknown>).big_picture = options.imageUrl;
    (notification as Record<string, unknown>).chrome_web_image = options.imageUrl;
  }

  return sendOneSignalRequest("notifications", notification);
}

// Send with action buttons
async function sendWithActions(userIds: string[], title: string, message: string) {
  const notification = {
    app_id: ONESIGNAL_APP_ID,
    include_external_user_ids: userIds,
    headings: { en: title },
    contents: { en: message },
    buttons: [
      { id: "accept", text: "Accept", icon: "ic_accept" },
      { id: "decline", text: "Decline", icon: "ic_decline" },
    ],
    web_buttons: [
      { id: "accept", text: "Accept", url: "https://yourapp.com/accept" },
      { id: "decline", text: "Decline", url: "https://yourapp.com/decline" },
    ],
  };

  return sendOneSignalRequest("notifications", notification);
}

User Tags and Segmentation

// Set tags on a user (server-side)
async function setUserTags(externalUserId: string, tags: Record<string, string | number>) {
  const response = await fetch(
    `https://onesignal.com/api/v1/apps/${ONESIGNAL_APP_ID}/users/by/external_id/${externalUserId}`,
    {
      method: "PATCH",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Basic ${ONESIGNAL_REST_API_KEY}`,
      },
      body: JSON.stringify({
        properties: { tags },
      }),
    }
  );
  return response.json();
}

// Set tags client-side
async function setClientTags(tags: Record<string, string>) {
  window.OneSignalDeferred?.push(async (OneSignal) => {
    for (const [key, value] of Object.entries(tags)) {
      await OneSignal.User.addTag(key, value);
    }
  });
}

// Send notification using tag filters
async function sendToFilteredUsers(
  title: string,
  message: string,
  filters: OneSignalFilter[]
) {
  const notification = {
    app_id: ONESIGNAL_APP_ID,
    filters,
    headings: { en: title },
    contents: { en: message },
  };

  return sendOneSignalRequest("notifications", notification);
}

// Example: notify premium users who haven't been active in 7 days
async function reEngagePremiumUsers() {
  await sendToFilteredUsers(
    "We miss you!",
    "Check out what's new since your last visit.",
    [
      { field: "tag", key: "plan", relation: "=", value: "premium" },
      { field: "last_session", relation: ">", value: "168" }, // hours
    ]
  );
}

Email Notifications

async function sendEmail(
  externalUserIds: string[],
  subject: string,
  htmlBody: string,
  options?: { preheader?: string; replyTo?: string }
) {
  const emailPayload = {
    app_id: ONESIGNAL_APP_ID,
    include_external_user_ids: externalUserIds,
    email_subject: subject,
    email_body: htmlBody,
    email_preheader: options?.preheader,
    email_reply_to_address: options?.replyTo,
    channel_for_external_user_ids: "email",
  };

  return sendOneSignalRequest("notifications", emailPayload);
}

// Send transactional email with template
async function sendTemplatedEmail(
  externalUserIds: string[],
  templateId: string,
  customData: Record<string, string>
) {
  const payload = {
    app_id: ONESIGNAL_APP_ID,
    include_external_user_ids: externalUserIds,
    template_id: templateId,
    custom_data: customData,
    channel_for_external_user_ids: "email",
  };

  return sendOneSignalRequest("notifications", payload);
}

SMS Notifications

async function sendSms(
  externalUserIds: string[],
  message: string,
  options?: { mediaUrl?: string }
) {
  const smsPayload: Record<string, unknown> = {
    app_id: ONESIGNAL_APP_ID,
    include_external_user_ids: externalUserIds,
    contents: { en: message },
    channel_for_external_user_ids: "sms",
    sms_media_urls: options?.mediaUrl ? [options.mediaUrl] : undefined,
  };

  return sendOneSignalRequest("notifications", smsPayload);
}

In-App Messaging

// In-app messages are configured via the OneSignal dashboard
// but triggered programmatically using tags/triggers

// Set a trigger to show an in-app message
async function setInAppTrigger(key: string, value: string) {
  window.OneSignalDeferred?.push(async (OneSignal) => {
    await OneSignal.InAppMessages.addTrigger(key, value);
  });
}

// Remove a trigger
async function removeInAppTrigger(key: string) {
  window.OneSignalDeferred?.push(async (OneSignal) => {
    await OneSignal.InAppMessages.removeTrigger(key);
  });
}

// React component to trigger in-app messages based on user actions
function InAppMessageTrigger({ onAction }: { onAction: (actionId: string) => void }) {
  useEffect(() => {
    window.OneSignalDeferred?.push(async (OneSignal) => {
      OneSignal.InAppMessages.addEventListener("click", (event) => {
        const clickData = event.result;
        if (clickData.actionId) {
          onAction(clickData.actionId);
        }
        if (clickData.url) {
          window.location.href = clickData.url;
        }
      });
    });
  }, [onAction]);

  return null;
}

// Trigger in-app message after specific user behavior
function usePageViewTrigger(pageName: string) {
  useEffect(() => {
    setInAppTrigger("page_view", pageName);

    return () => {
      removeInAppTrigger("page_view");
    };
  }, [pageName]);
}

Tracking and Analytics

// Track notification outcomes
async function getNotificationStats(notificationId: string) {
  const response = await fetch(
    `https://onesignal.com/api/v1/notifications/${notificationId}?app_id=${ONESIGNAL_APP_ID}`,
    {
      headers: { Authorization: `Basic ${ONESIGNAL_REST_API_KEY}` },
    }
  );
  const data = await response.json();

  return {
    sent: data.successful,
    opened: data.converted,
    ctr: data.converted / data.successful,
    failed: data.failed,
    errored: data.errored,
    remaining: data.remaining,
  };
}

// Webhook handler for notification events
import express from "express";

const app = express();
app.use(express.json());

app.post("/webhooks/onesignal", (req, res) => {
  const event = req.body;

  switch (event.event) {
    case "notification.displayed":
      console.log(`Notification ${event.id} displayed to ${event.userId}`);
      break;
    case "notification.clicked":
      console.log(`Notification ${event.id} clicked by ${event.userId}`);
      // Track conversion
      break;
    case "notification.dismissed":
      console.log(`Notification ${event.id} dismissed by ${event.userId}`);
      break;
  }

  res.sendStatus(200);
});

Best Practices

  • Always link OneSignal players to your user system with login(externalUserId) so you can target by your own user IDs rather than OneSignal player IDs
  • Use segments for recurring campaigns and include_external_user_ids for transactional notifications triggered by user actions
  • Set a TTL (time-to-live) on push notifications so stale messages are not delivered hours or days later
  • Use the delayed_option: "timezone" feature to send campaigns at a consistent local time across time zones
  • Tag users with behavioral and demographic data to enable precise segmentation without querying your own database
  • Test notifications on small segments before sending to your full audience — OneSignal supports A/B testing built in
  • Implement notification click handlers to track engagement and route users to the right content
  • Use in-app messages for non-urgent contextual messaging; reserve push notifications for time-sensitive or high-value alerts

Anti-Patterns

  • Sending push notifications without user opt-in context — Prompting for push permission immediately on first visit leads to low opt-in rates; explain the value first, then prompt
  • Over-segmenting with too many tags — Hundreds of tags per user creates management complexity; use a few well-chosen tags and let OneSignal's built-in filters handle the rest
  • Using segments for transactional notifications — Segments are for bulk targeting; for individual user notifications (order updates, password resets), use include_external_user_ids directly
  • Ignoring failed deliveries — Check the failed and errored counts in notification outcomes; persistent failures indicate stale subscriptions or provider issues
  • Not setting notification expiry — Push notifications without TTL can arrive hours late and confuse users; always set a reasonable expiry window
  • Sending identical content across all channels — Email, push, SMS, and in-app have different constraints and user expectations; tailor content length and format per channel
  • Bypassing OneSignal's rate limiting — OneSignal throttles to protect deliverability; sending too many API requests or notifications in a burst degrades performance and sender reputation
  • Hardcoding OneSignal player IDs — Player IDs change when users reinstall; always use external user IDs for targeting and let OneSignal manage the device-to-user mapping

Install this skill directly: skilldb add messaging-services-skills

Get CLI access →