CodePark/ questions / Classic design problems
START PRACTICINGPRACTICE
Backend question · System design & architecture › Classic design problems

Design a notification system that sends push, email and SMS notifications for many internal services.

Build one central notification service. It checks user preferences, renders templates, and puts one message per channel on a queue. Workers per channel then send through the providers.

API: internal services call the notification service, or publish an event. The request holds:

  • the user
  • a template id and the data for it
  • the channels
  • a priority
  • an idempotency key: a unique id for this request, so the same request is never sent twice

Processing: the service does these steps:

  • It validates the request.
  • It loads the user preferences and opt-outs. An opt-out means the user said "don't send me this".
  • It checks quiet hours and per-user frequency caps (a limit on how many messages one user gets).
  • It renders the template with the data.
  • It stores a notification record with status pending.
  • It puts one message per channel on separate queues: push, email and SMS.

Use priority separation: urgent messages get their own queue. Why? An OTP (one-time password) code must never wait behind a big marketing send.

Workers: each channel has its own workers that call the providers:

  • APNs or FCM for push
  • an email provider for email
  • an SMS gateway for SMS

The workers respect provider rate limits. They retry temporary failures with backoff, which means a longer wait before each retry. When a message runs out of retries, they send it to a DLQ (dead-letter queue: a queue that holds failed messages for later checking). Workers dedupe on the idempotency key, so a retry never sends the same message twice.

Tracking: providers call back with a status, such as delivered or bounced. The service updates the record and shows this status to the support team. Scheduled notifications go to a delayed queue or a scheduler.

Scaling: add more workers per channel. The queue holds sudden bursts until the workers catch up. For critical channels, add provider failover: if one provider is down, switch to another. This improves availability.

  • A central service handles preferences, templates and opt-outs.
  • Each channel has its own queue, and urgent messages are kept apart from bulk ones.
  • Workers retry with backoff, respect provider rate limits, and send failed messages to a DLQ.
  • An idempotency key stops the same notification from being sent twice.
  • The service tracks delivery status and supports scheduled notifications.