Design a Notification System
📖 Concept
A notification system delivers messages to users across multiple channels: push notifications (mobile), email, SMS, in-app notifications, and webhooks.
Requirements
Functional: Multi-channel delivery (push, email, SMS, in-app), template management, user preferences (opt-in/out per channel), scheduling, priority levels Non-Functional: 10B notifications/day, low latency for critical alerts, at-least-once delivery, graceful handling of third-party failures
Architecture
Event-Driven Design
- Service emits event:
order.shipped {userId, orderId, trackingUrl} - Notification service receives event
- Looks up user preferences (channels opted in)
- Renders notification template per channel
- Routes to channel-specific workers (push/email/SMS)
- Workers deliver via third-party APIs (FCM, SendGrid, Twilio)
Priority Queues
- Critical (P0): Security alerts, 2FA codes → immediate delivery
- High (P1): Order confirmations → within seconds
- Medium (P2): Social notifications → batched, within minutes
- Low (P3): Marketing → scheduled, respect quiet hours
Deduplication
Same notification sent twice is worse than not at all. Use idempotency keys: hash(userId + eventType + eventId) → check before sending.
Interview tip: Notification systems are great for demonstrating queue architecture, fan-out, and third-party API integration. Show the multi-channel routing and priority handling.
💻 Code Example
1// ============================================2// Notification System — Architecture3// ============================================45class NotificationService {6 constructor(queue, db, templateEngine) {7 this.queue = queue;8 this.db = db;9 this.templates = templateEngine;10 this.channels = {11 push: new PushWorker(),12 email: new EmailWorker(),13 sms: new SMSWorker(),14 inApp: new InAppWorker(),15 };16 }1718 async processEvent(event) {19 // 1. Get user preferences20 const prefs = await this.db.getUserNotificationPrefs(event.userId);2122 // 2. Check deduplication23 const dedupKey = `notif:\${event.userId}:\${event.type}:\${event.id}`;24 const alreadySent = await this.db.cache.get(dedupKey);25 if (alreadySent) return;2627 // 3. Route to enabled channels28 const enabledChannels = this.getEnabledChannels(event.type, prefs);2930 for (const channel of enabledChannels) {31 // 4. Render template for each channel32 const content = this.templates.render(event.type, channel, event.data);3334 // 5. Queue with priority35 await this.queue.publish(`notifications.\${channel}`, {36 userId: event.userId,37 channel,38 content,39 priority: this.getPriority(event.type),40 dedupKey,41 });42 }4344 // 6. Mark as sent (dedup)45 await this.db.cache.set(dedupKey, '1', 'EX', 86400);46 }4748 getEnabledChannels(eventType, prefs) {49 const defaults = {50 'security.alert': ['push', 'email', 'sms'],51 'order.shipped': ['push', 'email'],52 'social.like': ['push', 'inApp'],53 'marketing.promo': ['email'],54 };55 const defaultChannels = defaults[eventType] || ['inApp'];56 return defaultChannels.filter(ch => prefs[ch] !== false);57 }5859 getPriority(eventType) {60 if (eventType.startsWith('security')) return 0; // Critical61 if (eventType.startsWith('order')) return 1; // High62 if (eventType.startsWith('social')) return 2; // Medium63 return 3; // Low64 }65}6667class PushWorker {68 async send(notification) {69 console.log(`[Push] → User \${notification.userId}: \${notification.content.title}`);70 // Call FCM/APNs API71 }72}73class EmailWorker {74 async send(notification) {75 console.log(`[Email] → User \${notification.userId}: \${notification.content.subject}`);76 }77}78class SMSWorker {79 async send(notification) {80 console.log(`[SMS] → User \${notification.userId}: \${notification.content.body}`);81 }82}83class InAppWorker {84 async send(notification) {85 console.log(`[In-App] → User \${notification.userId}: \${notification.content.message}`);86 }87}8889console.log("Notification system architecture demonstrated.");
🏋️ Practice Exercise
Full Notification Design: Design a notification system for an e-commerce platform. Include: push, email, SMS, in-app. Handle: user preferences, quiet hours, rate limiting, batching.
Third-Party Failure: What happens when FCM (push) or SendGrid (email) goes down? Design retry logic, circuit breakers, and fallback channels.
Notification Batching: A user gets 50 likes in 10 minutes. Instead of 50 push notifications, send one: "50 people liked your post." Design the batching logic.
Global Notification: Send a notification to ALL 100M users (system maintenance announcement). Design the fan-out strategy.
⚠️ Common Mistakes
Not deduplicating notifications — without idempotency checks, retries or duplicate events cause users to receive the same notification multiple times.
Synchronous delivery — sending push/email/SMS synchronously blocks the main flow. Use queues for async delivery with per-channel workers.
Ignoring user preferences — sending notifications on opted-out channels is a legal/UX problem. Always check preferences before sending.
No rate limiting per user — a misbehaving service can trigger thousands of notifications to one user. Implement per-user rate limits.
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for Design a Notification System