Design a notification system: push, SMS and email

Notifications look simple until you send millions across push, SMS and email. We design a reliable, respectful pipeline.

A bell sending messages through several channels

Goal: other services call one notification system to send messages to users by push, SMS, email or in-app, reliably and without spamming.

Requirements

  • Multiple channels and templates (many languages).
  • Respect user preferences and quiet hours.
  • Reliable delivery with retries, no duplicates.
  • Handle spikes (a sale starts and millions are notified).
  • Track delivery status.

Architecture

  1. API / event intake: services send "notify user X about event Y" with a template ID and data.
  2. Notification service: validates, looks up the user's preferences and contact info, checks rate limits, renders the template, and decides channels.
  3. Queues: one queue per channel (push, SMS, email) so a slow SMS provider doesn't block push.
  4. Workers per channel: take messages and call the provider (APNs and FCM for push, an SMS gateway, an email service).
  5. Status tracking: record sent, delivered, failed, opened.

Why queues

They absorb spikes, isolate failures between channels and allow retries with exponential backoff. After repeated failures, a message goes to a dead-letter queue for inspection.

Avoiding duplicates

Providers and queues are at-least-once. Give each notification a unique dedup key (for example user + event + template) and skip a repeat within a time window. Make workers idempotent.

Priorities

Separate high-priority traffic (one-time passwords, security alerts) from bulk marketing, with its own queue and capacity, so a campaign never delays a login code.

Preferences and compliance

  • Let users opt out per channel and category. Transactional messages and marketing have different rules.
  • Honour unsubscribe links and local regulations.
  • Keep opt-in/out records.

Rate limiting and batching

Limit how many notifications one user gets per hour, and batch or digest low-priority ones ("5 new comments"). Respect provider rate limits too.

Failures to plan for

Invalid push tokens (remove them), bounced emails (stop sending), a provider outage (fail over to a second provider), and templates with missing data (validate before sending).

Observability

Track queue depth, latency from event to delivery, provider error rates and delivery success per channel.

Takeaway: decouple with per-channel queues, make sends idempotent, separate urgent from bulk, honour preferences, and plan provider failover.


System Design series, Part 3 (case studies): 8 of 16. Previous: Design a payment system for a mobile wallet.

Keep reading