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.

1. Requirements

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

Quick revision

  • 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.