Lesson 9 of 15Push Notification System

Push Notification System

Push notifications feel instant, but the architecture has many moving parts. Your app does not send directly to a phone. It sends to platform providers.

Your Worker
  |
  +--> FCM -> Android device
  +--> APNS -> iOS device
  +--> Web Push -> Browser

Device Tokens

Each installed app instance registers a token.

CREATE TABLE device_tokens (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  platform TEXT NOT NULL,
  token TEXT NOT NULL,
  app_version TEXT,
  last_seen_at TIMESTAMPTZ,
  disabled_at TIMESTAMPTZ,
  UNIQUE (platform, token)
);

A user can have multiple devices. A device token can expire. The worker must remove invalid tokens when the provider says they are no longer registered.

Payload Example

{
  "token": "device-token",
  "notification": {
    "title": "Package out for delivery",
    "body": "Your order ORD-123 will arrive today."
  },
  "data": {
    "type": "ORDER_STATUS",
    "orderId": "ORD-123",
    "deepLink": "app://orders/ORD-123"
  }
}

The notification section is shown by the OS. The data section helps the app open the right screen.

Deep Linking

Deep links turn a tap into context.

Push: Package out for delivery
Tap
  |
  v
App opens order tracking page

Validate deep links. Do not allow arbitrary redirect URLs from notification variables.

Priority

High priority can wake the device faster but should be reserved for urgent messages. If everything is high priority, mobile platforms may throttle you.

Examples:

High priority: OTP, suspicious login, ride arriving
Normal priority: comment liked, weekly digest
Low priority: marketing campaign

Silent Notifications

A silent notification wakes the app to refresh data without showing visible UI.

Use cases:

  1. Refresh unread count.
  2. Sync local cache.
  3. Update badge count.

Do not use silent notifications as a hidden tracking channel. Mobile platforms restrict abuse.

Badge Counts

Badge counts require a source of truth. If multiple notifications are read on web, the mobile badge should eventually update.

Unread count table
  |
  v
Push payload badge: 7

Worker Flow

for (const token of userDeviceTokens) {
  try {
    await pushProvider.send({
      token: token.value,
      title,
      body,
      data
    });
  } catch (error) {
    if (isExpiredToken(error)) {
      await disableDeviceToken(token.id);
      continue;
    }
    throw error;
  }
}

A push provider can accept a message even if the user never sees it. Device state, OS settings, and notification permissions all matter.

Common Mistakes

  1. Storing one token per user instead of one token per device.
  2. Not removing expired tokens.
  3. Putting too much data in payloads.
  4. Using high priority for everything.
  5. Trusting deep links without validation.

Interview Questions

  1. What is the difference between FCM and APNS?
  2. Why can one user have many device tokens?
  3. What is a silent notification?
  4. Why should expired tokens be cleaned up?

Exercise

Design a push notification for "driver arriving in 2 minutes." Include title, body, data payload, priority, and deep link.

What you will learn

How FCM, APNS, and Web Push fit together.

How device tokens are stored and expired.

How notification payloads support deep links and priority.

How silent notifications and badge counts work.

Production checklist

  • Device tokens are stored per user and platform
  • Expired tokens are removed
  • Payload size is controlled
  • Deep links are validated
  • Priority is used carefully
  • Provider feedback is processed
Push Notification System - Production Notification System Design | Niraj Kumar