Learn / DS & Algo / System Design / Notification System

System Design · Lesson 15 of 15

Notification System

Push, email, SMS and in-app notifications with fan-out, templates and preferences.

  • Advanced
  • 40 min read
  • 3 objectives

Before this lessonLesson 14: Typeahead and Nearby Search

What you will learn

  • Fan-out events to channels
  • Apply preferences, quiet hours and collapse
  • Avoid celebrity-scale push storms

Your Progress

0 of 15 lessons 0%

  • Lessons0 / 15
  • Completed0
  • Est. time left~ 8 hours

Create a free account to keep your progress on every device.

Design a notification system that can send push, email, SMS and in-app events — password resets, likes, delivery updates — without waking every follower of a celebrity, and without spamming someone at 3 a.m. for a like.

Step 1 — Clarify

  • Channels: iOS/Android push, email, SMS, in-app inbox?
  • Who produces events — many product services?
  • User preferences, quiet hours, marketing vs security?
  • Do we collapse bursts ('Alice and 79 others liked your photo')?

v1: all four channels, templates, preferences, quiet hours for marketing, collapse window, per-user rate limits. Security events (2FA, password reset) ignore marketing opt-out and quiet hours.

Step 2 — Pipeline

flowchart LR Svc[Product services] --> API[Notification API] API --> Q[(Event log)] Q --> Fan[Fan-out workers] Fan --> Pref[(Preferences cache)] Fan --> Push[Push workers] Fan --> Email[Email workers] Fan --> SMS[SMS workers] Fan --> Inbox[(In-app inbox)] Push --> APNs[APNs / FCM]

Producers enqueue a high-level event. Workers expand it, apply preferences, and talk to each provider. Channels scale independently.

Step 3 — API and data

POST /v1/events
  { "event_type": "comment_on_post",
    "actor_id": 9,
    "object_id": 441,
    "recipient_ids": [3, 4] }     -- or an audience: "followers_of"

templates(event_type, channel, locale, body)
devices(user_id, token, platform, last_seen)
preferences(user_id, event_type, channel, enabled)
inbox(user_id, notif_id, body, read, ts)   -- partition by user_id

Callers pass intent, not a rendered string. The notification service owns templates so copy can change without redeploying every product. Idempotency key: (event_type, object_id, recipient_id, time_bucket) so retries do not double-send.

Step 4 — Fan-out

  • Small N (ride update, 2FA): look up devices, send immediately.
  • Large N (celebrity post): do not create 40 million pushes. Write the post once. In-app feed uses the hybrid fan-out from the news-feed lesson. Push only recently active users who opted in, or skip push and badge the app. Everyone else sees it next open.
  • Kafka / log partitions by user_id so one person's notifications stay ordered.

Step 5 — Preferences, quiet hours, collapse

  • Preferences cached in Redis. Security events ignore marketing opt-out.
  • Quiet hours: delay marketing into a per-user local-time window; never delay 2FA.
  • Collapse: 80 likes in 45 seconds → one 'Alice and 79 others liked your photo'. Buffer by (recipient, event_type, object) before send.
  • Per-user rate limit: cap pushes per hour; overflow goes in-app only so a chatty group does not drain the battery.

Step 6 — Providers and reliability

APNs and FCM are queues of their own. Store device tokens, handle invalid-token callbacks, backoff on 5xx. Email/SMS providers have their own rate limits — a token bucket per provider. The in-app inbox is a partition per user, same shape as a chat inbox. Metrics: enqueue rate, send success, provider latency, unsubscribes. Dead-letter poison templates. Multi-region: inbox shards near the user; providers are global.

// Write your solution here
Course completeYou finished System DesignReview the full course or pick your next one.