@skoolite/notify-hub
v0.2.0
Published
Multi-channel notification hub for SMS, WhatsApp, and Email
Downloads
188
Maintainers
Readme
@skoolite/notify-hub
DISCLAIMER: INTERNAL USE PACKAGE
This package is built for internal use in Skoolyte projects. It is published publicly for convenience but comes with NO SUPPORT, NO WARRANTY, and NO GUARANTEES.
- No Issues/Bug Fixes: Issues and PRs will not be monitored or addressed
- Breaking Changes: Expect frequent breaking changes without notice
- No SLA: This is not production-ready software for general use
- Use at Your Own Risk: If you choose to use this package, you are responsible for any consequences
If you need a production-ready notification library, consider alternatives like Novu, Knock, or direct provider SDKs.
Multi-channel notification hub for SMS, WhatsApp, and Email. Send notifications through Twilio, Meta WhatsApp Cloud API, local Pakistan SMS providers, and AWS SES with a unified API.
Features
- Multi-channel: SMS, WhatsApp, and Email support
- Multiple providers: Twilio, Meta WhatsApp, local Pakistan SMS (Telenor, Jazz, Zong), AWS SES
- Smart routing: Route messages to different providers based on phone number prefix
- Automatic fallback: Fall back to secondary provider on failure
- Retry logic: Exponential backoff with jitter for transient errors
- Bulk sending: Send to many recipients with rate limiting
- Type-safe: Full TypeScript support with comprehensive types
- Provider agnostic: Switch providers without code changes
Installation
npm install @skoolite/notify-hub
# or
pnpm add @skoolite/notify-hub
# or
yarn add @skoolite/notify-hubQuick Start
import { NotifyHub } from '@skoolite/notify-hub';
// Initialize with Twilio
const notifyHub = new NotifyHub({
sms: {
primary: {
provider: 'twilio',
config: {
accountSid: process.env.TWILIO_ACCOUNT_SID!,
authToken: process.env.TWILIO_AUTH_TOKEN!,
fromNumber: '+1234567890',
},
},
},
});
// Send SMS
const result = await notifyHub.sms('+923001234567', 'Hello from NotifyHub!');
if (result.success) {
console.log('Message sent:', result.messageId);
} else {
console.error('Failed:', result.error?.message);
}Configuration
Full Configuration Example
import { NotifyHub } from '@skoolite/notify-hub';
const notifyHub = new NotifyHub({
// Default channel for notifications
defaultChannel: 'sms',
// SMS Configuration
sms: {
// Primary provider
primary: {
provider: 'twilio',
config: {
accountSid: process.env.TWILIO_ACCOUNT_SID!,
authToken: process.env.TWILIO_AUTH_TOKEN!,
fromNumber: '+1234567890',
},
},
// Fallback provider (optional)
fallback: {
provider: 'local',
config: {
provider: 'telenor',
apiKey: process.env.TELENOR_API_KEY!,
senderId: 'SKOOLYTE',
},
},
// Smart routing based on phone prefix
routing: {
'+92': 'fallback', // Pakistan -> local provider
'*': 'primary', // All others -> Twilio
},
},
// WhatsApp Configuration
whatsapp: {
provider: 'meta',
config: {
accessToken: process.env.META_ACCESS_TOKEN!,
phoneNumberId: process.env.WHATSAPP_PHONE_ID!,
businessAccountId: process.env.WHATSAPP_BUSINESS_ID,
},
},
// Email Configuration
email: {
provider: 'ses',
config: {
region: 'us-east-1',
fromAddress: '[email protected]',
fromName: 'My App',
},
},
// Retry Configuration
retry: {
maxAttempts: 3,
backoffMs: 1000,
backoffMultiplier: 2,
maxBackoffMs: 30000,
jitter: true,
},
// Logger (optional)
logger: console, // or winston, pino, etc.
});Sending SMS
Simple SMS
// Send to a single recipient
const result = await notifyHub.sms('+923001234567', 'Your OTP is 123456');SMS with Options
const result = await notifyHub.sms('+923001234567', 'Hello!', {
from: '+1555000000', // Override from number
metadata: { userId: '123' }, // Custom metadata
});Bulk SMS
const messages = [
{ to: '+923001234567', message: 'Hello John!' },
{ to: '+923001234568', message: 'Hello Jane!' },
{ to: '+923001234569', message: 'Hello Bob!' },
];
const result = await notifyHub.bulkSms(messages, {
rateLimit: { messagesPerSecond: 10 },
concurrency: 5,
stopOnError: false,
onProgress: (sent, total) => {
console.log(`Progress: ${sent}/${total}`);
},
});
console.log(`Sent: ${result.summary.sent}, Failed: ${result.summary.failed}`);Get Delivery Status
const status = await notifyHub.getSmsStatus('SM1234567890');
// Returns: 'queued' | 'sent' | 'delivered' | 'failed' | 'undelivered'Sending WhatsApp Messages
Template Messages
Template messages can be sent anytime (no 24-hour window restriction):
const result = await notifyHub.whatsappTemplate('+923001234567', {
name: 'appointment_reminder',
language: 'en',
variables: {
'1': 'John',
'2': 'Dr. Smith',
'3': 'Tomorrow at 10 AM',
},
});Text Messages
Text messages require an open conversation (24-hour window):
const result = await notifyHub.whatsapp('+923001234567', 'Thank you for your message!');Sending Email
const result = await notifyHub.email('[email protected]', {
subject: 'Welcome!',
body: 'Plain text content',
html: '<h1>Welcome!</h1><p>HTML content</p>',
});Generic Send
Use the send method for channel-agnostic sending:
// SMS
await notifyHub.send({
channel: 'sms',
to: '+923001234567',
message: 'Hello!',
});
// WhatsApp Template
await notifyHub.send({
channel: 'whatsapp',
to: '+923001234567',
template: {
name: 'welcome',
language: 'en',
variables: ['John'],
},
});
// Email
await notifyHub.send({
channel: 'email',
to: '[email protected]',
email: {
subject: 'Hello',
body: 'Hello!',
},
});Smart Routing
Route messages to different providers based on phone number prefix:
const notifyHub = new NotifyHub({
sms: {
primary: { provider: 'twilio', config: { ... } },
fallback: { provider: 'local', config: { provider: 'telenor', ... } },
routing: {
'+92': 'fallback', // Pakistan numbers -> local provider (cheaper)
'+1': 'primary', // US numbers -> Twilio
'*': 'primary', // Default -> Twilio
},
},
});
// This uses local provider (Pakistan number)
await notifyHub.sms('+923001234567', 'Hello!');
// This uses Twilio (US number)
await notifyHub.sms('+14155551234', 'Hello!');Automatic Fallback
If the primary provider fails with a retriable error, NotifyHub automatically tries the fallback:
const notifyHub = new NotifyHub({
sms: {
primary: { provider: 'local', config: { ... } },
fallback: { provider: 'twilio', config: { ... } },
},
});
// If local provider fails, automatically retries with Twilio
await notifyHub.sms('+923001234567', 'Hello!');Custom Providers
Implement your own provider:
import { SmsProvider, SendResult } from '@skoolite/notify-hub';
class MySmsProvider implements SmsProvider {
readonly name = 'my-provider';
async send(to: string, message: string): Promise<SendResult> {
// Your implementation
return {
success: true,
messageId: 'my-msg-id',
channel: 'sms',
status: 'sent',
provider: this.name,
timestamp: new Date(),
};
}
async getStatus(messageId: string): Promise<MessageStatus> {
// Your implementation
return 'delivered';
}
}
const notifyHub = new NotifyHub({
sms: {
primary: {
provider: 'custom',
instance: new MySmsProvider(),
},
},
});NestJS Integration
// notify-hub.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NotifyHub } from '@skoolite/notify-hub';
@Global()
@Module({
providers: [
{
provide: NotifyHub,
useFactory: (config: ConfigService) => {
return new NotifyHub({
sms: {
primary: {
provider: 'twilio',
config: {
accountSid: config.getOrThrow('TWILIO_ACCOUNT_SID'),
authToken: config.getOrThrow('TWILIO_AUTH_TOKEN'),
fromNumber: config.getOrThrow('TWILIO_FROM_NUMBER'),
},
},
},
});
},
inject: [ConfigService],
},
],
exports: [NotifyHub],
})
export class NotifyHubModule {}
// appointment.service.ts
import { Injectable } from '@nestjs/common';
import { NotifyHub } from '@skoolite/notify-hub';
@Injectable()
export class AppointmentService {
constructor(private readonly notifyHub: NotifyHub) {}
async sendReminder(phone: string, message: string) {
return this.notifyHub.sms(phone, message);
}
}Utility Functions
NotifyHub exports useful utility functions:
import {
validatePhoneNumber,
toE164,
isPakistanMobile,
getPakistanNetwork,
withRetry,
} from '@skoolite/notify-hub';
// Validate and parse phone number
const result = validatePhoneNumber('03001234567', 'PK');
console.log(result.e164); // '+923001234567'
// Convert to E.164 format
const e164 = toE164('0300-1234567', 'PK');
console.log(e164); // '+923001234567'
// Check if Pakistan mobile
console.log(isPakistanMobile('+923001234567')); // true
// Get Pakistan network
console.log(getPakistanNetwork('+923451234567')); // 'telenor'
// Retry with exponential backoff
const data = await withRetry(
() => fetchData(),
{ maxAttempts: 3, backoffMs: 1000, backoffMultiplier: 2, jitter: true }
);API Reference
NotifyHub
| Method | Description |
|--------|-------------|
| sms(to, message, options?) | Send SMS |
| bulkSms(messages, options?) | Send bulk SMS |
| getSmsStatus(messageId) | Get SMS delivery status |
| whatsapp(to, message, options?) | Send WhatsApp text message |
| whatsappTemplate(to, template, options?) | Send WhatsApp template |
| bulkWhatsApp(messages, options?) | Send bulk WhatsApp |
| email(to, email, options?) | Send email |
| bulkEmail(messages, options?) | Send bulk email |
| send(notification) | Generic send |
| initialize() | Initialize providers (auto-called) |
| destroy() | Cleanup resources |
SendResult
interface SendResult {
success: boolean;
messageId?: string;
channel: 'sms' | 'whatsapp' | 'email';
status: 'queued' | 'sent' | 'delivered' | 'failed' | 'undelivered';
provider: string;
error?: { code: string; message: string; retriable: boolean };
cost?: { amount: number; currency: string };
timestamp: Date;
metadata?: Record<string, unknown>;
}License
MIT
