System Design

Case Study: Notification System

**Push:** - FCM (Firebase): Free, both iOS & Android - APNs: Required for iOS, direct connection - OneSignal: Unified API, free tier **Email:** - SendGrid: 100 emails/day free, great API - Mailgun: 5000/month free, EU hosting - AWS SES: Cheapest at scale ($0.10/1000) **SMS:** - Twilio: Most popular, ~$0.0075/SMS - Nexmo/Vonage: Similar pricing - AWS SNS: Cheapest, limited features

**iOS:** - APNs may throttle if too many notifications - Silent notifications limited to ~3/hour - Notification grouping mandatory in iOS 12+ **Android:** - FCM quota: 240 messages/minute per device - High-priority limited to ~10/day per app - Doze mode delays normal priority notifications

- Explicit opt-in required for marketing emails in EU - Easy unsubscribe must be available - Log consent timestamps - Provide notification preferences UI - Delete user data on account deletion

Notification System Architecture

Notification System Architecture

Why is this needed?

Modern applications use many channels for notifications: push, email, SMS, in-app. A unified notification system ensures a consistent experience and avoids spamming users.

What is it?

A notification system consists of: event ingestion, preference filtering, template rendering, channel routing, delivery providers, and analytics. An asynchronous architecture ensures scalability.

How does it work?

Example

**Example notification flow:** ```typescript // Order shipped → multiple channels await notificationApi.send({ eventType: 'order.shipped', userId: 'user123', data: { orderId: 'ORD-456', trackingNumber: '1Z999AA10123456784', carrier: 'UPS', estimatedDelivery: '2024-01-20' } }); // Results in: // 1. Push: "Your order is on its way! Track: 1Z999..." // 2. Email: Full HTML email with tracking link // 3. In-app: Notification in inbox ```

What did you learn about Notification System architecture?

Push Notifications: FCM and APNs

Push Notifications: FCM and APNs

Why is this needed?

Push notifications are the most effective channel for mobile engagement. But they are complex: different providers for iOS and Android, token management, handling failures.

What is it?

Firebase Cloud Messaging (FCM) for Android and Apple Push Notification service (APNs) for iOS. The system must manage device tokens, handle invalid tokens, and support rich notifications.

How does it work?

Example

**Rich notification on iOS:** ```typescript // Notification with image and actions await pushService.send(userId, { title: 'New message from John', body: 'Hey, are you coming to the party?', imageUrl: 'https://cdn.app.com/avatars/john.jpg', categoryId: 'MESSAGE', // Enables action buttons data: { type: 'message', senderId: 'john123', threadId: 'thread456' } }); // iOS notification extension renders: // ┌──────────────────────────────┐ // │ [John's avatar] │ // │ New message from John │ // │ Hey, are you coming to... │ // │ ───────────────────────── │ // │ [Reply] [Mark Read] [Mute] │ // └──────────────────────────────┘ ```

What did you learn about push notifications: FCM and APNs?

User Preferences and Rate Limiting

User Preferences and Rate Limiting

Why is this needed?

Without proper preferences management and rate limiting, notifications become spam. This leads to disabling notifications or deleting the app. Rate limiting protects users from notification fatigue.

What is it?

The preferences system lets users control which notifications they receive and through which channels. Rate limiting restricts the number of notifications in a time period at different levels: per-user, per-channel, per-category.

How does it work?

Example

**Notification batching:** ```typescript // Instead of sending 20 "X liked your post" notifications: class NotificationBatcher { async batchSimilarNotifications( userId: string, eventType: string ): Promise<BatchedNotification | null> { const key = `pending:${userId}:${eventType}`; const pending = await this.redis.lrange(key, 0, -1); if (pending.length < 5) { return null; // Not enough to batch } // Clear pending await this.redis.del(key); const events = pending.map(JSON.parse); if (eventType === 'new_like') { return { title: `${events[0].userName} and ${events.length - 1} others liked your post`, body: null, data: { type: 'batched_likes', postId: events[0].postId, count: events.length } }; } // Similar for new_follower, new_comment, etc. } } ```

What did you learn about user preferences and rate limiting?

Template Engine and Localization

Template Engine and Localization

Why is this needed?

Notifications must be personalized and localized. A template engine separates content from logic, supports A/B testing, and enables quick text updates without a deployment.

What is it?

The template system stores templates for each event type and channel. Templates support variables, conditions, pluralization, and localization. They can be stored in a DB for dynamic updates.

How does it work?

Example

**Channel-specific templates:** ```typescript // Same event, different templates per channel // Push (short, actionable) const pushTemplate = { title: '🎉 Flash Sale!', body: '50% off everything. Ends in {{hoursLeft}}h' }; // Email (detailed) const emailTemplate = { subject: 'Don\'t miss out! 50% Flash Sale', htmlBody: ` <h1>Flash Sale is ON!</h1> <p>Hi {{userName}},</p> <p>For the next {{hoursLeft}} hours, enjoy 50% off on all items.</p> <a href="{{shopUrl}}" class="cta">Shop Now</a> ` }; // SMS (ultra-short) const smsTemplate = { body: 'Flash Sale! 50% off for {{hoursLeft}}h. Shop: {{shortLink}}' }; ```

What did you learn about template engine and localization?

Delivery Tracking and Analytics

Delivery Tracking and Analytics

Why is this needed?

Without tracking it's impossible to understand notification effectiveness. Delivery rate, open rate, and CTR metrics enable optimizing content and send times.

What is it?

Tracking includes: delivery status from providers, open tracking (via pixels or SDK callbacks), click tracking (via redirect URLs), and engagement analytics.

How does it work?

Example

**Real-time delivery monitoring:** ```typescript // Dashboard websocket for ops team class DeliveryMonitor { async getRealTimeStats(): Promise<RealTimeStats> { const now = new Date(); const fiveMinAgo = new Date(now.getTime() - 5 * 60 * 1000); return { sent: await this.redis.zcount('sent', fiveMinAgo, now), delivered: await this.redis.zcount('delivered', fiveMinAgo, now), failed: await this.redis.zcount('failed', fiveMinAgo, now), // Alert thresholds deliveryRate: 0.982, // Should be > 0.95 avgLatencyMs: 1250, // Should be < 5000 // Provider status providers: { fcm: { status: 'healthy', latency: 450 }, apns: { status: 'healthy', latency: 380 }, sendgrid: { status: 'degraded', latency: 2100 } } }; } } ```

What did you learn about delivery tracking and analytics?

Связанные уроки

  • rt-03-sse
Case Study: Notification System

0

1

Sign In