Push Notifications Guide
This guide covers everything you need to send push notifications through MsGine — whether you're using the dashboard as a business user or integrating the API as a developer.
How it works
MsGine delivers push notifications via Firebase Cloud Messaging (FCM). FCM is Google's cross-platform messaging service that routes notifications to Android, iOS, and web browsers.
Your app / backend
│
▼
MsGine API ──────► Firebase (FCM)
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Android iOS Web
device device browserMsGine handles batching, retry, stale-token cleanup, and delivery tracking so you don't have to.
For Dashboard Users
Step 1 — Create a Firebase project
- Go to console.firebase.google.com
- Click Add project → name it (e.g.,
my-app) → Create project - Click the
</>Web icon → register your app → copy thefirebaseConfigobject
Step 2 — Generate a service account key
- In your Firebase project: Project Settings → Service accounts
- Click Generate new private key → download the JSON file
- Open it and note the three values you need:
project_idclient_emailprivate_key
Step 3 — Connect Firebase to MsGine
Go to Messages → Push in the MsGine dashboard. You'll see the Connect Firebase banner. Fill in the three values from the JSON file and click Save & Validate. MsGine will verify the credentials immediately.
Once connected, you'll see a green Firebase connected badge with your project ID.
Step 4 — Send your first push notification
- Go to Compose → Push
- Fill in the title and body
- Leave Topic and User IDs empty to broadcast to all your registered devices
- Click Send Push
Step 5 — Track delivery
Go to Messages → Push. Each row shows:
- Title / Body — the notification content
- Recipients — how many devices were targeted
- Delivered/Total — e.g.
18/20 - Status —
sent,partial, orfailed
Click any row to see full details: first-delivered timestamp, first-opened timestamp, and delivery confirmation count.
For Developers
Prerequisites
- An API key with
SEND_PUSHscope — create one at Developer → API Keys - A Firebase project (or use the shared MsGine sender for quick testing)
Step 1 — (Optional) Connect your Firebase project
For white-label apps where notifications should appear from your own Firebase sender ID:
curl -X PUT https://api.msgine.net/api/v1/developers/push/config \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "your-firebase-project-id",
"clientEmail": "firebase-adminsdk@your-project.iam.gserviceaccount.com",
"privateKey": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n"
}'Skip this step to use the shared MsGine Firebase sender. You can always add it later.
Step 2 — Register device tokens from your app
In your mobile or web app, use the Firebase SDK to get an FCM token, then register it with MsGine. The externalUserId links the token to your own user system.
import { getToken } from 'firebase/messaging';
// Get FCM token (triggers browser permission prompt on first call)
const fcmToken = await getToken(messaging, {
vapidKey: 'YOUR_VAPID_KEY', // from Firebase Console → Cloud Messaging → Web Push certificates
});
// Register with MsGine
await fetch('https://api.msgine.net/api/v1/developers/push/device', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: fcmToken,
platform: 'web',
externalUserId: currentUser.id, // your own user identifier
}),
});// In your FirebaseMessagingService
override fun onNewToken(token: String) {
super.onNewToken(token)
// Re-register whenever the token rotates
registerWithMsGine(token)
}
fun registerWithMsGine(token: String) {
val body = JSONObject().apply {
put("token", token)
put("platform", "android")
put("externalUserId", currentUserId)
}
// POST to https://api.msgine.net/api/v1/developers/push/device
}// AppDelegate.swift
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
Messaging.messaging().token { token, error in
guard let token = token else { return }
// POST token to MsGine with platform: "ios"
}
}Token rotation
Call device registration on every app launch. Firebase tokens can rotate at any time. MsGine deduplicates automatically — re-registering the same token is safe and idempotent.
Step 3 — Send push notifications
curl -X POST https://api.msgine.net/api/v1/developers/push \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Payment received",
"body": "UGX 150,000 has been credited to your account.",
"externalUserIds": ["user_abc123"],
"data": { "screen": "billing", "txId": "TXN-456" }
}'curl -X POST https://api.msgine.net/api/v1/developers/push \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "App maintenance",
"body": "Scheduled maintenance on Sunday 2am–4am UTC.",
"topic": "all-users",
"priority": "normal"
}'import requests
resp = requests.post(
'https://api.msgine.net/api/v1/developers/push',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
json={
'title': 'New message',
'body': 'You have a new message from support.',
'externalUserIds': ['user_abc123'],
'data': {'chatId': 'chat_789'},
},
)
notification = resp.json()
print(notification['id']) # save this to confirm delivery laterconst res = await fetch('https://api.msgine.net/api/v1/developers/push', {
method: 'POST',
headers: {
'X-API-Key': process.env.MSGINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Order confirmed',
body: `Order #${order.id} is being prepared.`,
externalUserIds: [order.userId],
data: { orderId: order.id, screen: 'orders' },
idempotencyKey: `order-push-${order.id}`,
}),
});
const notification = await res.json();Step 4 — Report delivery from your app
When a user's device receives or taps the notification, call the delivery endpoint. This powers the deliveredAt, openedAt, and delivery rate metrics in the dashboard.
// In your push notification handler (foreground)
messaging.onMessage((payload) => {
const notificationId = payload.data?.notificationId;
if (notificationId) {
fetch('https://api.msgine.net/api/v1/developers/push/delivery', {
method: 'POST',
headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ notificationId, event: 'delivered' }),
});
}
});
// When user taps the notification (background click handler)
self.addEventListener('notificationclick', (event) => {
const notificationId = event.notification.data?.notificationId;
if (notificationId) {
// report 'opened' — do this from your app on launch if you can't do it in the SW
}
});Step 5 — Handle webhook events on your server
// Express.js example
app.post('/webhooks/msgine', express.json(), (req, res) => {
const { eventType, channel, data } = req.body;
if (channel === 'push') {
switch (eventType) {
case 'push.sent':
console.log(`Push delivered: ${data.successCount}/${data.recipientCount}`);
break;
case 'push.partial':
console.warn(`Partial delivery: ${data.failureCount} failed`);
// retry or alert if needed
break;
case 'push.failed':
console.error(`Push completely failed: ${data.error}`);
break;
}
}
res.sendStatus(200);
});Configure your webhook URL at Developer → Webhooks in the dashboard.
Targeting strategies
| Strategy | When to use | Field |
|---|---|---|
externalUserIds | Target specific users by your own ID | externalUserIds: ["user_1"] |
userIds | Target by MsGine UUID (dashboard users) | userIds: ["uuid-..."] |
topic | Broadcast to a named subscriber group | topic: "promotions" |
| Broadcast | Send to all registered devices | (leave targeting empty) |
Topics
Topics let users opt in to categories of notifications. Subscribe a device to a topic using Firebase SDK directly:
import { getMessaging, subscribeToTopic } from 'firebase/messaging';
// Firebase Admin (server-side)
await admin.messaging().subscribeToTopic(fcmToken, 'promotions');Then you can broadcast to that topic via MsGine without knowing individual device tokens.
Scheduling
Use scheduledFor to deliver at a specific time (ISO 8601, UTC):
{
"title": "Good morning!",
"body": "Your daily briefing is ready.",
"scheduledFor": "2026-06-01T06:00:00.000Z"
}A scheduledAt field in the response confirms the scheduled time.
Platform-specific tips
Android
- Always set
channelIdfor Android 8.0+ (Oreo). Without it, notifications are silently dropped. - Use
collapseKeyfor notifications that supersede each other (e.g., "you have N unread messages"). data-only messages (type: "data") are useful for silent syncs without showing a banner.
iOS
- Set
mutableContent: trueif you use a Notification Service Extension (e.g. for image attachments). - Use
contentAvailable: truefor silent background updates (your app receives the payload without showing a banner). - The
badgecount is set per-notification — manage it in sync with your app's unread count.
Web
- Web Push requires HTTPS. Localhost is allowed for development.
- Set
requireInteraction: truefor critical alerts that should stay visible until dismissed. - The
linkfield controls which URL opens when the user clicks the notification.
Troubleshooting
No devices found
400 No active devices found means no device tokens are registered under the target user.
- Verify device registration was called after login.
- Check
externalUserIdmatches exactly (case-sensitive). - Tokens may have been deactivated — re-register on next app launch.
Credentials invalid
400 Invalid Firebase credentials on PUT /push/config means one of the three fields is wrong.
- Ensure
privateKeyincludes the full PEM block including-----BEGIN/END PRIVATE KEY-----headers. - Newlines in the key must be
\n(literal backslash-n in JSON), not actual line breaks. - Re-download the JSON from Firebase Console if unsure.
Firebase not configured
400 Push notifications are not configured means no Firebase credentials are saved and the shared MsGine sender is also unavailable. Save credentials via PUT /push/config or contact support.
Token deactivation
MsGine automatically deactivates tokens that FCM reports as invalid (messaging/registration-token-not-registered). Re-registration on next app launch will restore delivery.
Pricing
Push notifications are billed per device reached (not per send request):
| Currency | Price per delivery |
|---|---|
| UGX | 0.38 |
| USD | 0.0001 |
A send to 1,000 devices costs UGX 380 / USD 0.10. Failed deliveries are not charged.
Next steps
- Push API Reference — Complete parameter reference
- Webhooks — Receive delivery events on your server
- Authentication — API key management
- Rate Limits — Default limits and burst behaviour