Mobile Architecture

Push Notifications

End-to-end architecture for push notifications: APNs and FCM infrastructure, device token lifecycle, payload design, silent push, delivery analytics, and opt-in UX patterns.

⏱ 10 min read

What it is

Push notifications are server-initiated messages delivered to a mobile app via platform-managed infrastructure: Apple Push Notification service (APNs) for iOS/macOS and Firebase Cloud Messaging (FCM) for Android (and cross-platform). The push infrastructure maintains persistent connections between the platform's servers and user devices, allowing backend systems to wake or alert apps without the app maintaining its own long-lived network connection — which would drain the battery.

There are two categories of push notification: visual notifications that appear in the notification tray and alert the user, and silent/background notifications (APNs: content-available: 1; FCM: data-only messages) that wake the app in the background to perform an operation (such as triggering a sync) without displaying anything to the user. The two types have different delivery guarantees, rate limits, and use cases.

Why it exists

Before push notifications existed, apps used polling — making regular API requests to check for new data. Polling is battery-inefficient (network radio activation is one of the most expensive mobile power operations), wastes server capacity, and introduces latency equal to the polling interval. Push notifications eliminate polling by allowing the server to initiate contact precisely when there is something to communicate, with the platform managing the delivery channel efficiently.

When to use

  • Real-time alerts: order shipped, message received, ride arriving, payment confirmed.
  • Re-engagement: cart abandonment reminders, scheduled digest notifications, personalised recommendations.
  • Silent sync triggers: notify the app that new content is available, triggering a background sync.
  • Transactional communications where email latency is unacceptable (OTP codes, security alerts).

When not to use

  • Silent notifications should not be used as a replacement for foreground data fetching — they are rate-limited and not guaranteed to be delivered.
  • Notification spam (more than one non-critical notification per day per user) destroys opt-in rates and drives uninstalls.
  • Do not put sensitive data directly in the notification payload — payload can be logged by intermediate systems; fetch sensitive details from the API on notification tap instead.

Typical architecture


PUSH NOTIFICATION FLOW
────────────────────────
  1. App registers with APNs/FCM on first launch
  2. APNs/FCM returns device token (opaque identifier for this app+device)
  3. App sends token to backend; stored in devices table
  4. Backend event triggers notification decision (order shipped, etc.)
  5. Backend sends message to APNs/FCM with device token + payload
  6. APNs/FCM delivers to device (best-effort, FIFO per device)
  7. Device displays notification; user taps → app opens

DEVICE TOKEN LIFECYCLE
───────────────────────
  Token created:   app install + first launch
  Token changes:   app reinstall, factory reset, iCloud restore to new device
  Token invalidated: user deletes app (FCM: UNREGISTERED error)

  Backend must:
  - Store token per user+device (a user may have multiple devices)
  - Update token when app reports a new token (onNewToken callback)
  - Delete token when FCM returns UNREGISTERED or APNs returns 410 Gone

APNS SPECIFICS
───────────────
  APNs HTTP/2 provider API (token-based auth with .p8 key file)
  Required headers:
    apns-topic: com.example.myapp (bundle ID)
    apns-push-type: alert | background | voip | complication | fileprovider
    apns-priority: 10 (immediate) | 5 (power considerations)
    apns-expiration: 0 (discard if not delivered immediately) | Unix timestamp
  Background notification:
    { "aps": { "content-available": 1 } }  — silent; no alert
    Max 3 per hour per device; throttled by iOS

FCM SPECIFICS
──────────────
  Firebase Admin SDK (Node.js, Python, Java, Go, .NET)
  Message structure:
    notification: { title, body }  — display notification
    data: { key: value }           — custom payload; always delivered
    android/apns/webpush: platform-specific overrides
  Topic messaging: send to /topics/news → all subscribers receive
  Condition messaging: 'sports' in topics && 'en' in topics

NOTIFICATION PAYLOAD DESIGN
────────────────────────────
  Bad:  { "body": "Your order has shipped", "orderId": "ORD-123" }
         → App must already have orderId in memory or must fetch full order

  Good: { "body": "Your MacBook has shipped — arriving Thursday",
           "deep_link": "myapp://orders/ORD-123",
           "action": "VIEW_ORDER",
           "order_id": "ORD-123",
           "carrier": "FedEx",
           "tracking": "7489234723" }
         → Actionable without additional API call for most cases
         → Deep link navigates directly to correct screen on tap

Pros and cons

Pros

  • Server-initiated delivery eliminates polling, saving battery and server resources.
  • Platform-managed delivery channel maintains persistence without app involvement.
  • Deep-link payloads enable contextual navigation directly to relevant content on tap.
  • Topic/condition subscriptions enable efficient broadcast to user segments without per-device loops.

Cons

  • Requires user opt-in on iOS (since iOS 10) — users who decline cannot receive visual notifications.
  • Delivery is best-effort: APNs/FCM may delay or drop notifications during device offline periods.
  • Silent notifications are heavily throttled — not suitable for real-time data delivery.
  • Managing device tokens at scale (millions of users, multi-device) requires careful token hygiene to avoid wasted sends.

Implementation notes

Opt-in UX: iOS requires explicit permission before showing any visual notifications. The permission dialog can only be shown once — if the user declines, re-requesting requires navigating to Settings. Best practice: do not request permission on first launch. Instead, show a custom pre-permission prompt explaining the value ("We'll notify you when your order ships and when your driver is nearby") and only call requestAuthorization after the user agrees to the custom prompt. This context-setting increases opt-in rates from ~40% to 70%+ in studies. On Android 13+, the same explicit permission is required (POST_NOTIFICATIONS).

Notification analytics: Track three events per notification: sent (your backend sent to APNs/FCM), delivered (device received — requires iOS notification service extension / FCM delivery receipt), and opened (user tapped). The funnel reveals issues: high sent + low delivered = token hygiene problem; high delivered + low opened = content relevance problem. Use a notification ID in the payload to correlate these events in your analytics pipeline.

Common failure modes

  • Sending to stale tokens: Not cleaning up invalid tokens causes silent delivery failures; FCM returns UNREGISTERED, APNs returns HTTP 410 — delete these tokens immediately.
  • Permission request on first launch: Asking for notification permission as the first action on first launch is the leading cause of notification opt-out; users have no context for why they should allow it.
  • Using silent push for critical data: Silent notifications are throttled by both iOS and Android; time-sensitive data (chat messages, location updates) should not rely on silent push for delivery.
  • Missing deep-link handling: The notification payload contains a deep link but the app does not handle being opened from a cold start via that deep link — user is taken to the home screen instead of the relevant content.

Decision checklist

  • Is the notification opt-in prompt shown after in-app context, not on first launch?
  • Does the backend clean up stale tokens returned as UNREGISTERED/410 by the push services?
  • Does every notification payload include a deep link for direct navigation on tap?
  • Are notification sent/delivered/opened events tracked for analytics?
  • Is the notification payload designed to be actionable without an additional API call for the common case?
  • Are notification categories (alerts vs promotional) aligned with user preferences and regulatory requirements?

Example use cases

  • E-commerce: Order status notifications (processing → shipped → out for delivery → delivered) each with tracking deep link; silent notification triggers cart sync when backend detects price change on a saved item.
  • Ride-hailing: High-priority APNs alert when driver is 2 minutes away; silent FCM data message every 10 seconds updates driver location on map (rate within FCM limits).
  • News app: Breaking news uses high-priority alert notification; daily digest uses scheduled low-priority notification delivered during user's local morning.
  • Mobile API Design — Push notification infrastructure integrates with the BFF for device token management and notification delivery.
  • Mobile Security — Notification payloads must not contain sensitive data; notification service extensions that fetch content must use authenticated API calls.
  • Offline-First Design — Silent notifications are often used as triggers for offline-first sync workers.

Further reading