Push Notification System
Design a high-throughput notification system that delivers push notifications, emails, and SMS to millions of users across channels.
These designs are for learning purposes and may not be 100% accurate or production-ready. Always cross-reference with official docs and other resources. Report an issue if you spot something wrong.
Reference Documents
Notification System Design — YouTube
Video walkthroughs of push notification system design covering fanout, FCM, APNS, and Kafka.
VideoByteByteGo — System Design Newsletter
Alex Xu's newsletter covering notification system architecture and fan-out patterns.
BlogAmazon SNS Developer Guide
Official AWS SNS documentation covering pub/sub patterns and cross-platform push notification delivery.
DocsOverview
What is a Notification System?
A notification system routes and delivers messages to users across multiple channels — push (iOS/Android), email, and SMS. It must handle high fan-out (one event triggers notifications to millions), user preferences (opt-out, quiet hours), and reliable delivery.
Key operations:
- Send: Accept notification request, route to correct channel(s)
- Fan-out: One event → notifications to millions of users
- Deliver: Call third-party providers (FCM, APNs, SendGrid, Twilio)
- Track: Delivery status, read receipts
Requirements
Functional
- Send push, email, and SMS notifications
- Respect user preferences (opt-outs, quiet hours, frequency caps)
- Support immediate and scheduled notifications
- Track delivery status
Non-Functional
- Scale: 10M notifications/day = ~120/sec average, 10K/sec peak (flash sales)
- Latency: push < 1s, email < 30s, SMS < 10s
- At-least-once delivery
- 99.9% availability
Capacity
- 10M/day × avg 1KB payload = 10GB/day through the system
- Device token DB: 1B users × 2 devices × 100B = ~200GB
Design Intuition
The Fan-out Problem
A single event (e.g. a flash sale) can trigger notifications to 50M users in seconds. You can't do this synchronously in the API call — it would timeout.
Key insight: Decouple with Kafka. Accept the request fast, fan out asynchronously.
Channel Isolation
Each channel (push, email, SMS) has different:
- Throughput limits (FCM: fast, SMS: slow + expensive)
- Failure modes (APNs rejects invalid tokens)
- Retry semantics
Kafka topics per channel let workers scale independently.
User Preferences are Critical
Always check opt-outs and quiet hours before sending. Nothing damages trust more than spamming a user who opted out.
High-Level Design
Flow
- Caller sends POST /notify with (user_id[], template, channel, data)
- Notification Service:
- Fetches user preferences + device tokens from User Prefs DB
- Filters out opted-out users
- Publishes one message per channel per user to Kafka
- Channel workers consume from Kafka:
- Push Worker: calls FCM (Android) or APNs (iOS)
- Email Worker: calls SendGrid / SES
- SMS Worker: calls Twilio
- On failure: Kafka consumer retries with exponential backoff
Device Token Management
- Register device token on app install
- FCM/APNs return invalid token errors → clean up DB
- One user can have multiple devices
Deep Dive
Priority Queues
Not all notifications are equal. Critical alerts (OTP, fraud) should skip the queue:
- Kafka topic:
notifications.high-priority→ dedicated fast worker - Kafka topic:
notifications.low-priority→ batched worker
Rate Limiting
FCM has per-project send limits. SMS is expensive. Add a per-user frequency cap:
- Max 3 push/hour, 1 email/day, 1 SMS/week for marketing
- No cap for transactional (OTP, order confirmation)
Idempotency
Event bus retries can cause duplicate sends. Use a notification_id as idempotency key:
- Check Redis set: has notification_id been processed?
- If yes → skip
- If no → send → add to Redis set with TTL
Trade-offs
| Decision | Option A | Option B | Chosen |
|---|---|---|---|
| Delivery | Synchronous | Kafka async | Kafka — handles fan-out spikes |
| Channel workers | Shared | Per-channel | Per-channel — independent scaling |
| Delivery guarantee | At-most-once | At-least-once | At-least-once with dedup |
| Priority | Single queue | Priority queues | Priority — critical alerts unblocked |
Interview Tips
What Interviewers Look For
- Fan-out via Kafka — not synchronous, not a single thread
- Channel separation — push/email/SMS have different workers
- User preferences — opt-out and quiet hours checked before send
- Idempotency — dedup on notification_id prevents double sends
- Priority queues — OTP/fraud alerts skip marketing queue
- Device token cleanup — remove stale tokens on send failure
Common Mistakes
- Synchronous delivery in the API handler
- Single queue for all channels
- Not filtering opt-outs before fan-out
- Ignoring idempotency on retries