Skip to main content

Design a Notification / Push System

Level 10: Level 10: Applied Case Studiesmedium35 minnotificationsfan-outqueue

An event flows through a queue to a preference filter, a renderer, priority per-channel lanes, and provider adapters with retries/failover, and an idempotency key checked against a dedup store is what prevents retries from double-sending.

A reusable delivery backbone

A notification system is a reusable delivery backbone: something happens (a like, a shipped order, a fraud alert) and the user must be reached across push, SMS, email, and in-app, respecting their preferences, without ever double-sending. The design is a pipeline, and the interview probes channel abstraction, idempotency, and preferences.

Channel abstraction with provider adapters

Do not scatter APNs, FCM, Twilio, and SES calls through your code. Define one internal notification, then route it to channel adapters. Each adapter (a Push adapter over APNs and FCM, an SMS adapter over Twilio, an Email adapter over SES) implements a common interface, handles that provider's quirks, retries transient failures with backoff, and can fail over to a backup provider (Twilio to a second SMS vendor). Adding a new channel is a new adapter, not a rewrite.

event -> ingestion API -> queue
   -> preference/eligibility filter (opt-out? quiet hours? channel enabled?)
   -> template/render service (localized, per-channel)
   -> per-channel queues (priority lanes) -> provider adapters (retry/failover)
   -> provider (APNs/FCM/Twilio/SES) -> delivery-status callback -> tracking + DLQ

Queues, priority lanes, and idempotency

Ingestion just validates and enqueues, returning fast. Workers consume from the queue (Kafka or SQS) and do the heavy work: fan-out, rendering, and dispatch. Use priority lanes: a 2FA code or fraud alert goes on a high-priority queue and must not sit behind a million marketing pushes. Per-user rate limiting prevents bombarding one user, and per-provider throttling respects APNs/Twilio rate limits.

Every request carries an idempotency key (event id + user + channel). Before sending, check whether that key was already delivered (a dedup store in Redis with a TTL, or a unique constraint). Delivery pipelines retry constantly (a worker crashes after sending but before recording success, a queue redelivers), and without idempotency a retry sends the same push twice. The dedup check is what makes at-least-once delivery machinery feel exactly-once to the user.

Templates, preferences, tracking

A template/rendering service turns an event plus data into channel-specific, localized content (a push has a title and short body, an email has HTML, an SMS has 160 characters). Keeping this separate means product can change copy without touching delivery.

A preference/eligibility filter runs before dispatch: has the user opted out of this category, is this channel enabled, is it their quiet hours (defer to morning), should low-priority notifications be batched into a digest? Digest/batching both respects the user and cuts provider cost.

Providers send delivery/open callbacks; record them. A dead-letter queue (DLQ) captures messages that fail after all retries for inspection and replay. Track send rate, delivery rate, and open rate per channel so you can see when APNs is degraded.

Interview nuance: the most common follow-up is "a worker retries and the user gets two pushes, why?" The answer names the idempotency key plus a dedup store checked before dispatch, and explains that the pipeline is at-least-once so dedup is mandatory, not optional.

Recap: an event flows through a queue to a preference filter, a renderer, priority per-channel lanes, and provider adapters with retries/failover, and an idempotency key checked against a dedup store is what prevents retries from double-sending.

Apply

Your turn

The task this lesson builds to.

Design a system that delivers a notification to a user across push (APNs/FCM), SMS, email, and in-app with per-user preferences.

Think about

  1. How do provider adapters with retries/failover abstract channels?
  2. How do you prevent double-sends with idempotency?
  3. How do preferences, quiet hours, and batching fit?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design the notification system for an incident-alerting product like PagerDuty, where an alert must reach an on-call engineer within seconds and escalate through push, then SMS, then a phone call if unacknowledged. Explain the escalation state machine, idempotency, and how you avoid both missed pages and alert storms.

Think about

  1. How does a durable, timer-driven escalation state machine work?
  2. Why does this system bias toward delivery over avoiding duplicates?
  3. How do alert grouping and dedup prevent an alert storm?