Skip to content

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        browser

MsGine handles batching, retry, stale-token cleanup, and delivery tracking so you don't have to.


For Dashboard Users ​

Step 1 — Create a Firebase project ​

  1. Go to console.firebase.google.com
  2. Click Add project → name it (e.g., my-app) → Create project
  3. Click the </> Web icon → register your app → copy the firebaseConfig object

Step 2 — Generate a service account key ​

  1. In your Firebase project: Project Settings → Service accounts
  2. Click Generate new private key → download the JSON file
  3. Open it and note the three values you need:
    • project_id
    • client_email
    • private_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 ​

  1. Go to Compose → Push
  2. Fill in the title and body
  3. Leave Topic and User IDs empty to broadcast to all your registered devices
  4. 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, or failed

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_PUSH scope — 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:

bash
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.

js
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
  }),
});
kotlin
// 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
}
swift
// 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 ​

bash
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" }
  }'
bash
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"
  }'
python
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 later
js
const 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.

js
// 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 ​

js
// 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 ​

StrategyWhen to useField
externalUserIdsTarget specific users by your own IDexternalUserIds: ["user_1"]
userIdsTarget by MsGine UUID (dashboard users)userIds: ["uuid-..."]
topicBroadcast to a named subscriber grouptopic: "promotions"
BroadcastSend 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:

js
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):

json
{
  "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 channelId for Android 8.0+ (Oreo). Without it, notifications are silently dropped.
  • Use collapseKey for 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: true if you use a Notification Service Extension (e.g. for image attachments).
  • Use contentAvailable: true for silent background updates (your app receives the payload without showing a banner).
  • The badge count 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: true for critical alerts that should stay visible until dismissed.
  • The link field 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 externalUserId matches 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 privateKey includes 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):

CurrencyPrice per delivery
UGX0.38
USD0.0001

A send to 1,000 devices costs UGX 380 / USD 0.10. Failed deliveries are not charged.


Next steps ​

Released under the MIT License.