Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on building notification systems! ๐ In this guide, weโll explore how to create robust email and push notification systems using TypeScript.
Youโll discover how proper notification architecture can transform your applicationโs user engagement. Whether youโre building social media platforms ๐ฑ, e-commerce sites ๐, or SaaS applications ๐ผ, understanding notification systems is essential for keeping users informed and engaged.
By the end of this tutorial, youโll feel confident implementing notification systems in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding Notification Systems
๐ค What is a Notification System?
A notification system is like a smart postal service ๐ฎ. Think of it as your applicationโs way of sending messages to users through different channels (email, push notifications, SMS) to keep them informed about important events.
In TypeScript terms, a notification system is a structured way to:
- โจ Send messages across multiple channels
- ๐ Queue and batch notifications efficiently
- ๐ก๏ธ Handle failures and retries gracefully
๐ก Why Use TypeScript for Notifications?
Hereโs why developers love TypeScript for notification systems:
- Type Safety ๐: Ensure notification payloads are always correct
- Better IDE Support ๐ป: Autocomplete for notification templates
- Code Documentation ๐: Types serve as inline documentation
- Refactoring Confidence ๐ง: Change notification structures safely
Real-world example: Imagine building a social media app ๐ฑ. With TypeScript, you can ensure every โlikeโ notification has the correct user data, post reference, and timestamp.
๐ง Basic Syntax and Usage
๐ Simple Email Notification
Letโs start with a friendly example:
// ๐ Hello, Notifications!
interface EmailNotification {
to: string; // ๐ง Recipient email
subject: string; // ๐ Email subject
body: string; // ๐ฌ Email content
priority?: 'low' | 'normal' | 'high'; // ๐ฏ Optional priority
}
// ๐จ Creating a notification service
class NotificationService {
private emailQueue: EmailNotification[] = [];
// ๐ฎ Send email notification
async sendEmail(notification: EmailNotification): Promise<void> {
console.log(`๐ง Sending email to ${notification.to}`);
// Email sending logic here
}
}
๐ฏ Push Notification Pattern
Hereโs how to structure push notifications:
// ๐ Push notification interface
interface PushNotification {
userId: string; // ๐ค Target user
title: string; // ๐ Notification title
message: string; // ๐ฌ Notification body
icon?: string; // ๐จ Optional icon URL
badge?: number; // ๐ข Optional badge count
data?: Record<string, any>; // ๐ฆ Custom data payload
}
// ๐ฑ Device token management
interface DeviceToken {
userId: string;
token: string;
platform: 'ios' | 'android' | 'web';
lastActive: Date;
}
๐ก Practical Examples
๐ Example 1: E-commerce Order Notifications
Letโs build a real notification system for an online store:
// ๐๏ธ Order notification types
interface OrderNotification {
orderId: string;
customerEmail: string;
customerName: string;
items: Array<{
name: string;
quantity: number;
emoji: string; // Every product needs an emoji!
}>;
total: number;
status: 'confirmed' | 'shipped' | 'delivered';
}
// ๐ง Email template builder
class OrderEmailBuilder {
// ๐จ Build order confirmation email
buildConfirmationEmail(order: OrderNotification): EmailNotification {
const itemsList = order.items
.map(item => `${item.emoji} ${item.name} x${item.quantity}`)
.join('\n');
return {
to: order.customerEmail,
subject: `๐ Order Confirmed! #${order.orderId}`,
body: `
Hi ${order.customerName}! ๐
Your order has been confirmed! ๐
๐ฆ Order Details:
${itemsList}
๐ฐ Total: $${order.total}
We'll notify you when it ships! ๐
`,
priority: 'high'
};
}
// ๐ Build shipping notification
buildShippingEmail(order: OrderNotification): EmailNotification {
return {
to: order.customerEmail,
subject: `๐ Your order is on the way! #${order.orderId}`,
body: `Great news ${order.customerName}! Your order is shipped! ๐ฆโจ`,
priority: 'normal'
};
}
}
// ๐ Push notification builder
class OrderPushBuilder {
buildOrderPush(order: OrderNotification): PushNotification {
const statusEmojis = {
confirmed: 'โ
',
shipped: '๐',
delivered: '๐ฆ'
};
return {
userId: order.customerEmail, // In real app, use userId
title: `${statusEmojis[order.status]} Order ${order.status}!`,
message: `Your order #${order.orderId} is ${order.status}`,
badge: 1,
data: {
orderId: order.orderId,
deepLink: `/orders/${order.orderId}`
}
};
}
}
// ๐ฎ Let's use it!
const emailBuilder = new OrderEmailBuilder();
const pushBuilder = new OrderPushBuilder();
const notificationService = new NotificationService();
const order: OrderNotification = {
orderId: "12345",
customerEmail: "[email protected]",
customerName: "Sarah",
items: [
{ name: "TypeScript Book", quantity: 1, emoji: "๐" },
{ name: "Coffee Mug", quantity: 2, emoji: "โ" }
],
total: 49.99,
status: 'confirmed'
};
// Send notifications
const email = emailBuilder.buildConfirmationEmail(order);
await notificationService.sendEmail(email);
๐ฎ Example 2: Social Media Engagement Notifications
Letโs create a notification system for social interactions:
// ๐ Social notification types
type NotificationType =
| 'like'
| 'comment'
| 'follow'
| 'mention'
| 'share';
interface SocialNotification {
id: string;
recipientId: string;
actorName: string;
actorAvatar: string;
type: NotificationType;
targetId: string; // Post ID, comment ID, etc.
timestamp: Date;
read: boolean;
}
// ๐ Notification aggregator
class NotificationAggregator {
private notifications = new Map<string, SocialNotification[]>();
// ๐ฏ Add notification with smart batching
addNotification(notification: SocialNotification): void {
const key = `${notification.recipientId}-${notification.type}-${notification.targetId}`;
if (!this.notifications.has(key)) {
this.notifications.set(key, []);
}
this.notifications.get(key)!.push(notification);
}
// ๐ฆ Get batched notifications
getBatchedNotifications(userId: string): Array<{
type: NotificationType;
count: number;
actors: string[];
message: string;
emoji: string;
}> {
const userNotifications: any[] = [];
this.notifications.forEach((notifs, key) => {
if (key.startsWith(userId)) {
const type = notifs[0].type;
const actors = [...new Set(notifs.map(n => n.actorName))];
userNotifications.push({
type,
count: notifs.length,
actors: actors.slice(0, 3),
message: this.buildMessage(type, actors, notifs.length),
emoji: this.getEmoji(type)
});
}
});
return userNotifications;
}
// ๐ฌ Build aggregated message
private buildMessage(
type: NotificationType,
actors: string[],
count: number
): string {
const othersCount = count - actors.length;
const actorString = actors.join(', ');
const others = othersCount > 0 ? ` and ${othersCount} others` : '';
const messages = {
like: `liked your post`,
comment: `commented on your post`,
follow: `started following you`,
mention: `mentioned you`,
share: `shared your post`
};
return `${actorString}${others} ${messages[type]}`;
}
// ๐จ Get emoji for notification type
private getEmoji(type: NotificationType): string {
const emojis = {
like: 'โค๏ธ',
comment: '๐ฌ',
follow: '๐ฅ',
mention: '@',
share: '๐'
};
return emojis[type];
}
}
// ๐ Real-time notification dispatcher
class RealtimeNotificationDispatcher {
private subscribers = new Map<string, (notification: any) => void>();
// ๐ก Subscribe to notifications
subscribe(userId: string, callback: (notification: any) => void): void {
this.subscribers.set(userId, callback);
console.log(`๐ค ${userId} subscribed to notifications`);
}
// ๐ค Dispatch notification
dispatch(notification: SocialNotification): void {
const callback = this.subscribers.get(notification.recipientId);
if (callback) {
callback({
...notification,
formattedTime: this.formatTime(notification.timestamp)
});
}
}
// โฐ Format time for display
private formatTime(date: Date): string {
const now = new Date();
const diff = now.getTime() - date.getTime();
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return 'just now โก';
if (minutes < 60) return `${minutes}m ago ๐`;
if (minutes < 1440) return `${Math.floor(minutes / 60)}h ago ๐`;
return `${Math.floor(minutes / 1440)}d ago ๐
`;
}
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Notification Queuing
When youโre ready to level up, implement advanced queuing:
// ๐ฏ Advanced notification queue with retry logic
interface QueuedNotification<T> {
id: string;
payload: T;
channel: 'email' | 'push' | 'sms';
attempts: number;
maxAttempts: number;
nextRetry?: Date;
priority: number;
status: 'pending' | 'processing' | 'completed' | 'failed';
}
// ๐ช Priority queue implementation
class NotificationQueue<T> {
private queue: QueuedNotification<T>[] = [];
private processing = false;
// โ Add to queue with priority
enqueue(
notification: T,
channel: QueuedNotification<T>['channel'],
priority: number = 5
): void {
const queued: QueuedNotification<T> = {
id: Date.now().toString(),
payload: notification,
channel,
attempts: 0,
maxAttempts: 3,
priority,
status: 'pending'
};
// Insert based on priority
const insertIndex = this.queue.findIndex(n => n.priority < priority);
if (insertIndex === -1) {
this.queue.push(queued);
} else {
this.queue.splice(insertIndex, 0, queued);
}
this.processQueue();
}
// ๐ Process queue with exponential backoff
private async processQueue(): Promise<void> {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const notification = this.queue.shift()!;
try {
notification.status = 'processing';
await this.sendNotification(notification);
notification.status = 'completed';
console.log(`โ
Sent ${notification.channel} notification`);
} catch (error) {
notification.attempts++;
if (notification.attempts < notification.maxAttempts) {
notification.status = 'pending';
notification.nextRetry = new Date(
Date.now() + Math.pow(2, notification.attempts) * 1000
);
this.queue.push(notification);
console.log(`๐ Retry scheduled for ${notification.id}`);
} else {
notification.status = 'failed';
console.log(`โ Failed to send ${notification.id}`);
}
}
this.processing = false;
setTimeout(() => this.processQueue(), 100);
}
// ๐ค Send notification (implement based on channel)
private async sendNotification(
notification: QueuedNotification<T>
): Promise<void> {
// Simulate sending
await new Promise(resolve => setTimeout(resolve, 1000));
if (Math.random() > 0.8) {
throw new Error('Simulated failure');
}
}
}
๐๏ธ Advanced Topic 2: Template System
For the brave developers, hereโs a template system:
// ๐ Type-safe template system
type TemplateVars<T extends string> =
T extends `${infer _Start}{{${infer Var}}}${infer Rest}`
? Var | TemplateVars<Rest>
: never;
type TemplateData<T extends string> = Record<TemplateVars<T>, string>;
// ๐จ Template engine
class NotificationTemplateEngine {
private templates = new Map<string, string>();
// ๐ Register template
registerTemplate<T extends string>(
name: string,
template: T
): void {
this.templates.set(name, template);
}
// ๐ Render template with type safety
render<T extends string>(
templateName: string,
data: TemplateData<T>
): string {
let template = this.templates.get(templateName) || '';
Object.entries(data).forEach(([key, value]) => {
template = template.replace(
new RegExp(`{{${key}}}`, 'g'),
value as string
);
});
return template;
}
}
// ๐ฏ Usage with type safety
const engine = new NotificationTemplateEngine();
engine.registerTemplate(
'welcome',
'Welcome {{name}}! ๐ Your account {{email}} is ready.'
);
// TypeScript ensures all variables are provided!
const rendered = engine.render('welcome', {
name: 'Alice',
email: '[email protected]'
});
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Notification Spam
// โ Wrong way - sending too many notifications!
users.forEach(user => {
notifications.forEach(notification => {
sendNotification(user, notification); // ๐ฅ Spam alert!
});
});
// โ
Correct way - batch and throttle!
class NotificationThrottler {
private lastSent = new Map<string, Date>();
private minInterval = 60000; // 1 minute
canSend(userId: string): boolean {
const last = this.lastSent.get(userId);
if (!last) return true;
return Date.now() - last.getTime() > this.minInterval;
}
markSent(userId: string): void {
this.lastSent.set(userId, new Date());
}
}
๐คฏ Pitfall 2: Missing Error Handling
// โ Dangerous - no error handling!
async function sendPushNotification(token: string, message: string) {
const response = await fetch('/api/push', {
method: 'POST',
body: JSON.stringify({ token, message })
});
// ๐ฅ What if this fails?
}
// โ
Safe - proper error handling!
async function sendPushNotification(
token: string,
message: string
): Promise<{ success: boolean; error?: string }> {
try {
const response = await fetch('/api/push', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, message })
});
if (!response.ok) {
console.log(`โ ๏ธ Push failed: ${response.status}`);
return { success: false, error: response.statusText };
}
return { success: true };
} catch (error) {
console.log(`โ Push error: ${error}`);
return { success: false, error: String(error) };
}
}
๐ ๏ธ Best Practices
- ๐ฏ Use Queues: Always queue notifications for reliability
- ๐ Template Everything: Use templates for consistent messaging
- ๐ก๏ธ Handle Failures: Implement retry logic with backoff
- ๐จ Batch Similar Notifications: Aggregate to reduce noise
- โจ Respect User Preferences: Always check notification settings
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Complete Notification System
Create a notification system for a task management app:
๐ Requirements:
- โ Support email and push notifications
- ๐ท๏ธ Different notification types (task assigned, due soon, completed)
- ๐ค User preference management
- ๐ Schedule notifications for future delivery
- ๐จ Each notification type needs custom templates!
๐ Bonus Points:
- Add SMS support
- Implement notification history
- Create an unsubscribe system
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete notification system for task management!
interface Task {
id: string;
title: string;
assigneeId: string;
dueDate: Date;
priority: 'low' | 'medium' | 'high';
emoji: string;
}
interface UserPreferences {
userId: string;
email: boolean;
push: boolean;
sms: boolean;
quietHours: { start: number; end: number };
frequency: 'instant' | 'hourly' | 'daily';
}
// ๐ง Notification service
class TaskNotificationService {
private preferences = new Map<string, UserPreferences>();
private scheduled = new Map<string, Date>();
// ๐ฏ Send task assigned notification
async notifyTaskAssigned(task: Task, assignerName: string): Promise<void> {
const prefs = this.preferences.get(task.assigneeId);
if (!prefs) return;
const notification = {
title: `${task.emoji} New task assigned!`,
message: `${assignerName} assigned you: ${task.title}`,
priority: task.priority,
data: { taskId: task.id }
};
if (this.canSendNow(prefs)) {
if (prefs.email) await this.sendEmail(task.assigneeId, notification);
if (prefs.push) await this.sendPush(task.assigneeId, notification);
} else {
this.scheduleForLater(task.assigneeId, notification);
}
}
// โฐ Check quiet hours
private canSendNow(prefs: UserPreferences): boolean {
const hour = new Date().getHours();
const { start, end } = prefs.quietHours;
if (start < end) {
return hour < start || hour >= end;
} else {
return hour < start && hour >= end;
}
}
// ๐
Schedule for later
private scheduleForLater(userId: string, notification: any): void {
const prefs = this.preferences.get(userId)!;
const now = new Date();
const hour = now.getHours();
const { end } = prefs.quietHours;
let scheduledHour = end;
if (hour < end) {
scheduledHour = end;
} else {
now.setDate(now.getDate() + 1);
scheduledHour = end;
}
const scheduled = new Date(now);
scheduled.setHours(scheduledHour, 0, 0, 0);
this.scheduled.set(`${userId}-${Date.now()}`, scheduled);
console.log(`๐
Scheduled notification for ${scheduled.toISOString()}`);
}
// ๐ Get notification stats
getStats(userId: string): {
sent: number;
scheduled: number;
preferences: UserPreferences | undefined;
} {
const scheduled = Array.from(this.scheduled.keys())
.filter(key => key.startsWith(userId)).length;
return {
sent: 0, // Would track this in real implementation
scheduled,
preferences: this.preferences.get(userId)
};
}
// Stub methods for sending
private async sendEmail(userId: string, notification: any): Promise<void> {
console.log(`๐ง Email sent to ${userId}: ${notification.title}`);
}
private async sendPush(userId: string, notification: any): Promise<void> {
console.log(`๐ Push sent to ${userId}: ${notification.title}`);
}
}
// ๐ฎ Test it out!
const notificationService = new TaskNotificationService();
// Set user preferences
notificationService['preferences'].set('user123', {
userId: 'user123',
email: true,
push: true,
sms: false,
quietHours: { start: 22, end: 8 },
frequency: 'instant'
});
// Send notification
await notificationService.notifyTaskAssigned({
id: 'task456',
title: 'Review TypeScript PR',
assigneeId: 'user123',
dueDate: new Date(),
priority: 'high',
emoji: '๐จโ๐ป'
}, 'Alice');
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Build notification systems with confidence ๐ช
- โ Implement multiple channels (email, push, SMS) ๐ฑ
- โ Handle queuing and retries like a pro ๐
- โ Respect user preferences and quiet hours ๐
- โ Create scalable architectures with TypeScript! ๐
Remember: Good notification systems enhance user experience without being annoying! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered notification systems!
Hereโs what to do next:
- ๐ป Build the exercise notification system
- ๐๏ธ Add real email/push providers (SendGrid, Firebase)
- ๐ Learn about notification analytics
- ๐ Implement A/B testing for notification content
Remember: Every great app has a thoughtful notification system. Keep building, keep notifying wisely! ๐
Happy coding! ๐๐โจ