Sending push notifications from a Node.js server
Send FCM push notifications from Node.js: firebase-admin setup, one user, many tokens, topics, all users, scheduling and cleaning up dead tokens.
This is the server side of push in Node.js with the Firebase Admin SDK: one device, one user with many devices, many users, everyone, and keeping your token list clean. It uses the HTTP v1 API, which replaced the legacy API in 2024.
Setup
npm install firebase-admin
Download a service account key from the Firebase console (Project settings, Service accounts) and keep it out of your repo, for example in an environment variable.
import { initializeApp, cert } from "firebase-admin/app";
import { getMessaging } from "firebase-admin/messaging";
initializeApp({ credential: cert(JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT)) });
const fcm = getMessaging();
Send to one device
await fcm.send({
token: deviceToken,
notification: { title: "Your order is out for delivery", body: "Ravi is 10 minutes away" },
data: { order_id: "88213" }, // strings only
android: { priority: "high" },
});
Send to every device of one user
FCM knows tokens, not users. Store tokens against your user id, then send to all of them.
const tokens = await db.tokens.find({ user_id: "usr_8f21c4" }); // your table
const res = await fcm.sendEachForMulticast({
tokens, // up to 500
notification: { title: "New login on Chrome", body: "Was this you?" },
});
// Remove tokens FCM says are dead
res.responses.forEach((r, i) => {
if (r.error?.code === "messaging/registration-token-not-registered") db.tokens.delete(tokens[i]);
});
Send to everyone, or a segment
Use topics. Subscribe when a device registers:
await fcm.subscribeToTopic(tokens, "all");
await fcm.subscribeToTopic(tokens, "city-pune");
Then one call reaches them all:
await fcm.send({ topic: "offers", notification: { title: "Weekend offer", body: "20% off fruit" } });
Scheduling
FCM sends immediately. To send at 6 pm, schedule the call yourself: a job queue such as BullMQ, a cron job, or a cloud scheduler that calls your endpoint. Store the job id so it can be cancelled.
Things that go wrong in production
- Numbers in
data. Every value must be a string, or FCM returnsINVALID_ARGUMENT. - Tokens never cleaned. Delete on
registration-token-not-registered; otherwise lists grow and sends slow down. - Sending one by one in a loop. Use
sendEachForMulticastor topics, and respect the default quota of 600,000 messages a minute per project. - Silent data messages for things users must see. They are not shown when the app is killed on many phones. See why FCM notifications are not received.
The same with SignOps
SignOps Push keeps the token table, topics, scheduling and cleanup for you. With your Firebase and Apple keys uploaded once:
import { SignOps } from "@signops/node";
const signops = new SignOps(process.env.SIGNOPS_KEY);
await signops.push.send({
user_id: "usr_8f21c4", // every device of this user
title: "Your order is out for delivery",
body: "Ravi is 10 minutes away",
send_at: "2026-10-24T18:00:00+05:30", // optional
});
Sources: Firebase: send messages with the Admin SDK, Firebase: topic messaging.