Back to System Design

SYSTEM DESIGN · QUESTION 106

How would you design a notification service?

On this page
Advanced 4 min read
Interview-ready answerThe concise version to remember

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.

Persist notification intent before channel deliveryTrace request acceptance, preference checks, queues, provider workers, and status.Notification architecture
01Accept and validateCheck identity, payload, preferences, and idempotency key.
02Persist and queueStore the notification before acknowledging acceptance.
03Deliver by channelWorkers apply templates, rate limits, and provider calls.
04Track and retryRecord status, retry transient failures, and isolate permanent failures.

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

01

Accept and validate

Check identity, payload, preferences, and idempotency key.

Why it matters: Invalid or duplicate intents should not enter the delivery path.

02

Persist and queue

Store the notification before acknowledging acceptance.

Why it matters: Durable intent survives worker or provider outages.

03

Deliver by channel

Workers apply templates, rate limits, and provider calls.

Why it matters: Channel-specific failures need independent handling.

04

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.
Consider the alternative

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

  1. 1Persist intent before asynchronous delivery.
  2. 2Separate accepted, sent, and delivered states.
  3. 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.”

Continue your learning path

Connect this question to the roadmap topic it supports.

Open roadmap topic