Send mobile, web, and multichannel push with Node.js and Courier. Sync device tokens via the mobile SDKs and send to a user in one API call.
Updated Jul 6, 2026
Push notifications are one of the most direct ways to reach users, letting you communicate with them even when they are not in your app or on your site. Timely, relevant push can lift engagement and retention, as long as the messages are worth the interruption.
Building and maintaining your own push infrastructure is real work: you design templates, manage authentication for each push provider, handle token lifecycle, logging, analytics, and the rules for when messages go out. Courier removes most of that. You send to a user through one API, and Courier renders the content, routes to the right provider, and records delivery.
In this guide you'll learn how mobile, web, and multichannel push notifications work, and how to send them with Node.js and Courier.
A push notification is a message sent from a remote server to an app or browser on a phone, computer, or other device. The user does not need the app open to receive it: on mobile it appears on the lock screen or in the notification center, and in a browser it appears as a small banner.
Users must opt in before an app or site can send push, so permission handling is part of every implementation. Once a user opts in, push is useful for product updates, transactional alerts (an order shipped, a login from a new device), event-driven reminders (flight check-in has opened), and location-aware messages (your driver is arriving).
The most effective notification strategies are multichannel: the same event can reach a user by push, email, SMS, or in-app inbox, and you let their preferences decide which. That is where a notification service earns its keep, because you send once and it fans out across channels and providers.
To send mobile push you need a push provider that delivers to the device operating system: Firebase Cloud Messaging (FCM) for Android (and iOS via APNs), and the Apple Push Notification service (APNs) for iOS.
The flow is: your app registers with the OS and receives a device token, that token is stored against a user, and your Node.js backend makes an API call to send a notification to that user. The notification service renders the content and hands it to the push provider, which delivers to the device (or holds it briefly if the device is offline).
The one piece teams underestimate is token lifecycle: tokens rotate, apps get reinstalled, and stale tokens need cleaning up. Courier's mobile SDKs handle this by syncing the device token to the signed-in user for you, which is what lets your backend send to a user_id instead of tracking raw tokens.
For browser (web) push, Courier delivers through OneSignal, which is purpose-built for web push and covers Chrome, Firefox, Edge, and Safari. Courier orchestrates it the same way as mobile: you send to a user, and Courier routes the web channel to OneSignal.
Set up the OneSignal integration in Courier (App ID + REST API Key). On your site, use the OneSignal Web SDK to subscribe the browser and set OneSignal's External ID to your app's user_id. Store that on the Courier user profile as oneSignalExternalUserId (or the Player ID as oneSignalPlayerID) so Courier can target it. Your backend then sends to the user and routes the onesignal channel:
await client.send.message({message: {to: { user_id: "user_123" },content: { title: "New reply", body: "You have a new message." },routing: { method: "all", channels: ["onesignal"] },},});
If instead you want an in-app notification feed inside your web app (rather than browser push), Courier's Inbox renders one from the same send.
Now let's build a multichannel push flow. You'll send to a user across their devices with a single API call, using FCM and APNs through Courier.
Prerequisites:
Open the Courier Integrations page and configure a push provider. Add Firebase FCM for Android and web, and Apple Push Notification (APNs) for iOS. Courier will route to whichever providers you enable.
Create a template in Design Studio. Add a Push channel, write your content (you can use variables like {{name}} for personalization), and publish. Note the template's ID, which you'll reference when you send. You can also skip the template and send inline content, shown below.
Push is delivered to devices, so Courier needs each user's device tokens. The client app collects them using a Courier mobile SDK, which syncs the token to the signed-in user automatically. For a React Native app, use the @trycourier/courier-react-native SDK; for native apps, use the iOS or Android SDKs. Each signs the user in with a JWT your backend mints and then keeps FCM and APNs tokens current for that user_id.
Because the SDK handles tokens, your backend never touches raw tokens. It sends to a user_id and Courier resolves the devices. (See Users and Profiles for how tokens attach to a user.)
mkdir courier-push-notifications && cd courier-push-notificationstouch index.jsnpm init -y
Set the project type to module in package.json:
"type": "module"
npm install @trycourier/courier
Open index.js and add the code below. It creates a client with your Courier API key and sends a push to a user across their FCM and APNs devices:
import Courier from "@trycourier/courier";const client = new Courier({ apiKey: process.env.COURIER_API_KEY });const { requestId } = await client.send.message({message: {to: { user_id: "user_123" },content: {title: "Your order shipped",body: "Track it anytime in the app.",},routing: { method: "all", channels: ["firebase-fcm", "apn"] },providers: {"firebase-fcm": {override: { body: { data: { click_action: "https://example.com/orders" } } },},apn: {override: { body: { aps: { sound: "default", badge: 1 } } },},},},});console.log(`Sent with request ID: ${requestId}`);
A few things to note:
to: { user_id: "user_123" }, and Courier delivers to every device token synced for that user.routing.channels lists provider keys (firebase-fcm, apn, and expo if you use it), not a generic "push". method: "all" sends every listed channel; "single" tries them in order for failover.content with template: "YOUR_TEMPLATE_ID" and add a data object for your variables. Note that provider override fields like sounds and badges are not compatible with a template on the same send.Run the app with your API key (available under Settings in the Courier dashboard):
COURIER_API_KEY=<your-api-key> node index.js
You'll get back a requestId. Open Message Logs to see the per-recipient delivery timeline, which providers were attempted, and the rendered content.
Do I need Firebase to send push with Node.js? For Android, yes: Android push is delivered through FCM. For iOS you can use APNs directly. With Courier you configure whichever providers you need and send to all of them through one call.
How do device tokens get to Courier?
Your client app collects them with a Courier mobile SDK (React Native, iOS, or Android), which syncs each token to the signed-in user_id. Your Node.js backend then sends to that user and never handles raw tokens.
Can one send reach a user on both iOS and Android?
Yes. Send to a user_id with routing.channels: ["firebase-fcm", "apn"] and method: "all", and Courier delivers to every synced device.
What about web push? Deliver browser push through your web push provider, or use Courier's Inbox for an in-app notification feed rendered from the same send.
How do I personalize the notification?
Send a template built in Design Studio and pass a data object (for example { "name": "Nomen Nescio" }), or include inline content with variables. Courier renders it per recipient.
Push notifications connect you directly with users and carry timely, relevant information for everything from transactional alerts to event-driven reminders. With Courier and its Node.js SDK, you send to a user across FCM, APNs, and Expo with one API call, let mobile SDKs handle token sync, and see exactly what happened in Message Logs. To go further, explore channel routing and failover and the full Send API.
Author: Zara Cooper
Keep exploring
One API, every channel
Courier gives you one API for email, SMS, push, and chat, with templates, routing, retries, and delivery logs built in.
Last updated Jul 6, 2026. Code samples are illustrative; provider APIs and pricing change over time, so check each provider’s docs before relying on them.
© 2026 Courier. All rights reserved.