Push Notification System Architecture
Push notification architecture for routing, preferences, delivery, and retries.
A push notification system sits between product events inside your application and delivery networks owned by Apple, Google, browsers, email providers, or SMS carriers. Its job is not just to send messages. It has to decide whether a message should exist at all, which channel should carry it, when it should be sent, and how to prove what happened afterwards.
The process usually starts with a business event such as a payment succeeding, a comment arriving, or a fraud rule firing. The product service should emit a structured notification request instead of building message text itself. That request normally includes a user identifier, notification type, template data, priority, expiry time, and an idempotency key. Idempotency matters because upstream services retry. Without it, a single event can easily become three identical pushes.
A notification gateway receives those requests and performs admission checks. It validates the schema, rejects obviously invalid payloads, applies rate limits, and records the event for auditability. From there, a decision layer enriches the request with user preferences, locale, quiet hours, legal consent, and channel policies. This is where the system answers questions like: should this be a mobile push, an email fallback, an in app badge, or nothing at all?
Template rendering usually happens before delivery but after channel selection. That allows one logical event to produce different bodies for iOS, Android, email, and SMS. The rendered message is then placed on queues so the system can absorb bursts. Queueing is essential because providers such as APNs, FCM, and SMS gateways all have different throughput limits and error behaviour. A queue also lets operators prioritise urgent transactional traffic over marketing traffic during spikes.
For mobile push, the platform services are mandatory intermediaries. Your app registers with APNs on iOS or FCM on Android and receives a device token. Your backend stores that token against the user and later sends pushes to the platform provider, not directly to the phone. The provider then attempts delivery when the device is reachable and allowed to receive the message. Tokens expire, users reinstall apps, and permissions change, so token lifecycle management is part of the core design, not a side feature.
Delivery is only partially observable. A provider acknowledgement usually means the platform accepted the message, not that a human saw it. Devices may be offline, operating systems may coalesce low priority notifications, and users may disable alerts entirely. This is why mature systems track multiple states such as accepted, provider rejected, delivered where known, opened, and permanently failed. They also attach time to live values and collapse identifiers so stale notifications can be dropped or merged instead of arriving in a useless flood.
Failure handling is where many systems become real. Temporary provider errors should trigger bounded retries with backoff. Permanent failures, such as invalid device tokens or hard bounced emails, should retire the target so future sends do not waste capacity. Large systems also need deduplication, scheduled delivery, cancellation, campaign fanout controls, and per tenant isolation so one noisy customer does not starve everyone else.
The main tradeoff is between speed and policy richness. A simple system can deliver quickly, but once you add preferences, experimentation, legal rules, localisation, analytics, and fallback channels, the pipeline becomes a policy engine. The best designs keep the event contract small, the routing logic explicit, and the delivery workers disposable, because notification volume is bursty and the last hop is never fully under your control.