Notifications Module
The Notifications module is a comprehensive push notification and in-app messaging system for the Comdeall platform that manages the complete lifecycle of notifications including Firebase Cloud Messaging (FCM) integration, device token management, template-based notifications, bulk messaging, failed token cleanup, and background job processing. It provides real-time communication capabilities across mobile and web platforms with sophisticated retry mechanisms and delivery tracking.
Table of Contents
- Module Structure
- Notification Endpoints
- Core Features
- Notification Types & Workflows
- Firebase Integration
- Background Job Processing
- Device Token Management
- Template System
- Security & Authorization
- Technical Implementation
Module Structure
@Module({
imports: [DBModule, forwardRef(() => BackgroundModule)],
controllers: [NotificationsController],
providers: [
NotificationsService,
{
provide: 'FirebaseProvider',
useClass: FirebaseProvider,
},
],
exports: [NotificationsService],
})
export class NotificationsModule {}
Architecture Components
- NotificationsController - REST API endpoints (
/api/communication) - NotificationsService - Business logic and workflow orchestration
- FirebaseProvider - Firebase Cloud Messaging integration
- NotificationsDBService - Database operations and token management
- Background Jobs - Async notification processing and retry logic
- Template Engine - Pre-defined notification templates
Notification Endpoints
| Method | Endpoint | Auth | Roles | Description |
|---|---|---|---|---|
| POST | /send-notification | JWT | ADMIN | Send template-based notifications |
| POST | /send-single-notification | JWT | ADMIN | Send notification to single device |
| POST | /send-custom-notification | JWT | ADMIN | Send custom notifications to multiple users |
Core Features
1. Multi-channel Notifications
- Push Notifications - Firebase Cloud Messaging for mobile and web
- In-app Notifications - Database-stored notifications for app inbox
- Template System - Pre-defined notification templates
- Custom Notifications - Ad-hoc notifications with dynamic content
2. Device Management
- Token Registration - Automatic device token registration and updates
- Token Cleanup - Automatic removal of expired and invalid tokens
- Multi-device Support - Users can have multiple registered devices
- Platform Support - iOS, Android, and web push notifications
3. Delivery Assurance
- Retry Mechanisms - Automatic retry for failed deliveries
- Error Handling - Comprehensive error classification and handling
- Delivery Tracking - Track successful and failed notification deliveries
- Dead Letter Queue - Failed jobs moved to DLQ for analysis
4. Background Processing
- Async Delivery - Non-blocking notification processing
- Batch Processing - Efficient bulk notification handling
- Queue Management - Redis-backed job queue with concurrency control
- Rate Limiting - Prevents API rate limit violations
Notification Types & Workflows
Template-based Notifications
Admin selects template → Users selected → Device tokens fetched → Firebase delivery → Database logging → Cleanup failed tokens
Use Cases:
- System announcements
- Feature updates
- Promotional messages
- Scheduled reminders
Custom Notifications
Custom content created → Target users identified → In-app notification saved → Push notification sent → Response tracking
Use Cases:
- Assessment assignments
- Appointment reminders
- Progress updates
- Personalized messages
Single Device Notifications
Device token provided → Direct Firebase call → Immediate response → Error handling
Use Cases:
- Testing and debugging
- Direct device targeting
- Emergency notifications
- Development testing
Automated System Notifications
System event triggered → Background job queued → Notification template applied → Multi-channel delivery → Audit logging
Use Cases:
- User inactivity reminders
- Appointment confirmations
- Payment confirmations
- System maintenance alerts
Firebase Integration
Firebase Cloud Messaging Setup
// Firebase configuration
{
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')
}
Single Notification Delivery
async sendSingleNotification(payload: NotificationPayload, token: string): Promise<string> {
const response = await this.messaging.send({
notification: {
title: payload.subject,
body: payload.description,
},
token,
});
// Error handling and logging
return response;
}
Bulk Notification Delivery
async sendBulkNotification(payload: MulticastNotificationPayload, tokens: string[]): Promise<BatchResponse> {
const response = await this.messaging.sendEachForMulticast({
data: {
screen_type: payload.custom_data?.screen_type || ScreenTypeEnum.HOME_SCREEN,
},
notification: {
title: payload.subject,
body: payload.description,
},
tokens,
});
// Process failed tokens and responses
return response;
}
Error Handling & Token Cleanup
// Automatic cleanup of invalid tokens
if (notificationResponse.failureCount > 0) {
const expiredTokens = notificationResponse.responses
.filter(response => {
return response.error?.code === 'messaging/invalid-registration-token' ||
response.error?.code === 'messaging/registration-token-not-registered';
})
.map((_, index) => tokens[index]);
await this.removeDeviceTokens(expiredTokens);
}
Topic-based Notifications
async sendNotificationToTopic(topic: string, payload: NotificationPayload) {
const response = await this.messaging.send({
notification: {
title: payload.subject,
body: payload.description,
},
topic: topic,
});
return response;
}
Background Job Processing
Job Queue Configuration
@Processor(QueueName.NOTIFICATION, {
concurrency: 2, // Process 2 jobs simultaneously
drainDelay: 300, // Wait time before shutdown
stalledInterval: 300000, // 5 minutes stalled timeout
maxStalledCount: 3, // Max stalled attempts
limiter: {
max: 10, // Max 10 jobs
duration: 150, // Per 150ms
},
})
Job Types & Processing
Custom Notification Job
case JobName.SEND_CUSTOM_NOTIFICATION:
await this.notificationQueueService.sendCustomNotification({
user_ids: ['user-uuid'],
subject: 'New Assessment Available',
description: 'A new assessment has been assigned',
custom_data: { screen_type: 'ASSESSMENT_SCREEN' }
});
break;
Template Notification Job
case JobName.NOTIFICATION_SEND:
await this.notificationQueueService.sendNotification({
user_ids: ['user-uuid'],
notification_template_ids: ['template-uuid']
});
break;
Notification Logging Job
case JobName.SEND_NOTIFICATION_LOG:
await this.notificationQueueService.sendNotificationLog({
notification_template_id: 'template-uuid',
sent_at: new Date(),
user_ids: ['user-uuid'],
sent_by: 'admin-uuid'
});
break;
Error Handling & Recovery
@OnWorkerEvent('failed')
async onFailed(job: Job) {
// Log failure details
this.logger.error(`Job ${job.id} failed: ${job.failedReason}`);
// Move to Dead Letter Queue for analysis
await this.dlqService.addFailedJobToDLQ({
originalQueueName: QueueName.NOTIFICATION,
originalJobId: job.id,
originalJobData: job.data,
failedReason: job.failedReason,
timestamp: Date.now(),
});
}
Device Token Management
Token Registration Process
App Install/Login → FCM Token Generated → Token Sent to Backend → Database Storage → Token Validation → Ready for Notifications
Token Lifecycle Management
// Token storage with user association
{
user_id: "user-uuid",
device_token: "fcm-token-string",
platform: "iOS|Android|Web",
app_version: "1.0.0",
created_at: "timestamp",
last_used: "timestamp"
}
Automatic Token Cleanup
// Remove expired and invalid tokens
async removeDeviceTokens(tokens: string[]): Promise<void> {
// Delete from database
await this.notificationsDBService.deleteDeviceTokens(tokens);
// Log cleanup activity
this.logger.debug(`Removed ${tokens.length} expired device tokens`);
}
Token Validation Logic
- Registration Token Not Registered - Token removed from database
- Invalid Registration Token - Token marked as invalid and removed
- Message Rate Exceeded - Temporary backoff applied
- Server Unavailable - Retry with exponential backoff
Template System
Notification Templates
Predefined notification templates stored in database with variables support:
// Template structure
{
id: "template-uuid",
subject: "New Assessment Available",
description: "A new {{assessment_type}} assessment has been assigned to {{child_name}}",
deep_link: "ASSESSMENT_SCREEN",
status: "ACTIVE",
variables: ["assessment_type", "child_name"]
}
Template Categories
- System Notifications - App updates, maintenance alerts
- Assessment Notifications - New assessments, completion reminders
- Appointment Notifications - Booking confirmations, reminders
- Progress Notifications - Milestone achievements, reports
- Marketing Notifications - Feature announcements, promotions
Variable Substitution
// Template with variables
const template = "Hello {{parent_name}}, {{child_name}} has completed the {{assessment_name}} assessment";
// Runtime substitution
const notification = template
.replace('{{parent_name}}', user.name)
.replace('{{child_name}}', child.name)
.replace('{{assessment_name}}', assessment.name);
Security & Authorization
Access Control
| Operation | ADMIN | THERAPIST | PARENT | PUBLIC |
|---|---|---|---|---|
| Send Template Notifications | ✓ | ✗ | ✗ | ✗ |
| Send Custom Notifications | ✓ | ✗ | ✗ | ✗ |
| Send Single Notifications | ✓ | ✗ | ✗ | ✗ |
| View Notification Logs | ✓ | ✗ | ✗ | ✗ |
| Manage Templates | ✓ | ✗ | ✗ | ✗ |
Firebase Security
- Service Account Authentication - Server-to-server authentication
- Token Validation - Device token validation before sending
- Rate Limiting - Prevents abuse and API quota violations
- Error Logging - Comprehensive error logging for security monitoring
Data Protection
- Token Encryption - Device tokens encrypted at rest
- Content Filtering - Notification content validated and sanitized
- User Isolation - Users can only receive notifications intended for them
- Audit Trails - All notification activities logged for compliance
Technical Implementation
Service Architecture
@Injectable()
export class NotificationsService {
constructor(
@Inject('FirebaseProvider') private readonly firebaseProvider: FirebaseProvider,
private readonly notificationsDBService: NotificationsDBService,
private readonly backgroundServiceManager: BackgroundServiceManager
) {}
async sendCustomNotification(data: MulticastNotificationPayload, user_ids: string[]) {
// Fetch device tokens for target users
const tokens = await this.getDeviceTokens(user_ids);
// Save in-app notifications to database
await this.saveUserNotification(notificationEntries);
// Send push notifications via Firebase
if (tokens.length > 0) {
const response = await this.firebaseProvider.sendBulkNotification(data, tokens);
// Handle failed tokens
await this.handleFailedTokens(response, tokens);
}
}
}
Firebase Provider Implementation
@Injectable()
export class FirebaseProvider implements NotificationProvider {
private readonly messaging: admin.messaging.Messaging;
constructor(private readonly configService: ConfigService<EnvConfig>) {
// Initialize Firebase Admin SDK
this.firebaseAdmin = admin.initializeApp({
credential: admin.credential.cert({
projectId: this.configService.get('FIREBASE_PROJECT_ID'),
clientEmail: this.configService.get('FIREBASE_CLIENT_EMAIL'),
privateKey: this.configService.get('FIREBASE_PRIVATE_KEY'),
}),
});
this.messaging = getMessaging();
}
}
Background Job Integration
// Queue notification jobs for async processing
await this.backgroundServiceManager.addSendCustomNotificationJob(JobName.SEND_CUSTOM_NOTIFICATION, {
user_ids: targetUsers,
subject: 'Assessment Completed',
description: `${childName} has completed the ${assessmentName} assessment`,
custom_data: {
screen_type: 'ASSESSMENT_RESULTS',
assessment_id: assessmentId
}
});
Error Handling Strategy
// Comprehensive error handling
try {
const response = await this.firebaseProvider.sendBulkNotification(payload, tokens);
// Process delivery results
if (response.failureCount > 0) {
const failedTokens = this.extractFailedTokens(response);
await this.removeDeviceTokens(failedTokens);
}
} catch (error) {
// Log error details
this.logger.error(`Notification delivery failed: ${error.message}`);
// Queue for retry if applicable
if (this.isRetryableError(error)) {
await this.queueForRetry(payload, tokens);
}
throw error;
}
Performance Optimizations
- Batch Processing - Send notifications in batches to optimize Firebase API usage
- Token Caching - Cache frequently used device tokens
- Async Processing - Use background jobs for non-critical notification delivery
- Connection Pooling - Reuse Firebase connections for efficiency
- Rate Limiting - Implement rate limiting to prevent API quota exhaustion
Monitoring & Analytics
- Delivery Metrics - Track success/failure rates for notifications
- Performance Monitoring - Monitor delivery times and error rates
- User Engagement - Track notification open rates and user interactions
- System Health - Monitor queue depths and processing times
This Notifications module provides a robust, scalable push notification system that ensures reliable message delivery across the Comdeall platform, with comprehensive error handling, automatic token management, and efficient background processing for optimal user experience.