01

Requirements & scope

Problem Statement & Requirements

Functional Requirements

  • Support multiple notification channels: push (mobile), SMS, email, in-app (web/mobile badge + feed)
  • Producers (any internal service) can trigger notifications via an API
  • Template-based content with variable substitution (e.g., "{{user}} liked your photo")
  • User notification preferences — per-channel opt-in/out, quiet hours, frequency caps
  • Notification feed — paginated list of past notifications (in-app inbox)
  • Delivery tracking — sent, delivered, opened/clicked, failed
  • Priority levels — critical (2FA codes, payment alerts) vs. marketing (promos)
  • Rate limiting — prevent notification fatigue (max N notifications per user per hour)
  • Scheduling — send at a future time, timezone-aware
  • Batching / digest — group related notifications ("3 people liked your photo" instead of 3 separate)

Non-Functional Requirements

  • High throughput — 1B+ notifications/day
  • Low latency for critical — 2FA codes delivered within 5 seconds
  • At-least-once delivery — no notification silently dropped
  • Exactly-once display — dedup on the client side
  • Scalability — handle viral events (celebrity post → millions of notifications in seconds)
  • Observability — per-channel delivery rate, failure rate, open rate

Out of Scope

  • Content generation (the calling service provides the content)
  • User-facing notification creation (this is an infrastructure service)
  • Rich media rendering (handled by client apps)
02

Scale estimations

Scale Estimations

Traffic

Metric Value
Total notifications / day 1B
Notifications / second (avg) ~11,600
Peak notifications / second ~100K (viral event, breaking news)
Push notifications / day 500M (50%)
Email notifications / day 300M (30%)
SMS notifications / day 50M (5%)
In-app notifications / day 150M (15%)

Fanout Scenarios

Scenario Recipients Latency Target
1:1 (payment receipt) 1 < 5s
Small group (team mention) 10-50 < 10s
Large group (channel update) 1K-10K < 30s
Broadcast (app-wide alert) 10M-100M < 5 min
Viral fanout (celebrity post) 50M+ < 10 min

Storage

Metric Value
Notification record size ~500 bytes
Notification feed storage / day 1B × 500B = ~500 GB/day
Retention (90 days) ~45 TB
User preferences 500M users × 200 bytes = ~100 GB

Third-Party Costs

Channel Provider Rate Limit
iOS Push APNs ~Unlimited (but throttled per device token)
Android Push FCM ~Unlimited (batch up to 500 tokens/request)
Email SES / SendGrid 50K-100K emails/sec
SMS Twilio / SNS 1K-10K SMS/sec (varies by country)
03

Layered architecture

High-Level Architecture

High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore

Detailed Component Architecture

Detailed Component ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
04

API & contracts

API Design

Send Notification

EXAMPLE
POST /api/v1/notifications
Content-Type: application/json
X-Idempotency-Key: "order-123-shipped"

Request:
{
  "recipients": {
    "user_ids": ["u123", "u456"],          // explicit user list
    // OR
    "segment": "premium_users",            // audience segment
    // OR
    "topic": "breaking_news"               // pub/sub topic
  },
  "template_id": "order_shipped",
  "template_data": {
    "order_id": "ORD-789",
    "item_name": "Wireless Headphones",
    "tracking_url": "https://track.example.com/xyz"
  },
  "channels": ["push", "email"],           // override user prefs (optional)
  "priority": "high",                      // critical | high | normal | low
  "schedule_at": "2026-04-04T09:00:00Z",   // future send (optional)
  "ttl": 3600,                              // expire if not delivered in 1h
  "collapse_key": "order_update_789",       // replace previous with same key
  "metadata": {
    "source": "order-service",
    "campaign_id": "spring_sale_2026"
  }
}

Response (202 Accepted):
{
  "notification_id": "n_abc123",
  "status": "queued",
  "estimated_recipients": 2,
  "channels": ["push", "email"]
}

Query Notification Feed (In-App)

EXAMPLE
GET /api/v1/users/{user_id}/notifications?cursor=abc&limit=20

Response:
{
  "notifications": [
    {
      "id": "n_abc123",
      "type": "order_shipped",
      "title": "Your order has shipped!",
      "body": "Wireless Headphones is on the way.",
      "image_url": "https://...",
      "action_url": "https://track.example.com/xyz",
      "read": false,
      "created_at": "2026-04-03T14:30:00Z"
    },
    ...
  ],
  "next_cursor": "def",
  "unread_count": 7
}

User Preferences

EXAMPLE
PUT /api/v1/users/{user_id}/notification-preferences

{
  "channels": {
    "push": { "enabled": true },
    "email": { "enabled": true, "digest": "daily" },
    "sms": { "enabled": false }
  },
  "quiet_hours": {
    "enabled": true,
    "start": "22:00",
    "end": "08:00",
    "timezone": "America/New_York"
  },
  "categories": {
    "marketing": { "enabled": false },
    "social": { "enabled": true, "channels": ["push"] },
    "transactional": { "enabled": true, "channels": ["push", "email", "sms"] }
  }
}
05

Data model

Data Model

Notification Log (Cassandra)

Notification Log (Cassandra)Excalidraw diagram · editable shapes · reveal step by stepExplore

Delivery Status (ClickHouse — Analytics)

Delivery Status (ClickHouse — Analytics)Excalidraw diagram · editable shapes · reveal step by stepExplore
06

Core design decisions

Fanout Strategies

The Fanout Problem

The Fanout ProblemExcalidraw diagram · editable shapes · reveal step by stepExplore

Strategy 1: Fanout-on-Write (Push Model)

Strategy 1: Fanout-on-Write (Push Model)Excalidraw diagram · editable shapes · reveal step by stepExplore

Strategy 2: Fanout-on-Read (Pull Model)

Strategy 2: Fanout-on-Read (Pull Model)Excalidraw diagram · editable shapes · reveal step by stepExplore

Strategy 3: Hybrid (Recommended)

Strategy 3: Hybrid (Recommended)Excalidraw diagram · editable shapes · reveal step by stepExplore
07

Request flows

Notification Processing Pipeline

Full Flow Diagram

Full Flow DiagramExcalidraw diagram · editable shapes · reveal step by stepExplore

Priority Routing

Priority RoutingExcalidraw diagram · editable shapes · reveal step by stepExplore
08

Performance & caching

Notification Deduplication & Idempotency

Notification Deduplication & IdempotencyExcalidraw diagram · editable shapes · reveal step by stepExplore
09

Advanced design

Push Notification Deep Dive

Push Architecture (iOS + Android + Web)

Push Architecture (iOS + Android + Web)Excalidraw diagram · editable shapes · reveal step by stepExplore

APNs (Apple Push Notification Service) Integration

APNs (Apple Push Notification Service) IntegrationExcalidraw diagram · editable shapes · reveal step by stepExplore

FCM (Firebase Cloud Messaging) Integration

FCM (Firebase Cloud Messaging) IntegrationExcalidraw diagram · editable shapes · reveal step by stepExplore

Notification Batching & Digests

Notification Batching & DigestsExcalidraw diagram · editable shapes · reveal step by stepExplore

Rate Limiting & Quiet Hours

Rate Limiting & Quiet HoursExcalidraw diagram · editable shapes · reveal step by stepExplore

Delivery Tracking & Analytics

Delivery Tracking & AnalyticsExcalidraw diagram · editable shapes · reveal step by stepExplore

Template Engine

Template EngineExcalidraw diagram · editable shapes · reveal step by stepExplore

Scheduled & Timezone-Aware Notifications

Scheduled & Timezone-Aware NotificationsExcalidraw diagram · editable shapes · reveal step by stepExplore
10

Edge cases

Workshop note · added for the website’s common reading format

Walk through three failure moments in Notification System: a request times out before its result is known; a dependency becomes slow rather than unavailable; and a process restarts after committing state but before acknowledging it.

For each case, name the authoritative record, define a safe retry, cap resource usage, and describe what the caller sees. Retry + DLQ should help detect and contain the problem: Retry transient failures with backoff and jitter; quarantine persistent failures for controlled replay.

11

Tradeoffs

Tradeoffs & Design Decisions Summary

Decision Option A Option B Chosen Why
Fanout Fanout-on-write Fanout-on-read Hybrid Write for small audiences + push; read/background for VIP/broadcast
Queue Redis Pub/Sub Kafka Kafka Durability, replay, consumer groups, ordering, DLQ support
Priority Single queue + priority field Separate topics per priority Separate topics No head-of-line blocking; critical path fully isolated
Notification DB MySQL Cassandra Cassandra Write-heavy (1B/day), partition by user, TTL for auto-cleanup
Analytics DB PostgreSQL ClickHouse ClickHouse Columnar, fast aggregations over billions of delivery events
Template storage Database only Redis cache + DB Redis + MySQL Templates are read-heavy, small, and rarely change
Scheduling DB polling Redis sorted set Redis sorted set O(log N) operations, no polling waste, low latency
Retry Immediate retry loop Exponential backoff + DLQ Backoff + DLQ Prevents hammering failed providers; DLQ for investigation
Rate limiting Per-service Per-user per-channel Per-user per-channel Prevents notification fatigue regardless of source
Idempotency None Redis SETNX with TTL Redis SETNX Producers may retry; prevents duplicate notifications
Push delivery Individual sends Batch API (FCM 500/req) Batch where possible 10x fewer API calls to FCM; APNs uses HTTP/2 multiplexing
Quiet hours Drop notifications Defer to next allowed window Defer Don't lose notifications; deliver at appropriate time
12

Reliability & fault tolerance

Retry & Dead Letter Queue

Retry & Dead Letter QueueExcalidraw diagram · editable shapes · reveal step by stepExplore
13

Production architecture

Production Architecture

Production ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
14

Further exploration

Workshop note · added for the website’s common reading format

Rebuild Notification System from memory, then change one assumption: ten times more traffic, a new region, or a stricter consistency requirement. Which component must change first—and which does not?

Compare Policy engine with the same boundary in a related design. Write down one alternative you rejected, what it would simplify, and when you would choose it instead. Follow the source link at the end of this article to explore the original document.

15

Interview playbook

Interview Tips

  1. Start by clarifying channels — "Which channels do we need? Push, email, SMS, in-app? All of them?" This scopes the design.

  2. Draw the pipeline first — Producer → API → Kafka → Workers → Providers. This is the backbone.

  3. Separate critical from non-critical immediately — "2FA codes and marketing promos must not share the same queue." This shows operational maturity.

  4. Discuss fanout for broadcast — "What happens when a celebrity posts and 50M users need a notification?" Show chunked fanout, FCM topics, and background workers.

  5. User preferences are a first-class concern — Don't treat them as an afterthought. "Users must be able to opt out per channel, set quiet hours, and control frequency."

  6. Rate limiting prevents fatigue — "Without rate limiting, a burst of social activity could send 50 push notifications in a minute." Show per-user per-channel limits.

  7. Idempotency is critical — "The producer might retry. Kafka gives at-least-once. We need dedup at every layer." Show the idempotency key pattern.

  8. Don't forget device token management — "Tokens go stale when users uninstall the app. APNs returns 410 → we must clean up."

  9. Batching shows design depth — "3 people liked your photo" instead of 3 separate notifications. Explain time-window and count-threshold strategies.

  10. End with observability — "How do we know it's working? Delivery rate dashboards, p99 latency, DLQ depth alerts." This is what separates a design from a production system.