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

MetricValue
Total notifications / day1B
Notifications / second (avg)~11,600
Peak notifications / second~100K (viral event, breaking news)
Push notifications / day500M (50%)
Email notifications / day300M (30%)
SMS notifications / day50M (5%)
In-app notifications / day150M (15%)

Fanout Scenarios

ScenarioRecipientsLatency 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

MetricValue
Notification record size~500 bytes
Notification feed storage / day1B × 500B = ~500 GB/day
Retention (90 days)~45 TB
User preferences500M users × 200 bytes = ~100 GB

Third-Party Costs

ChannelProviderRate Limit
iOS PushAPNs~Unlimited (but throttled per device token)
Android PushFCM~Unlimited (batch up to 500 tokens/request)
EmailSES / SendGrid50K-100K emails/sec
SMSTwilio / SNS1K-10K SMS/sec (varies by country)
03

Layered architecture

Phase 1 of 4 · Client & edge

Start with the entry points

First, define who uses the system and where requests enter. This is the boundary of the design, not yet a complete request path.

Producers
Accept notification intent independently of any particular email, SMS, or push provider.
Notification API
Require a stable deduplication key and validate the recipient and template variables.
Architecture after phase 1. Includes Producers, Notification API. Producers connects to Notification API.
Phase 1 — Start with Producers, Notification API.SVGSVGEditable diagramEditable diagram

This first layer defines the system boundary. The next chapters add application logic and data dependencies. After examining the request paths and tradeoffs, we will add operational safeguards and bring the complete architecture together.

04

API & contracts

Phase 2 of 4 · Application & compute

Add the application logic

Keep the entry points and add the services that do the work. Give each service a clear responsibility before defining its contracts. Storage and background dependencies come next.

Policy engine
Apply opt-outs, timezone-aware quiet hours, and channel-specific rate limits before delivery.
Channel workers
Render channel-specific content and isolate provider integrations behind adapters.
Architecture after phase 2. Includes Producers, Notification API, Policy engine, Channel workers. Producers connects to Notification API. Notification API connects to Policy engine.
Phase 2 — Added Policy engine, Channel workers. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

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

Phase 3 of 4 · Data & async work

Connect data and background work

The services now need their dependencies: durable state, fast lookups, or work that continues outside a request. Separate these responsibilities rather than treating every component as a database.

Priority queues
Separate urgent transactional traffic from bulk traffic so one cannot starve the other.
Delivery store
Persist attempts and provider receipts; accepted by a provider does not mean delivered to a person.
Providers
Handle provider throttling, timeout ambiguity, and channel-specific delivery semantics.
Architecture after phase 3. Includes Producers, Notification API, Policy engine, Channel workers, Priority queues, Delivery store, Providers. Producers connects to Notification API. Notification API connects to Policy engine. Policy engine connects to Priority queues. Priority queues connects to Channel workers. Channel workers connects to Delivery store. Channel workers connects to Providers.
Phase 3 — Added Priority queues, Delivery store, Providers. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

Data Model

Notification Log (Cassandra)

Notification Log (Cassandra)
Notification Log (Cassandra)SVGSVGEditable diagramEditable diagram

Delivery Status (ClickHouse — Analytics)

Delivery Status (ClickHouse — Analytics)
Delivery Status (ClickHouse — Analytics)SVGSVGEditable diagramEditable diagram
06

Core design decisions

Fanout Strategies

The Fanout Problem

The Fanout Problem
The Fanout ProblemSVGSVGEditable diagramEditable diagram

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

Strategy 1: Fanout-on-Write (Push Model)
Strategy 1: Fanout-on-Write (Push Model)SVGSVGEditable diagramEditable diagram

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

Strategy 2: Fanout-on-Read (Pull Model)
Strategy 2: Fanout-on-Read (Pull Model)SVGSVGEditable diagramEditable diagram

Strategy 3: Hybrid (Recommended)

Strategy 3: Hybrid (Recommended)
Strategy 3: Hybrid (Recommended)SVGSVGEditable diagramEditable diagram
07

Request flows

Notification Processing Pipeline

Full Flow Diagram

Full Flow Diagram
Full Flow DiagramWide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram

Priority Routing

Priority Routing
Priority RoutingSVGSVGEditable diagramEditable diagram
08

Performance & caching

Notification Deduplication & Idempotency

Notification Deduplication & Idempotency
Notification Deduplication & IdempotencySVGSVGEditable diagramEditable diagram
09

Advanced design

Push Notification Deep Dive

Push Architecture (iOS + Android + Web)

Push Architecture (iOS + Android + Web)
Push Architecture (iOS + Android + Web)SVGSVGEditable diagramEditable diagram

APNs (Apple Push Notification Service) Integration

APNs (Apple Push Notification Service) Integration
APNs (Apple Push Notification Service) IntegrationSVGSVGEditable diagramEditable diagram

FCM (Firebase Cloud Messaging) Integration

FCM (Firebase Cloud Messaging) Integration
FCM (Firebase Cloud Messaging) IntegrationSVGSVGEditable diagramEditable diagram

Notification Batching & Digests

Notification Batching & Digests
Notification Batching & DigestsSVGSVGEditable diagramEditable diagram

Rate Limiting & Quiet Hours

Rate Limiting & Quiet Hours
Rate Limiting & Quiet HoursSVGSVGEditable diagramEditable diagram

Delivery Tracking & Analytics

Delivery Tracking & Analytics
Delivery Tracking & AnalyticsSVGSVGEditable diagramEditable diagram

Template Engine

Template Engine
Template EngineSVGSVGEditable diagramEditable diagram

Scheduled & Timezone-Aware Notifications

Scheduled & Timezone-Aware Notifications
Scheduled & Timezone-Aware NotificationsSVGSVGEditable diagramEditable diagram
10

Edge cases

A user disables promotional email after a campaign has already entered the queue. Check the relevant preference again before delivery rather than relying only on the snapshot taken when the job was created. Provider timeouts need similar care: retain the provider’s operation ID and distinguish an unknown outcome from a confirmed rejection.

11

Tradeoffs

Tradeoffs & Design Decisions Summary

DecisionOption AOption BChosenWhy
FanoutFanout-on-writeFanout-on-readHybridWrite for small audiences + push; read/background for VIP/broadcast
QueueRedis Pub/SubKafkaKafkaDurability, replay, consumer groups, ordering, DLQ support
PrioritySingle queue + priority fieldSeparate topics per prioritySeparate topicsNo head-of-line blocking; critical path fully isolated
Notification DBMySQLCassandraCassandraWrite-heavy (1B/day), partition by user, TTL for auto-cleanup
Analytics DBPostgreSQLClickHouseClickHouseColumnar, fast aggregations over billions of delivery events
Template storageDatabase onlyRedis cache + DBRedis + MySQLTemplates are read-heavy, small, and rarely change
SchedulingDB pollingRedis sorted setRedis sorted setO(log N) operations, no polling waste, low latency
RetryImmediate retry loopExponential backoff + DLQBackoff + DLQPrevents hammering failed providers; DLQ for investigation
Rate limitingPer-servicePer-user per-channelPer-user per-channelPrevents notification fatigue regardless of source
IdempotencyNoneRedis SETNX with TTLRedis SETNXProducers may retry; prevents duplicate notifications
Push deliveryIndividual sendsBatch API (FCM 500/req)Batch where possible10x fewer API calls to FCM; APNs uses HTTP/2 multiplexing
Quiet hoursDrop notificationsDefer to next allowed windowDeferDon't lose notifications; deliver at appropriate time
12

Reliability & fault tolerance

Phase 4 of 4 · Operations & resilience

Add operational safeguards

With the request paths and tradeoffs established, add the controls that help detect trouble and keep the system operating. The final diagram retains every layer introduced so far.

Retry + DLQ
Retry transient failures with backoff and jitter; quarantine persistent failures for controlled replay.
Architecture after phase 4. Includes Producers, Notification API, Policy engine, Channel workers, Priority queues, Delivery store, Providers, Retry + DLQ. Producers connects to Notification API. Notification API connects to Policy engine. Policy engine connects to Priority queues. Priority queues connects to Channel workers. Channel workers connects to Delivery store. Channel workers connects to Providers. Channel workers connects to Retry + DLQ.
Phase 4 — Added Retry + DLQ. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

Retry & Dead Letter Queue

Retry & Dead Letter Queue
Retry & Dead Letter QueueSVGSVGEditable diagramEditable diagram
13

Production architecture

High-Level Architecture

High-Level Architecture
High-Level ArchitectureSVGSVGEditable diagramEditable diagram

Detailed Component Architecture

Detailed Component Architecture
Detailed Component ArchitectureWide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram

Production Architecture

Production Architecture
Production ArchitectureWide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram
14

Further exploration

Send an urgent security alert while a large newsletter campaign is draining. Trace where the urgent work might still wait: queue partitions, worker capacity, and provider quotas. Separate priority queues are useful only if the downstream resources can also reserve capacity for urgent traffic.

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.