REUSABLE COMPONENT DESIGN / OBJECT DESIGN BRIEF

Notification service design

A notification service sends messages to users over channels such as email, SMS, push, and in-app, based on events (order shipped, password reset).

IntermediatePhase 08 / Topic 4 of 7ResponsibilitiesCollaborationsExtensibility
01

Overview

A notification service sends messages to users over channels such as email, SMS, push, and in-app, based on events (order shipped, password reset). The LLD focuses on clean abstractions: a Channel interface with provider adapters, templates rendered with data, user preferences (opt-outs, quiet hours), priority, retries with fallback providers, and delivery tracking.

Patterns fit naturally: Strategy/Adapter for channels and providers, Template Method or template engines for content, Observer or event subscriptions for triggers, Chain of Responsibility for preference and rate-limit checks, and Decorator for retries and logging.

A post office with many delivery methods

You hand over a message and the recipient's preferences. The post office decides whether it goes by courier, regular mail, or phone call, retries if nobody is home, and records the delivery.

02

When to use it

  • Interview prompt: 'Design a notification system'.
  • Any product sending transactional or marketing messages.
  • Multi-provider messaging with failover.
03

Where it shows up in interviews

Multi-channel delivery

Recognize it when: email, SMS, push with preferences.

  • Design a notification service
  • Design an alerting system
Provider failover

Recognize it when: if Twilio fails, use another provider.

  • Design an OTP service
  • Design transactional email
04

Where it is used in real software

Amazon SNS, Twilio, SendGrid, FCM

Providers behind notification services for SMS, email, and push.

Courier, Knock, Novu

Notification infrastructure products with templates, preferences, and routing.

Uber and Airbnb

Internal notification platforms route millions of messages with priorities and preferences.

05

Key terms

Channel
Delivery medium: email, SMS, push, in-app.
Provider
Vendor implementing a channel (SES, Twilio).
Template
Content with placeholders rendered per user.
Preference
User opt-ins, quiet hours, channel choices.
Delivery status
Queued, sent, delivered, failed.
06

How it works, step by step

  1. 1
    Define the request

    userId, type, data, priority.

  2. 2
    Resolve preferences

    Which channels are allowed now.

  3. 3
    Render the template

    Per channel and locale.

  4. 4
    Send via channel providers

    With retries and fallback.

  5. 5
    Record status

    For deduplication, audit, and analytics.

Sending an 'order shipped' notification
Step 1 / 4
Event
Preferences
Templates
Push
Email
Status log

STEP 1OrderShipped arrives. User 42 allows push and email, not SMS.

07

Routing decisions

Different notification types

Step 1 / 4
TypePriorityChannelsRespect quiet hours?
Password reset OTPCriticalSMS, fallback emailNo
Order shippedNormalPush + emailPush: yes
Weekly digestLowEmailYes
Security alertCriticalPush + email + SMSNo

NOWType: Password reset OTP | Priority: Critical | Channels: SMS, fallback email | Respect quiet hours?: No

Priority and preferences are policy data, not hard-coded branches in senders.

08

Implementation

type ChannelName = "email" | "sms" | "push";type Message = { to: string; subject?: string; body: string }; interface Channel { name: ChannelName; send(msg: Message): Promise<void> } class FallbackChannel implements Channel {  constructor(readonly name: ChannelName, private providers: Channel[]) {}  async send(msg: Message) {    let lastError: unknown;    for (const p of this.providers) {      try { return await p.send(msg); } catch (e) { lastError = e; }    }    throw lastError;  }} type Prefs = { channels: ChannelName[]; quietHours?: [number, number]; contact: Record<ChannelName, string> };type Request = { eventId: string; userId: string; type: string; priority: "critical" | "normal" | "low"; data: Record<string, string> }; class NotificationService {  private sent = new Set<string>();  constructor(    private channels: Map<ChannelName, Channel>,    private prefs: (userId: string) => Promise<Prefs>,    private templates: (type: string, channel: ChannelName, data: Record<string, string>) => Message["body"],    private hour = () => new Date().getHours(),  ) {}   async notify(req: Request) {    const p = await this.prefs(req.userId);    const quiet = p.quietHours && this.hour() >= p.quietHours[0] && this.hour() < p.quietHours[1];    const targets = p.channels.filter((c) => req.priority === "critical" || !(quiet && c === "push"));    await Promise.allSettled(targets.map(async (c) => {      const key = `${req.eventId}:${c}`;      if (this.sent.has(key)) return;                               // idempotent per event and channel      await this.channels.get(c)!.send({ to: p.contact[c], body: this.templates(req.type, c, req.data) });      this.sent.add(key);    }));  }}
09

Complexity and performance

Per notificationO(channels)

Plus provider latency.

Adding a channel1 class

Registered in the map.

10

Trade-offs

Sync vs async sending

Sending inline is simple but slow; production systems enqueue and send from workers.

Idempotency storage

In-memory dedupe is lost on restart; store delivery records durably.

11

Variants and related techniques

Digest batching

Combine low-priority notifications into periodic summaries.

In-app inbox

Persist notifications for users to read later.

12

Common mistakes

  • Hard-coded channel logic in business services.

    Fix: Services publish events; the notification service decides channels.

  • Ignoring user preferences and regulations.

    Fix: Honor opt-outs and quiet hours; required for compliance.

  • Duplicate sends on retry.

    Fix: Deduplicate by event ID and channel.

13

Interview questions

How would you support a new channel like WhatsApp?

Implement the Channel interface with a WhatsApp provider adapter, add templates for it, register it in the channel map, and let users opt in through preferences; no changes to existing senders.

How do you handle provider outages?

Wrap channels with retry decorators and fallback providers, send asynchronously from a queue with backoff, and track delivery status so failures can be retried or alerted.

14

Practice problems

ProblemDifficultyWhat it trains
Multi-channel notifier with preferencesMediumStrategy and adapters.
Add retries, fallback, and dedupeHardReliability.