· software-engineers Editorial · Career · 6 min read
Swe Interview System Design Notification Service
Design a scalable notification service for SWE system design interviews: architecture, delivery guarantees, provider comparison, and 2026 talking points.
Why “Design a Notification System” Is a Top-Tier System Design Prompt
Designing a notification service (push, SMS, email, in-app) is one of the most frequently asked mid-to-senior system design prompts in 2026 because it touches nearly every hard distributed systems concept in one bounded problem: fan-out at scale, at-least-once vs exactly-once delivery, third-party rate limits, retry/backoff strategy, and user preference management. Unlike “design Twitter” or “design a URL shortener,” which candidates often over-rehearse from memorized templates, a notification system forces genuine reasoning because the failure modes are numerous and interesting — duplicate notifications, silent drops, and provider outages all have real, testable consequences.
Interviewers commonly frame it as: “Design a system that sends push, email, and SMS notifications to users when an order status changes, supporting 50 million users and 500 million notifications per day.” The scale numbers exist to force you into async, queue-based design rather than a naive synchronous call-and-send approach.
Core Architecture Components
A production-grade notification system decomposes into five layers, and walking through them in this order in an interview shows a clean mental model:
- Notification API / ingestion layer — receives requests from internal services (order service, chat service) via REST/gRPC or, at higher scale, by consuming events directly off a message bus (Kafka/Kinesis) to decouple producers from the notification system entirely.
- Preference and routing service — checks user notification preferences (channel opt-ins, do-not-disturb windows, timezone), deduplicates against recently-sent notifications, and determines which channels (push/email/SMS/in-app) apply.
- Templating and rendering layer — merges notification data into channel-specific templates (push payloads have strict size limits, ~4KB on APNs; email needs full HTML rendering).
- Channel-specific dispatch workers — separate worker pools per channel (push worker, email worker, SMS worker), each with its own queue, so a slowdown in one provider (e.g., SMS carrier outage) doesn’t back up push or email delivery.
- Third-party provider integration — the actual outbound call to APNs/FCM (push), SES/SendGrid (email), Twilio (SMS), with per-provider rate limiting, circuit breakers, and retry logic.
The single most important design decision to state explicitly: the ingestion layer should never call providers synchronously. Every request gets written to a durable queue first and acknowledged immediately; workers pull from the queue asynchronously. This decouples producer latency from provider latency and lets you absorb traffic spikes and provider slowdowns without cascading failure back to core business services.
Delivery Guarantees and Deduplication
At-least-once delivery is the practical standard — guaranteeing exactly-once across an unreliable third-party API (push providers, SMS carriers) is prohibitively expensive and mostly unnecessary. Instead, the pattern is: at-least-once delivery at the infrastructure level, paired with idempotency keys and client-side deduplication to make the effective user experience feel exactly-once. Concretely: every notification request carries a unique idempotency key (often userId + eventId); before dispatch, workers check a fast dedup store (Redis with TTL matching the retry window) to skip already-sent notifications, and clients on the receiving end (mobile apps) also dedupe by notification ID to guard against rare double-delivery from provider-side retries.
Provider and Channel Comparison
| Channel | Typical Provider | Delivery Guarantee | Latency | Cost per Message | Key Constraint |
|---|---|---|---|---|---|
| Push (iOS) | Apple APNs | Best-effort, no guaranteed delivery | Sub-second to minutes | Free | 4KB payload limit, requires valid device token |
| Push (Android) | Firebase Cloud Messaging (FCM) | Best-effort | Sub-second to minutes | Free | Doze mode / battery optimization can delay delivery |
| Amazon SES, SendGrid | High reliability with bounce/complaint feedback | Seconds | ~$0.0001-0.001/email | Deliverability depends on sender reputation, SPF/DKIM | |
| SMS | Twilio, AWS SNS | High reliability, carrier-dependent | Seconds | $0.0075-0.05+/message (varies by country) | Highest cost channel; strict opt-in/compliance (TCPA) |
| In-app / WebSocket | Custom (e.g., Pusher-style) | Only if client is connected | Milliseconds | Infra cost only | No delivery if client offline; needs fallback to push |
The cost column is why routing logic matters at scale: a naive “send every channel every time” design at 500M notifications/day would make SMS costs alone unsustainable, so real systems apply channel priority rules — try push first, fall back to email/SMS only for high-priority notifications (security alerts, OTPs) or if the push delivery receipt indicates failure.
Handling Scale: Fan-Out and Rate Limiting
For scenarios with massive fan-out (e.g., a broadcast notification to all 50M users, versus a single order-status update to one user), the design must branch: single-user notifications go through the standard per-user queue path, but bulk/broadcast notifications need a separate batch fan-out pipeline that reads user segments in chunks, respects per-provider rate limits (APNs and FCM both throttle per-app-ID send rates), and spreads load over minutes rather than attempting an instantaneous blast. A common follow-up question is “how do you avoid overwhelming a third-party provider’s rate limit during a broadcast?” — the answer is token-bucket rate limiting per provider integration, combined with backpressure signals from the provider (HTTP 429s) feeding back into the dispatch workers’ send rate.
What Distinguishes a Strong Answer From a Mediocre One
Mediocre answers stop at “queue plus workers plus third-party API.” Strong answers additionally address: retry strategy with exponential backoff and a dead-letter queue for permanently failed notifications (with alerting so a silent provider outage doesn’t go unnoticed); notification preference storage that supports per-channel, per-category granularity (users often want push for security alerts but not marketing); and observability — tracking delivery rate, open/click rate, and provider-level failure rate as first-class metrics, since notification systems that silently fail are one of the hardest classes of bug to detect without explicit monitoring.
Getting Interview-Ready for This Prompt
Because this question rewards structured, layer-by-layer reasoning over memorized diagrams, it’s worth rehearsing out loud with a timer rather than just reading references. The 0-to-1 SWE Interview Playbook includes a full worked example of this exact prompt with a scoring rubric matching what 2026 interview panels actually grade against, plus a set of follow-up questions interviewers commonly escalate to once the base design is on the whiteboard.
FAQ
Q: Should I default to Kafka for the queue layer, or is that overkill for this design? A: State the requirement first — durability and replay capability across multiple consumer groups (push worker, email worker, analytics) favor Kafka/Kinesis at genuine scale; for smaller scope or if the interviewer narrows scale, a simpler managed queue (SQS) is a perfectly defensible choice as long as you justify it against the stated requirements.
Q: How do I handle a user who should receive a notification but has all channels disabled? A: Always design an in-app notification center as the fallback of last resort — even users who opt out of push/email/SMS should have notifications persisted and visible when they next open the app, which also solves the “went offline, missed the push” problem.
Q: What’s the most common mistake candidates make on this problem? A: Designing a fully synchronous path (API call directly triggers provider call) and only introducing a queue when prompted by the interviewer — proactively identifying the need for async decoupling from the start is the clearest signal of system design maturity on this specific prompt.