@tjoc/notifications
v3.0.0
Published
Notification service for The Journey of Code platform
Maintainers
Readme
@tjoc/notifications
A comprehensive notifications package that provides email and push notification capabilities with template support.
Features
📧 Email Service
- Resend integration for reliable email delivery
- HTML and plain text support
- Template-based emails with variable substitution
- Type-safe email configuration
📱 Push Notifications
- Firebase Cloud Messaging (FCM) integration
- Single and multi-device notifications
- Topic-based notifications
- Rich notifications with images and click actions
- Template support
📝 Template System
- Shared templates for both email and push notifications
- Variable substitution with type safety
- Template validation and error handling
- Support for HTML, text, and push notification formats
🎯 User Action Notifications
- Pre-built templates for common user actions (welcome emails, security alerts, etc.)
- Event-based notification system with conditions and scheduling
🤖 Automation Engine
- Advanced automation with rules, scheduling, frequency controls, and analytics
- Notification triggers with flexible conditions
- Built-in user action helpers for common scenarios
In-App Notifications & Repository Integration
This package supports in-app notifications by allowing you to provide a custom NotificationRepository implementation. Any time an email or push notification is sent, a copy is saved to the repository (if provided) for in-app use. You can also fetch and mark notifications as read for a user.
Repository Contract
Implement the following interface to persist notifications (e.g., in a database):
import type { Notification } from '@tjoc/notifications/notification.entity';
export interface NotificationRepository {
save(notification: Notification): Promise<void>;
findByUser(
userId: string,
options?: { limit?: number; offset?: number }
): Promise<Notification[]>;
markAsRead(notificationId: string): Promise<void>;
findById?(notificationId: string): Promise<Notification | null>;
}Usage Example
import {
NotificationService,
type NotificationRepository,
} from '@tjoc/notifications';
// Example in-memory repository (replace with your DB logic)
const repo: NotificationRepository = {
notifications: [],
async save(notification) {
this.notifications.push(notification);
},
async findByUser(userId, { limit = 20, offset = 0 } = {}) {
return this.notifications
.filter((n) => n.userId === userId)
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
.slice(offset, offset + limit);
},
async markAsRead(notificationId) {
const n = this.notifications.find((n) => n.id === notificationId);
if (n) {
n.status = 'read';
n.readAt = new Date();
}
},
async findById(notificationId) {
return this.notifications.find((n) => n.id === notificationId) || null;
},
};
const notifications = new NotificationService({
resendApiKey: 'your-resend-api-key',
firebaseCredentials: {
projectId: 'your-firebase-project-id',
clientEmail: 'your-firebase-client-email',
privateKey: 'your-firebase-private-key',
},
notificationRepository: repo,
});
// Send an email and persist as in-app notification
await notifications.sendEmail({
to: '[email protected]',
subject: 'Welcome!',
html: '<h1>Welcome!</h1>',
userId: 'user-123', // Required for in-app notification
});
// Send a push notification and persist as in-app notification
await notifications.sendPushNotification('device-token', {
title: 'Hello',
body: 'You have a new message!',
userId: 'user-123', // Required for in-app notification
});
// Fetch notifications for a user
const userNotifications = await notifications.getUserNotifications('user-123', {
limit: 10,
});
// Mark a notification as read
await notifications.markNotificationRead('notification-id');Installation
pnpm add @tjoc/notificationsQuick Start
import {
NotificationsModule,
NotificationTriggerService,
NotificationAutomationEngine,
createUserActionHelpers
} from '@tjoc/notifications';
// Initialize the module
const notificationsModule = new NotificationsModule();
const notificationService = await notificationsModule.init({
resend: {
apiKey: 'your-resend-api-key',
fromEmail: '[email protected]',
fromName: 'Your App'
},
firebase: {
projectId: 'your-project-id',
privateKey: 'your-private-key',
clientEmail: 'your-client-email'
}
});
// Initialize trigger system and automation engine
const triggerService = new NotificationTriggerService(notificationService);
const automationEngine = new NotificationAutomationEngine(
notificationService,
notificationService.getTemplatesService(),
triggerService
);
// Setup user action helpers
const userActionHelpers = createUserActionHelpers(triggerService, automationEngine);
userActionHelpers.setupDefaultUserActionTriggers();
await userActionHelpers.setupDefaultUserActionRules();
// Send a basic email notification
await notificationService.sendEmail({
to: '[email protected]',
subject: 'Welcome!',
html: '<h1>Welcome to our platform!</h1>',
text: 'Welcome to our platform!',
userId: 'user123'
});
// Send a push notification
await notificationService.sendPushNotification('device-token', {
title: 'Welcome!',
body: 'Welcome to our platform!',
userId: 'user123'
});
// Trigger user action notifications
await userActionHelpers.triggerEmailActivationWelcome('user123', {
name: 'John Doe',
email: '[email protected]',
loginDate: new Date(),
dashboardUrl: 'https://app.example.com/dashboard',
});Usage
Email Service
import { EmailService, TemplatesService } from '@tjoc/notifications';
// Initialize services
const templatesService = new TemplatesService();
const emailService = new EmailService(
{
apiKey: 'your-resend-api-key',
defaultFrom: '[email protected]',
defaultReplyTo: '[email protected]',
},
templatesService
);
// Send a direct email
await emailService.sendEmail({
to: '[email protected]',
subject: 'Welcome!',
html: '<h1>Welcome to our platform!</h1>',
text: 'Welcome to our platform!',
});
// Send using a template
await emailService.sendEmail({
to: '[email protected]',
templateId: 'welcome-email',
templateData: {
name: 'John Doe',
activationLink: 'https://example.com/activate',
},
});Push Notifications
import { PushService, TemplatesService } from '@tjoc/notifications';
// Initialize services
const templatesService = new TemplatesService();
const pushService = new PushService(
{
credentials: {
projectId: 'your-firebase-project-id',
clientEmail: 'your-firebase-client-email',
privateKey: 'your-firebase-private-key',
},
},
templatesService
);
// Send to single device
await pushService.sendToDevice('device-token', {
title: 'New Message',
body: 'You have a new message!',
imageUrl: 'https://example.com/image.jpg',
clickAction: 'https://example.com/messages',
});
// Send to multiple devices
await pushService.sendToDevices(['token1', 'token2'], {
title: 'New Update',
body: 'App version 2.0 is available!',
});
// Send using template
await pushService.sendToDevice('device-token', {
templateId: 'new-message',
templateData: {
sender: 'John',
messagePreview: 'Hello there!',
},
});
// Topic management
await pushService.subscribeToTopic(['token1', 'token2'], 'news');
await pushService.sendToTopic('news', {
title: 'Breaking News',
body: 'Check out our latest update!',
});Template Management
import { TemplatesService } from '@tjoc/notifications';
const templatesService = new TemplatesService();
// Add an email template
templatesService.addTemplate({
id: 'welcome-email',
name: 'Welcome Email',
type: 'email',
content: {
subject: 'Welcome {{name}}!',
body: 'Click here to activate: {{activationLink}}',
html: '<p>Click <a href="{{activationLink}}">here</a> to activate</p>',
},
variables: [
{ name: 'name', type: 'string', required: true },
{ name: 'activationLink', type: 'string', required: true },
],
});
// Add a push notification template
templatesService.addTemplate({
id: 'new-message',
name: 'New Message Notification',
type: 'push',
content: {
title: 'New message from {{sender}}',
body: '{{messagePreview}}',
},
variables: [
{ name: 'sender', type: 'string', required: true },
{ name: 'messagePreview', type: 'string', required: true },
],
});Configuration
Email Service Configuration
interface EmailConfig {
apiKey: string; // Resend API key
defaultFrom: string; // Default sender email
defaultReplyTo?: string; // Default reply-to email
}Push Service Configuration
interface PushConfig {
credentials: {
projectId: string; // Firebase project ID
clientEmail: string; // Firebase client email
privateKey: string; // Firebase private key
};
}User Action Notifications
The package includes pre-built templates and helpers for common user actions:
import { createUserActionHelpers, UserActionType } from '@tjoc/notifications';
// Setup user action helpers
const userActionHelpers = createUserActionHelpers(triggerService, automationEngine);
// Setup default triggers and automation rules
userActionHelpers.setupDefaultUserActionTriggers();
await userActionHelpers.setupDefaultUserActionRules();
// Trigger specific user action notifications
// 1. Email Activation & Welcome
await userActionHelpers.triggerEmailActivationWelcome('user123', {
name: 'John Doe',
email: '[email protected]',
loginDate: new Date(),
dashboardUrl: 'https://app.example.com/dashboard',
});
// 2. Resume Upload Welcome
await userActionHelpers.triggerResumeUploadWelcome('user123', {
name: 'Jane Smith',
uploadDate: new Date(),
resumeUrl: 'https://app.example.com/resume/123',
improvementUrl: 'https://app.example.com/improve',
});
// 3. New Device Login Alert
await userActionHelpers.triggerNewDeviceLoginAlert('user123', {
name: 'John Doe',
deviceInfo: 'Chrome on Windows 11',
location: 'New York, NY',
loginTime: new Date(),
securityUrl: 'https://app.example.com/security',
});
// 4. Password Changed Confirmation
await userActionHelpers.triggerPasswordChangedConfirmation('user123', {
name: 'John Doe',
changeTime: new Date(),
deviceInfo: 'Chrome on Windows 11',
securityUrl: 'https://app.example.com/security',
});Notification Triggers & Automation
Setting up Custom Triggers
import {
NotificationTriggerService,
UserActionType,
SystemEventType,
EngagementEventType
} from '@tjoc/notifications';
const triggerService = new NotificationTriggerService(notificationService);
// Register a custom trigger
triggerService.registerTrigger({
id: 'custom-welcome',
name: 'Custom Welcome Email',
description: 'Send welcome email with custom conditions',
eventType: UserActionType.EMAIL_ACTIVATION,
conditions: [
{
field: 'user.isFirstLogin',
operator: 'equals',
value: true
}
],
enabled: true,
templateId: 'welcome-template',
notificationTypes: ['email']
});
// Fire a trigger event
await triggerService.fireEvent(UserActionType.EMAIL_ACTIVATION, {
userId: 'user123',
user: { isFirstLogin: true, email: '[email protected]' },
metadata: { source: 'registration' }
});Automation Engine
import { NotificationAutomationEngine } from '@tjoc/notifications';
const automationEngine = new NotificationAutomationEngine(
notificationService,
templatesService,
triggerService
);
// Add automation rule
await automationEngine.addRule({
id: 'welcome-sequence',
name: 'Welcome Email Sequence',
description: 'Send welcome emails to new users',
triggerId: 'email-activation',
conditions: [
{
field: 'user.isFirstLogin',
operator: 'equals',
value: true
}
],
actions: [
{
type: 'send_notification',
templateId: 'email-activation-welcome',
notificationTypes: ['email'],
delay: 0
}
],
scheduling: {
timezone: 'UTC',
allowedHours: { start: 9, end: 17 },
allowedDays: [1, 2, 3, 4, 5] // Monday to Friday
},
frequency: {
maxPerDay: 1,
maxPerWeek: 3,
cooldownMinutes: 60
},
enabled: true
});
// Process automation rules
await automationEngine.processRules();
// Get analytics
const analytics = await automationEngine.getAnalytics('welcome-sequence', {
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31')
});Error Handling
All services use proper error handling and validation:
- Input validation using Zod schemas
- Type-safe template rendering
- Detailed error messages
- Error wrapping for third-party service errors
Testing
The package includes comprehensive tests:
# Run all tests
pnpm test
# Run with coverage
pnpm test:coverageLicense
MIT
