Design push notification systems that engage without annoying users. Covers platform-specific delivery (APNs, FCM), notification channels, rich notifications, delivery tracking, and the patterns that maximize open rates while respecting user attention.
Mobile push notification architecture defines the end-to-end flow of messages from a server to a mobile device, from the moment a notification is triggered to its delivery, rendering, and user interaction. It matters when a mobile app needs to communicate time-sensitive, user-specific, or event-driven updates—such as real-time chat messages, order status changes, or personalized offers—without requiring the app to be actively running.
A mobile push notification begins when a server sends a payload to a platform-specific push service. The service delivers the message to the target device, which may be idle, backgrounded, or foregrounded. The operating system (iOS or Android) handles delivery, presentation, and interaction, and the app may respond to the notification by fetching data or updating state.
The lifecycle consists of four phases: creation, delivery, presentation, and interaction.
A notification is created on the server side, typically triggered by a business event: a new message arrives, a user’s order ships, or a background job completes. The payload is structured as a JSON object sent over HTTP to the push service.
A minimal notification payload for both platforms:
{
"to": "device_token_123",
"title": "New Message",
"body": "Alice sent you a photo.",
"data": {
"message_id": "msg_456",
"category": "chat"
},
"aps": {
"alert": {
"title": "New Message",
"body": "Alice sent you a photo."
},
"badge": 1,
"sound": "default"
}
}
The to field identifies the target device via a platform-specific token. The aps object contains Apple Push Notification Service (APNS) fields, while data holds application-specific metadata.
The server uses curl to send a notification to APNS:
curl -X POST https://api.push.apple.com/3/device/device_token_123 \
-H "Authorization: bearer <jwt_token>" \
-H "Content-Type: application/json" \
-d @notification.json
The Authorization: bearer <jwt_token> header uses an HMAC-signed JWT to authenticate the sender. The JWT includes kid, iss, and aud claims, and the aud is set to https://api.push.apple.com.
The push service processes the request and routes the message through its internal systems. It maintains a queue of pending notifications and uses a persistent connection to the device’s OS.
APNS uses HTTP/2 multiplexing to send multiple notifications over a single connection. Each notification is a stream with an :path header set to /3/device/<token>. The service returns a 200 OK response upon receipt, but delivery is not guaranteed.
A failure response might look like:
{
"reason": "BadDeviceToken",
"message": "The device token is invalid or not registered.",
"details": {
"token": "device_token_123",
"error_code": 4000
}
}
Common HTTP status codes:
200: Success, notification queued400: Client error (e.g., invalid JSON, malformed token)410: GCM/FCM: Device not registered; client should re-register412: GCM/FCM: App not registered; server must register it413: GCM/FCM: Payload too large (max 4096 bytes)500: Server error, retry laterDelivery is asynchronous. A notification may arrive minutes after creation, and the OS may batch multiple notifications into a single system alert.
When the notification is delivered, the OS presents it to the user through a system-level UI. Presentation behavior depends on the OS, the notification type, and the app’s configuration.
On iOS, presentation is governed by the aps dictionary. The alert object controls the title, body, and actions. The badge value updates the app’s icon badge. The sound key specifies a sound file or a built-in name.
On Android, the data object is used by the app to configure presentation. The system reads the notification key in the payload:
{
"to": "fcm_token_abc",
"notification": {
"title": "New Message",
"body": "Alice sent you a photo.",
"icon": "ic_notification",
"color": "#4CAF50",
"click_action": "OPEN_CHAT"
},
"data": {
"message_id": "msg_456",
"category": "chat"
}
}
The click_action field triggers a specific action when the user taps the notification. If click_action is OPEN_CHAT, the app opens the chat screen with message_id passed in the intent.
Presentation is not atomic. The OS may:
The app may also define notification channels on Android to group notifications by type (e.g., chat, promo, system). Each channel has a name, description, importance, and sound.
Interaction begins when the user taps, swipes, or long-presses a notification. The OS invokes the app, possibly creating a new instance or bringing an existing one to the foreground.
The interaction flow depends on how the app handles the incoming notification. On iOS, the app receives the notification via application(_:didReceive:withCompletionHandler:), and the app can fetch data in the background.
On Android, the system launches the app’s MainActivity with an Intent containing the data fields. The app can extract the message_id and category from the intent extras.
The interaction may include:
An interaction may also trigger a background fetch. The app requests data to update its state when the user taps the notification.
APNS requires a persistent connection to api.push.apple.com. The connection uses HTTP/2 with TLS 1.2. Each request is a stream with a unique :path and :authority.
APNS supports three types of messages:
:path and :methodA multicast request can send a notification to 100 devices with a single HTTP/2 stream:
[
{
"to": "device_token_123",
"title": "New Message",
"body": "Alice sent you a photo."
},
{
"to": "device_token_456",
"title": "New Message",
"body": "Bob sent you a document."
}
]
The server sends the array as a POST request with Content-Type: application/json, and APNS processes each object independently.
APNS supports silent notifications—notifications that do not present to the user but wake the app in the background.
A silent notification includes aps with content-available: 1:
{
"to": "device_token_123",
"aps": {
"content-available": 1,
"category": "background_update"
},
"data": {
"sync_token": "sync_789"
}
}
When the app is backgrounded, APNS wakes it and triggers application(_:didReceiveRemoteNotification:fetchCompletionHandler:).
FCM is Google’s push service for Android, iOS, and web. It uses a REST API at https://fcm.googleapis.com/v1/projects/{project_id}/messages:send.
The message format is similar to APNS but includes a message object:
{
"message": {
"token": "fcm_token_abc",
"notification": {
"title": "New Message",
"body": "Alice sent you a photo."
},
"data": {
"message_id": "msg_456",
"category": "chat"
},
"android": {
"notification": {
"channel_id": "chat_channel",
"icon": "ic_notification"
}
},
"apns": {
"headers": {
"apns-priority": "10"
},
"payload": {
"aps": {
"alert": {
"title": "New Message",
"body": "Alice sent you a photo."
},
"badge": 1
}
}
}
}
}
The android and apns keys allow platform-specific customization. The headers in apns configure APNS-specific behavior, such as priority and expiration.
FCM supports topic messaging, where a message is sent to all devices subscribed to a topic:
curl -X POST https://fcm.googleapis.com/v1/projects/{project_id}/topics/{topic_name} \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d @message.json
A device subscribes to a topic using the FCM SDK:
{
"to": "fcm_token_abc",
"topic": "news_updates"
}
FCM also supports message time-to-live (TTL) and priority. The server sets the TTL in seconds, and the message is retained in the queue until delivered or expired.
410 error, but the app never receives the notification.payload size and validate the aps object.aps key: iOS apps expect aps in the payload. If the aps object is missing or malformed, the notification may not appear in the notification center.click_action: The app expects OPEN_CHAT, but the server sends OPEN_CHAT_SCREEN. The app fails to navigate to the correct screen.kid, iss, or aud fails to authenticate, returning 401 Unauthorized.notification vs data structure: Developers often confuse the notification and data keys in FCM. The notification object is used by the OS; data is passed to the app. Misplacing a field can lead to missing titles, icons, or data.content-available flag is missing.remote-notification and handle didReceiveRemoteNotification in application(_:didFinishLaunchingWithOptions:).When a notification fails to deliver, the system retries using a queue and exponential backoff. A notification that fails to reach APNS may be requeued and sent to FCM as a fallback.
The server maintains a retry queue, storing each notification with a status and failed_attempts field. Upon failure, the server updates the status to failed, logs the error, and schedules a retry.
A notification may carry metadata, but the app must fetch the full content. The app uses the data fields to initiate a background sync.
For example, a chat notification includes message_id. When the user taps the notification, the app fetches the message from the server and updates the UI.
The app may use a SyncService that runs on a WorkManager (Android) or BackgroundTasks (iOS). The service checks for pending messages and updates the local database.
For high-volume apps, the system aggregates multiple notifications into a single message.
The server collects notifications for 30 seconds and sends a single batched message to APNS. The aps object includes a summary field to describe the batch:
{
"aps": {
"alert": {
"title": "3 New Messages",
"body": "You have 3 unread messages."
},
"summary-arg": "3",
"summary-arg-count": 3
},
"data": {
"message_ids": ["msg_1", "msg_2", "msg_3"],
"category": "chat"
}
}
The OS presents a summary, and the user can expand it to see individual messages.
Mobile push notification architecture is a distributed system of interdependent components: servers, push services, OSes, and apps. It is not a one-size-fits-all mechanism, but a layered, asynchronous flow where each component may fail silently. The architecture succeeds when a notification is not just delivered, but presented, interacted with, and used to enrich the user’s experience.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy