Push Notification Architecture Mobile App
We once worked on a fintech application where the client attempted to send a "Payment Received" notification by making a direct API call to Firebase from their primary application server. It worked fine during the beta with 500 users. However, during a flash sale event, the surge in transactions caused the application server's event loop to choke. The system was waiting on HTTP responses from the push provider while the actual payment processing logic lagged, leading to timeouts and duplicate transactions.
This is a classic mistake. Treating push notifications as a synchronous side-effect of a business action is a recipe for system failure. In our experience, a push notification is not just a message; it is a distributed systems problem involving asynchronous queuing, token lifecycle management, and third-party dependency handling.
To build a production-grade push notification architecture mobile app, you must decouple the trigger from the delivery. Whether you are building for a few thousand users or several million, the architecture must assume that the push provider (FCM or APNs) will occasionally latency-spike or reject requests, and your system must handle those failures without impacting the core user experience.
What You Will Learn
- Design a decoupled notification pipeline using message brokers.
- Implement a robust device token registry to manage multi-device users.
- Compare FCM vs APNs and determine when to use a wrapper.
- Scale notification delivery to handle millions of concurrent pushes.
- Handle token expiration and "ghost" devices to optimize costs.
- Implement priority-based queuing for critical vs. marketing alerts.
Decoupling the Trigger from the Delivery
The most critical architectural decision is the introduction of an asynchronous layer. We recommend using a message broker like RabbitMQ or Apache Kafka between your application logic and the push gateway. When a business event occurs (e.g., a new message is sent), the application server should simply publish a "NotificationEvent" to a queue and immediately return a success response to the user.
The Worker Pattern
We utilize a dedicated set of "Push Workers." These are lightweight consumers that pull messages from the queue and handle the actual HTTP handshake with the push service. This pattern provides three immediate advantages:
- Load Smoothing: If you send 1 million notifications at once, the workers process them at a steady rate that doesn't overwhelm your database or the push provider's rate limits.
- Retry Logic: If FCM returns a 503 Service Unavailable, the worker can requeue the message with an exponential backoff.
- Isolation: A failure in the notification system cannot crash the payment or authentication modules of your android app development backend.
Handling Payload Constraints
Push providers have strict payload limits (typically 4KB). We strongly advise against sending large amounts of data in the push payload. Instead, send a "data-only" message containing a reference ID. When the app receives this ID, it should trigger a background fetch to your API to get the latest content. This ensures that if a user changes their nickname or a post is deleted between the time the push was sent and delivered, the user sees the current state of the data.
Managing the Device Token Registry
A common point of failure in push notification architecture mobile app designs is the token store. A user might have an iPad, an Android phone, and a tablet. Each device has a unique token. If you store only one token per user ID, you lose the ability to target devices specifically or reach the user on all their screens.
The Token Schema
We recommend a dedicated device_tokens table rather than adding a column to the users table. The schema should include:
user_id: Foreign key to the user.device_id: A unique hardware identifier.token: The FCM/APNs token.platform: ios or android.last_seen: Timestamp to identify stale tokens.
Token Lifecycle and Pruning
Tokens expire. Users uninstall apps. If you continue sending pushes to invalid tokens, providers like FCM may throttle your account. We implement a "Pruning Service" that monitors the responses from the push gateway. If the gateway returns a NotRegistered or InvalidRegistration error, the worker must immediately mark that token as invalid in the database or delete it. This keeps the registry lean and maintains a high delivery reputation.
Scaling the Delivery Pipeline
When moving from 10,000 to 1 million users, the bottleneck shifts from the API to the database and the network I/O. To scale, we implement "Fan-out" architecture.
The Fan-out Strategy
For a broadcast notification (e.g., "System Maintenance at Midnight"), you should not iterate through 1 million tokens in a single loop. This will time out and likely crash your worker. Instead:
- The Dispatcher queries the database for all active tokens.
- It breaks these tokens into "batches" (e.g., 500 tokens per batch).
- Each batch is published as a separate message to the queue.
- Multiple Push Workers consume these batches in parallel.
Priority Queuing
Not all notifications are equal. A "Password Reset" code is time-critical; a "Weekly Summary" is not. We recommend using separate queues for different priorities:
- High Priority: Dedicated workers, no rate limiting, immediate processing.
- Medium Priority: Standard workers, basic retry logic.
- Low Priority: Processed during off-peak hours or with lower concurrency to save costs.
+-------------------+ +-------------------+ +-------------------+
| Application App | ----> | Message Broker | ----> | Push Workers |
| (Trigger Event) | | (RabbitMQ/Kafka) | | (Consumer Logic) |
+-------------------+ +-------------------+ +-------------------+
^ |
| |
v v
+-------------------+ +-------------------+
| Token Registry | | Push Gateways |
| (PostgreSQL/Redis)| | (FCM / APNs) |
+-------------------+ +-------------------+
|
v
+-------------------+
| Mobile Device |
| (iOS / Android) |
+-------------------+
Figure 1: Decoupled Push Notification Architecture showing the flow from event trigger to device delivery.
Implementing the Token Registry Logic
This TypeScript snippet demonstrates how we handle token registration. We ensure that we don't create duplicate entries for the same device, which prevents the user from receiving the same notification multiple times.
import { db } from './database';
async function registerDeviceToken(userId: string, deviceId: string, token: string, platform: 'ios' | 'android') {
// We use an 'upsert' pattern to avoid duplicate tokens for the same device
const query = `
INSERT INTO device_tokens (user_id, device_id, token, platform, last_seen)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (device_id)
DO UPDATE SET
token = EXCLUDED.token,
last_seen = NOW(),
user_id = EXCLUDED.user_id;
`;
try {
await db.query(query, [userId, deviceId, token, platform]);
console.log(`Token updated for device ${deviceId}`);
} catch (error) {
console.error('Failed to register token:', error);
throw new Error('TokenRegistrationError');
}
}
The Push Worker Implementation
The following Node.js code shows how a worker processes a batch and handles the "Not Registered" response from FCM. This is the core of our token pruning strategy.
const admin = require('firebase-admin');
const { markTokenInvalid } = require('./tokenService');
async function processPushBatch(batch) {
const messages = batch.map(item => ({
token: item.token,
notification: {
title: item.title,
body: item.body
},
data: { pushId: item.id }
}));
try {
const response = await admin.messaging().sendEachForMulticast(messages);
// Critical: Handle failed tokens to prevent future waste
if (response.failureCount > 0) {
response.responses.forEach((resp, idx) => {
if (!resp.success) {
const errorCode = resp.error.code;
if (errorCode === 'messaging/registration-token-not-registered' ||
errorCode === 'messaging/invalid-registration-token') {
// Async call to prune the database
markTokenInvalid(batch[idx].token);
}
}
});
}
} catch (error) {
console.error('Batch delivery failed:', error);
// Re-queue logic would go here
}
}
FCM vs APNs: The Integration Trade-off
When designing a push notification architecture mobile app, you face a choice: integrate with both Apple Push Notification service (APNs) and Firebase Cloud Messaging (FCM) separately, or use FCM as a wrapper for both. We have a strong opinion on this based on years of react native app development.
| Feature | FCM (as Wrapper) | Direct APNs + FCM | Trade-off |
|---|---|---|---|
| Implementation Speed | Fast (Single API) | Slow (Two APIs) | FCM reduces initial dev time. |
| Control | Moderate | High | Direct APNs allows better use of iOS-specific features. |
| Reliability | Dependent on Google | Direct to Vendor | FCM adds one more hop in the network. |
| Complexity | Low | High | Direct integration requires managing .p8 certificates and FCM keys. |
| Best for | MVPs, Cross-platform apps | Enterprise, iOS-first apps | Choose based on target audience. |
| Pinakinvox Recommendation | Recommended | Only for specific needs | FCM's unified API outweighs the marginal latency for 95% of projects. |
Choosing the Right Approach
The "right" architecture depends on your scale and your budget. We don't believe in one-size-fits-all solutions.
If you are building an MVP with < 50k users: Use a serverless approach. Trigger FCM calls directly from AWS Lambda or Google Cloud Functions. Avoid the complexity of a message broker until you hit the performance ceiling. Learn more about our mvp development company services to get started quickly.
If you have a high-frequency event stream (e.g., Live Sports/Trading): Implement a Kafka-based pipeline. You need the ability to handle millions of events per second and the capacity to "replay" messages if a downstream worker fails. This requires a dedicated DevOps setup.
If you are targeting a high-end iOS user base with complex notification categories: Go direct with APNs. This allows you to utilize advanced iOS features like Notification Service Extensions and custom sound files more reliably than through a wrapper. For this, we recommend a specialized ios app development company approach.
Real-World Implementation: E-commerce Flash Sales
In a recent project for a regional e-commerce player, we had to handle a "Deal of the Hour" notification sent to 2.5 million users simultaneously. A synchronous approach would have locked the database for minutes. We implemented the fan-out architecture described above, using Redis to cache the active token lists and RabbitMQ to distribute the load across 20 worker nodes.
The result was a peak delivery rate of approximately 15,000 notifications per second. We observed a 3-5% token failure rate, which our pruning service cleaned up in real-time, ensuring that the subsequent "Deal" notification was sent to a cleaner, more accurate list of devices.
Frequently Asked Questions
How do we handle notifications when the app is killed?
What is the typical cost of implementing this architecture in India?
Can we implement this for clients based in Gurgaon or Delhi NCR?
How do we prevent users from being spammed?
user_preferences table before publishing a message to the queue. If the user has disabled "Marketing Alerts," the dispatcher should simply drop the event before it ever reaches the push worker.
What happens if the push provider is down?
Is there a limit to how many notifications I can send?
Final Recommendation
For the vast majority of mobile applications, we recommend a Decoupled FCM-based Architecture. The combination of a PostgreSQL token registry, a RabbitMQ queue, and FCM as the delivery gateway provides the best balance between development speed and operational scalability.
Do not fall into the trap of making synchronous API calls to push providers. It will work during your first month of launch, but it will fail the moment you achieve actual growth. Invest in the asynchronous pipeline early; the cost of refactoring a synchronous notification system under heavy load is significantly higher than building it correctly from the start.
If you are struggling with notification latency or token management in your current app, contact our engineering team for a technical audit of your mobile architecture.
Need a technical partner?
We design and build production systems. If you are working through the architecture decisions covered here, our engineering team can help you scope, validate, and execute.
Production-verified.
Every architectural pattern published here has been deployed in real client systems — not demo environments.
Written by engineers.
Our architecture articles are written by the engineers who built the systems — not by marketing teams.