ayushsalampuriya.xyz Revise

System Design · Design Question

Design a Notification System

Multi-channel notifications: push, SMS, email — templates, preferences, fan-out, retries, and provider abstraction.

In simple words: Accept one request, queue it safely, then let channel workers deliver push, email, or SMS while respecting user preferences.

Concrete example: Order shipped → API accepts the event, queues it, and a worker sends push "Order #123 on the way" plus an email with tracking — unless the user disabled email in preferences.

1. Requirements

Functional

Non-functional

Back-of-envelope

100M notifications/day ≈ 1.2k/s average; a 10× campaign peak is ≈ 12k/s. At ~500B of metadata per channel attempt, 100M attempts add ~50GB/day before indexes.

2. API

POST /v1/notifications { "userId": "u42", "templateId": "order_shipped", "data": { "orderId": "O9", "eta": "Tomorrow" }, "channels": ["push", "email"], // optional override "idempotencyKey": "order_shipped:O9" } → { "notificationId": "n123", "status": "accepted" } GET /v1/notifications/{id} → status per channel

3. Data model

templates(template_id, channel, body, locale...) user_devices(user_id, device_token, platform) user_prefs(user_id, channel, category, enabled) notifications(id, user_id, template_id, payload, created_at) delivery_attempts(notification_id, channel, status, provider_id, ts)

4. High-level design

Producers (Order, Social, Auth) → Notification API → validate + persist → Kafka topics: txn_notifications | bulk_campaigns | v Dispatcher workers |-- prefs filter / quiet hours |-- render template |-- channel routers |-- Push Worker → FCM/APNs |-- Email Worker → SES/SendGrid |-- SMS Worker → Twilio / gateway |-- update delivery_attempts; DLQ on poison

5. Deep dives

Priority lanes

OTP and security alerts get a dedicated high-priority queue and provider account so a marketing blast cannot starve them.

Idempotency

Unique(idempotencyKey) on accept Workers dedupe by (notification_id, channel) Providers may also support idempotent request ids

Fan-out

One event → N devices. Expand device tokens in the push worker; batch provider APIs where supported. Soft-fail invalid tokens and remove them.

Rate limits

Respect provider quotas; token-bucket per channel. Backpressure via consumer lag metrics.

6. Scaling

7. Observability

8. Edge cases

9. Template rendering

Keep templates versioned. Render with strict escaping to avoid injection into HTML email. Support locale fallback: en-INen. Preview API for product teams reduces production mistakes.

template order_shipped@v3 push: "Order {{orderId}} shipped. ETA {{eta}}" email: subject + HTML body with same variables

10. Compliance

11. Failure modes

Interview talking points

  • Lead with: Protect transactional traffic with a separate priority lane.
  • Accept API → durable queue → channel workers → providers.
  • Separate transactional vs bulk lanes.
  • Prefs, quiet hours, templates before send.
  • Idempotency keys prevent duplicate OTPs/blasts.
  • Track per-channel attempts; DLQ poison messages.
  • Respect provider rate limits; clean invalid push tokens.
  • Priority: never let marketing block OTP.