Back to System design

Design a Notification System

medium
Scale: 10M+ notifications/min peak; sub-minute latency on transactional Storage: Audit log grows forever; preferences + suppression bounded Meta, Twitter, Slack
Case StudyAsyncPub/Sub

A notification platform delivers transactional and marketing messages to users across push, email, SMS, in-app, and chat. The architecture problem is rarely any single channel — it's the orchestration: preferences, dedup, rate limits, vendor failover, and lane separation.

Scale10M+ notifications/min peak; sub-minute latency on transactional
StorageAudit log grows forever; preferences + suppression bounded

Key Concepts

1
1. The funnel. Internal producers emit NotificationRequest {recipient, template_id, params, channels, priority, dedup_key}. Central service: check preferences (does this user accept push? marketing?), apply suppression list (bounces, opt-outs), rate-limit per user per template, dedup via dedup_key, render template (localized), emit per-channel jobs to Kafka.
1. The funnel.
2
2. Transactional vs marketing — separate lanes. 2FA codes, security alerts: critical, latency-sensitive. Marketing: bulk, latency-tolerant. Mix them on one queue and a marketing burst delays your 2FA codes. Separate Kafka topics or per-channel queues. Optimize transactional path for low latency (small batches, eager consumers). Optimize bulk for throughput (large batches, vendor rate limits respected).
2. Transactional vs marketing — separate lanes.
3
3. Vendor strategy. Multiple providers per channel for redundancy (primary SendGrid, backup Mailgun). Vendor abstraction routes by health. On 5xx / timeout, fail over. Vendors return delivery status async via webhooks (hours delayed). Periodic reconciliation: compare what we sent vs what vendor reports delivered.
3. Vendor strategy.
4
4. Compliance and deliverability. Email needs SPF + DKIM + DMARC alignment for good inbox placement. IP warming over weeks before sending bulk. Marketing IP pool separate from transactional. SMS: TCPA in US (prior written consent for marketing), A2P 10DLC compliance, carrier registration. GDPR: unsubscribe links, preference center, audit log.
4. Compliance and deliverability.
5
5. Production essentials. Dedup window outlasts retries. Push token cleanup (invalid tokens expire — actively prune). Bounce handler: hard bounces auto-suppress; soft bounces retry with backoff. References: Twilio, SendGrid, Customer.io, Braze, Meta's notification platform, Slack's notification stack.
5. Production essentials.

High-level design

Producers → NotificationRequest API.
Notification service → preference check, rate limit, dedup, template render.
Kafka topics per channel + priority (push-tx, push-bulk, email-tx, email-bulk, ...).
Per-channel worker pools → vendor API call with retries.
Vendor webhooks → status update → audit store.
Bounce handler → update suppression list.
Analytics → opens, clicks, conversion.

Components

  • Notification API gateway.
  • Preference service (per-user channel preferences).
  • Suppression list (bounces, opt-outs).
  • Rate limiter (per user, per template, per recipient).
  • Template renderer (with localization, A/B variants).
  • Channel workers (push, email, sms, in-app, slack).
  • Vendor abstraction layer (multi-vendor with health-based routing).
  • Audit + analytics store (Cassandra or ClickHouse).
  • Webhook receiver for vendor status updates.
  • Bounce / unsub processor.

Multi-channel preferences

User preferences: per-channel (email yes, SMS no) per-template-category (transactional always, marketing opt-in).

Suppression overrides preferences for bounces and TCPA / GDPR.

Cross-channel dedup: if you already sent an email about this event, don't also send a push.

Fallback chain: try push; if no device registered, fall back to email.

Vendor strategy

Multi-vendor for redundancy: primary + backup per channel.

Vendor abstraction layer routes to primary; on 5xx or timeout, fails over.

Vendor-specific rate limits respected via per-vendor rate limiters.

Async delivery status via webhooks; sometimes delayed by hours.

Periodic reconciliation: compare what we sent vs what the vendor reports delivered.

Trade-offs

Single queue for all: simple but bulk starves transactional.

Separate queues per priority: better isolation, more infra.

Sync API for transactional, async for bulk: lower latency for critical paths.

Push tokens churn — apps reinstall, devices change. Background cleanup of invalid tokens essential.

Email warm-up takes weeks; cold IPs land in spam.

SMS expensive — gate behind verification flows; A2P 10DLC compliance in US.

Compliance and deliverability

Email: SPF, DKIM, DMARC alignment required for good deliverability.

IP warming: send small volumes from new IPs gradually; reputation builds.

Marketing vs transactional IP separation prevents marketing bounces from poisoning transactional reputation.

Unsubscribe link in every marketing message (CAN-SPAM).

Preference center; honor opt-outs across channels.

TCPA for SMS in US: prior express written consent for marketing; carrier registration (A2P 10DLC).

GDPR for EU: explicit consent, data subject rights, audit trail.