SYSTEM DESIGN · QUESTION 106
How would you design a notification service?
On this page
I would clarify channels, delivery urgency, preferences, volume, and retry expectations. A typical design accepts a notification request, validates user preferences, stores it durably, and queues channel-specific delivery workers. Use idempotency keys, provider adapters, retry with backoff, dead-letter handling, and delivery status. Separate acceptance from successful delivery so the API does not overpromise.
LET'S MAKE IT SIMPLE
First, the idea in plain English
Applications need to send email, text, or push messages, but providers can be slow or unavailable. A notification service accepts intent, stores it, and delivers asynchronously. It also needs to respect preferences, avoid duplicates, and explain whether a message was queued, sent, or actually received.
A USEFUL COMPARISON
Picture it this way
A post office accepts a parcel and gives a receipt before the parcel reaches its destination. The receipt means accepted for delivery, not delivered. The service needs separate states for those milestones and a process for retries or returned parcels.
FOLLOW THE JOURNEY
Persist notification intent before channel delivery
Trace request acceptance, preference checks, queues, provider workers, and status.
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; The service should make delivery state observable.
An API validates preferences and persists a notification, queues it for channel workers, sends through provider adapters, and records delivery outcomes or retry failures.
How to read this visual
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; The service should make delivery state observable.
UNDER THE HOOD
How it works, step by step
Accept and validate
Check identity, payload, preferences, and idempotency key.
Why it matters: Invalid or duplicate intents should not enter the delivery path.
Persist and queue
Store the notification before acknowledging acceptance.
Why it matters: Durable intent survives worker or provider outages.
Deliver by channel
Workers apply templates, rate limits, and provider calls.
Why it matters: Channel-specific failures need independent handling.
Track and retry
Record status, retry transient failures, and isolate permanent failures.
Why it matters: The service should make delivery state observable.
OUTSIDE THE TEXTBOOK
A practical example
A user requests a password reset email. The API validates the account flow, stores a notification job, and returns that it was accepted. A worker sends it through an email provider using a deduplication key, retries temporary errors, and records final provider status without revealing sensitive account details.
GO A LITTLE DEEPER
Technical details that matter
- Use outbox patterns when a business transaction and notification intent must commit together.
- Retries need idempotency or provider deduplication to avoid duplicate messages.
- Preferences, quiet hours, unsubscribe, and regional delivery rules belong in policy checks.
MAKE A GOOD CHOICE
When is this useful?
- Transactional emails, push alerts, and SMS workflows.
- Decoupling user-facing requests from slow external providers.
- Centralizing preferences, retries, templates, and delivery audit.
A small product can use a managed notification provider directly, while preserving durable intent and clear failure semantics.
WATCH OUT FOR
Common misunderstandings
- Treating API acceptance as successful delivery.
- Retrying indefinitely without a dead-letter or expiry policy.
- Ignoring user preferences and duplicate requests.
REAL-WORLD TRADE-OFFS
Limitations and failure cases
- Provider outages and rate limits affect delivery timing.
- Exactly-once delivery cannot generally be guaranteed across external channels.
- Channel policies and privacy rules add operational complexity.
WHAT TO REMEMBER
Key takeaways
- 1Persist intent before asynchronous delivery.
- 2Separate accepted, sent, and delivered states.
- 3Make retries bounded, observable, and safe against duplicates.
PRACTICE THE FOLLOW-UP
What might an interviewer ask next?
How do you prevent duplicate notifications?
Use an idempotency key for the business event and store delivery state so retries do not create a second logical message.
How should a worker handle provider failure?
Retry transient failures with backoff and limits, then route permanent or exhausted failures to a dead-letter path for review.
SAY IT OUT LOUD
How to explain this in an interview
“I would clarify channels, urgency, preferences, and delivery guarantees. The API validates and persists a notification, then queues channel-specific work. Workers use provider adapters, rate limits, idempotent retries, and dead-letter handling, while status distinguishes acceptance from delivery. This avoids holding user requests open for external providers and makes failures observable.”
Connect this question to the roadmap topic it supports.