Home/Learn/Low Level Design/Design a Notification System

Design a Notification System

Intermediate
LLD Interview Problems
Java Source Code

Build a multi-channel notification system with pluggable channel handlers, template support, retry logic via Chain of Responsibility, and event-driven dispatch.

Overview

A Notification System delivers messages across EMAIL, SMS, PUSH, and IN_APP channels. NotificationService receives a NotificationRequest, resolves the appropriate ChannelHandler implementations via Strategy, and dispatches through a Chain of Responsibility that adds retry and fallback behaviour. NotificationTemplate externalises message content so channel implementations stay free of business copy. Observer (event-driven) triggers notifications from application events without the caller needing to know the NotificationService API. This design is commonly asked at companies building platform-level services.

Requirements Analysis

Functional: send notification to one or more channels, support message templates, retry on transient failure, fall back to next channel when primary fails, track delivery status. Non-functional: adding a new channel requires only a new ChannelHandler class (OCP), retry logic isolated in the chain and not duplicated across handlers.

Requirements
// Entities : NotificationService, NotificationRequest, NotificationChannel, ChannelHandler, NotificationTemplate
// Patterns : Strategy (channel selection), Chain of Responsibility (retry/fallback), Observer (event-driven trigger)

Core Classes & Relationships

NotificationChannel enum: EMAIL, SMS, PUSH, IN_APP. ChannelHandler interface has send(NotificationRequest). Abstract RetryHandler wraps a ChannelHandler and retries on failure up to maxAttempts. NotificationTemplate holds subject and body with placeholder substitution. NotificationRequest carries recipient, channels list, template id, and dynamic variables.

Java — enums & interfaces
public enum NotificationChannel { EMAIL, SMS, PUSH, IN_APP }

public interface ChannelHandler {
    boolean send(NotificationRequest request);
    NotificationChannel channel();
}

public class NotificationRequest {
    private final String recipientId;
    private final String recipientAddress;  // email, phone, deviceToken
    private final List<NotificationChannel> channels;
    private final String templateId;
    private final Map<String, String> variables;

    public NotificationRequest(String recipientId, String recipientAddress,
                                List<NotificationChannel> channels,
                                String templateId, Map<String, String> variables) {
        this.recipientId = recipientId; this.recipientAddress = recipientAddress;
        this.channels = channels; this.templateId = templateId; this.variables = variables;
    }
    public List<NotificationChannel> getChannels() { return channels; }
    public String getRecipientAddress()            { return recipientAddress; }
    public Map<String, String> getVariables()      { return variables; }
    public String getTemplateId()                  { return templateId; }
}

public class NotificationTemplate {
    private final String subject;
    private final String bodyTemplate;

    public NotificationTemplate(String subject, String bodyTemplate) {
        this.subject = subject; this.bodyTemplate = bodyTemplate;
    }
    public String render(Map<String, String> vars) {
        String body = bodyTemplate;
        for (Map.Entry<String, String> e : vars.entrySet()) {
            body = body.replace("{{" + e.getKey() + "}}", e.getValue());
        }
        return body;
    }
    public String getSubject() { return subject; }
}

Java Implementation

EmailHandler and SmsHandler are concrete ChannelHandlers. RetryChannelHandler wraps any ChannelHandler and retries up to maxAttempts on failure. NotificationService resolves the template, selects registered handlers for the requested channels, and dispatches each through its retry wrapper. A FallbackChain tries channels in order until one succeeds.

Java — core classes
public class EmailHandler implements ChannelHandler {
    @Override public NotificationChannel channel() { return NotificationChannel.EMAIL; }
    @Override public boolean send(NotificationRequest req) {
        System.out.println("[EMAIL] Sending to " + req.getRecipientAddress());
        // integrate with SendGrid/SES here
        return true;
    }
}

public class SmsHandler implements ChannelHandler {
    @Override public NotificationChannel channel() { return NotificationChannel.SMS; }
    @Override public boolean send(NotificationRequest req) {
        System.out.println("[SMS] Sending to " + req.getRecipientAddress());
        // integrate with Twilio here
        return true;
    }
}

public class PushHandler implements ChannelHandler {
    @Override public NotificationChannel channel() { return NotificationChannel.PUSH; }
    @Override public boolean send(NotificationRequest req) {
        System.out.println("[PUSH] Sending to device " + req.getRecipientAddress());
        return true;
    }
}

// Retry decorator — wraps any ChannelHandler
public class RetryChannelHandler implements ChannelHandler {
    private final ChannelHandler delegate;
    private final int maxAttempts;

    public RetryChannelHandler(ChannelHandler delegate, int maxAttempts) {
        this.delegate = delegate; this.maxAttempts = maxAttempts;
    }
    @Override public NotificationChannel channel() { return delegate.channel(); }

    @Override public boolean send(NotificationRequest request) {
        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                if (delegate.send(request)) return true;
            } catch (Exception e) {
                System.out.println("Attempt " + attempt + " failed: " + e.getMessage());
            }
        }
        System.out.println("[RETRY] All " + maxAttempts + " attempts failed for " + channel());
        return false;
    }
}

// NotificationService — Facade + Strategy for channel selection
public class NotificationService {
    private final Map<NotificationChannel, ChannelHandler> handlers = new EnumMap<>(NotificationChannel.class);
    private final Map<String, NotificationTemplate> templates = new HashMap<>();

    public void registerHandler(ChannelHandler handler, int retries) {
        handlers.put(handler.channel(), new RetryChannelHandler(handler, retries));
    }
    public void registerTemplate(String id, NotificationTemplate template) {
        templates.put(id, template);
    }

    public void send(NotificationRequest request) {
        NotificationTemplate template = templates.get(request.getTemplateId());
        if (template != null) {
            String body = template.render(request.getVariables());
            System.out.println("Message: " + body);
        }
        for (NotificationChannel ch : request.getChannels()) {
            ChannelHandler handler = handlers.get(ch);
            if (handler != null) {
                boolean sent = handler.send(request);
                System.out.printf("Channel %s: %s%n", ch, sent ? "SUCCESS" : "FAILED");
            }
        }
    }
}

Key Points to Remember

  • 1ChannelHandler as an interface makes adding WhatsApp or Slack a new class with zero changes to NotificationService.
  • 2RetryChannelHandler is a Decorator that wraps any handler transparently — retry logic is not duplicated across channel implementations.
  • 3Template-based messaging decouples notification content from delivery infrastructure, enabling marketing to update copy without code changes.
  • 4Observer-driven triggering (ApplicationEvent → NotificationService) ensures business code never directly instantiates notification requests.

Interview Questions

Sign in to ask Aria
1

How would you implement user notification preferences so each user can opt out of specific channels?

MediumAmazon
2

How would you design a rate limiter per recipient to prevent notification spam?

HardUber
3

How would you track delivery receipts and retry undelivered notifications after a delay?

HardMicrosoft

Ask Aria about Design a Notification System

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…