@skoolite/notify-hub 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,452 @@
1
+ # @skoolite/notify-hub
2
+
3
+ > **DISCLAIMER: INTERNAL USE PACKAGE**
4
+ >
5
+ > 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**.
6
+ >
7
+ > - **No Issues/Bug Fixes**: Issues and PRs will not be monitored or addressed
8
+ > - **Breaking Changes**: Expect frequent breaking changes without notice
9
+ > - **No SLA**: This is not production-ready software for general use
10
+ > - **Use at Your Own Risk**: If you choose to use this package, you are responsible for any consequences
11
+ >
12
+ > **If you need a production-ready notification library, consider alternatives like [Novu](https://novu.co), [Knock](https://knock.app), or direct provider SDKs.**
13
+
14
+ ---
15
+
16
+ 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.
17
+
18
+ ## Features
19
+
20
+ - **Multi-channel**: SMS, WhatsApp, and Email support
21
+ - **Multiple providers**: Twilio, Meta WhatsApp, local Pakistan SMS (Telenor, Jazz, Zong), AWS SES
22
+ - **Smart routing**: Route messages to different providers based on phone number prefix
23
+ - **Automatic fallback**: Fall back to secondary provider on failure
24
+ - **Retry logic**: Exponential backoff with jitter for transient errors
25
+ - **Bulk sending**: Send to many recipients with rate limiting
26
+ - **Type-safe**: Full TypeScript support with comprehensive types
27
+ - **Provider agnostic**: Switch providers without code changes
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install @skoolite/notify-hub
33
+ # or
34
+ pnpm add @skoolite/notify-hub
35
+ # or
36
+ yarn add @skoolite/notify-hub
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```typescript
42
+ import { NotifyHub } from '@skoolite/notify-hub';
43
+
44
+ // Initialize with Twilio
45
+ const notifyHub = new NotifyHub({
46
+ sms: {
47
+ primary: {
48
+ provider: 'twilio',
49
+ config: {
50
+ accountSid: process.env.TWILIO_ACCOUNT_SID!,
51
+ authToken: process.env.TWILIO_AUTH_TOKEN!,
52
+ fromNumber: '+1234567890',
53
+ },
54
+ },
55
+ },
56
+ });
57
+
58
+ // Send SMS
59
+ const result = await notifyHub.sms('+923001234567', 'Hello from NotifyHub!');
60
+
61
+ if (result.success) {
62
+ console.log('Message sent:', result.messageId);
63
+ } else {
64
+ console.error('Failed:', result.error?.message);
65
+ }
66
+ ```
67
+
68
+ ## Configuration
69
+
70
+ ### Full Configuration Example
71
+
72
+ ```typescript
73
+ import { NotifyHub } from '@skoolite/notify-hub';
74
+
75
+ const notifyHub = new NotifyHub({
76
+ // Default channel for notifications
77
+ defaultChannel: 'sms',
78
+
79
+ // SMS Configuration
80
+ sms: {
81
+ // Primary provider
82
+ primary: {
83
+ provider: 'twilio',
84
+ config: {
85
+ accountSid: process.env.TWILIO_ACCOUNT_SID!,
86
+ authToken: process.env.TWILIO_AUTH_TOKEN!,
87
+ fromNumber: '+1234567890',
88
+ },
89
+ },
90
+ // Fallback provider (optional)
91
+ fallback: {
92
+ provider: 'local',
93
+ config: {
94
+ provider: 'telenor',
95
+ apiKey: process.env.TELENOR_API_KEY!,
96
+ senderId: 'SKOOLYTE',
97
+ },
98
+ },
99
+ // Smart routing based on phone prefix
100
+ routing: {
101
+ '+92': 'fallback', // Pakistan -> local provider
102
+ '*': 'primary', // All others -> Twilio
103
+ },
104
+ },
105
+
106
+ // WhatsApp Configuration
107
+ whatsapp: {
108
+ provider: 'meta',
109
+ config: {
110
+ accessToken: process.env.META_ACCESS_TOKEN!,
111
+ phoneNumberId: process.env.WHATSAPP_PHONE_ID!,
112
+ businessAccountId: process.env.WHATSAPP_BUSINESS_ID,
113
+ },
114
+ },
115
+
116
+ // Email Configuration
117
+ email: {
118
+ provider: 'ses',
119
+ config: {
120
+ region: 'us-east-1',
121
+ fromAddress: 'noreply@example.com',
122
+ fromName: 'My App',
123
+ },
124
+ },
125
+
126
+ // Retry Configuration
127
+ retry: {
128
+ maxAttempts: 3,
129
+ backoffMs: 1000,
130
+ backoffMultiplier: 2,
131
+ maxBackoffMs: 30000,
132
+ jitter: true,
133
+ },
134
+
135
+ // Logger (optional)
136
+ logger: console, // or winston, pino, etc.
137
+ });
138
+ ```
139
+
140
+ ## Sending SMS
141
+
142
+ ### Simple SMS
143
+
144
+ ```typescript
145
+ // Send to a single recipient
146
+ const result = await notifyHub.sms('+923001234567', 'Your OTP is 123456');
147
+ ```
148
+
149
+ ### SMS with Options
150
+
151
+ ```typescript
152
+ const result = await notifyHub.sms('+923001234567', 'Hello!', {
153
+ from: '+1555000000', // Override from number
154
+ metadata: { userId: '123' }, // Custom metadata
155
+ });
156
+ ```
157
+
158
+ ### Bulk SMS
159
+
160
+ ```typescript
161
+ const messages = [
162
+ { to: '+923001234567', message: 'Hello John!' },
163
+ { to: '+923001234568', message: 'Hello Jane!' },
164
+ { to: '+923001234569', message: 'Hello Bob!' },
165
+ ];
166
+
167
+ const result = await notifyHub.bulkSms(messages, {
168
+ rateLimit: { messagesPerSecond: 10 },
169
+ concurrency: 5,
170
+ stopOnError: false,
171
+ onProgress: (sent, total) => {
172
+ console.log(`Progress: ${sent}/${total}`);
173
+ },
174
+ });
175
+
176
+ console.log(`Sent: ${result.summary.sent}, Failed: ${result.summary.failed}`);
177
+ ```
178
+
179
+ ### Get Delivery Status
180
+
181
+ ```typescript
182
+ const status = await notifyHub.getSmsStatus('SM1234567890');
183
+ // Returns: 'queued' | 'sent' | 'delivered' | 'failed' | 'undelivered'
184
+ ```
185
+
186
+ ## Sending WhatsApp Messages
187
+
188
+ ### Template Messages
189
+
190
+ Template messages can be sent anytime (no 24-hour window restriction):
191
+
192
+ ```typescript
193
+ const result = await notifyHub.whatsappTemplate('+923001234567', {
194
+ name: 'appointment_reminder',
195
+ language: 'en',
196
+ variables: {
197
+ '1': 'John',
198
+ '2': 'Dr. Smith',
199
+ '3': 'Tomorrow at 10 AM',
200
+ },
201
+ });
202
+ ```
203
+
204
+ ### Text Messages
205
+
206
+ Text messages require an open conversation (24-hour window):
207
+
208
+ ```typescript
209
+ const result = await notifyHub.whatsapp('+923001234567', 'Thank you for your message!');
210
+ ```
211
+
212
+ ## Sending Email
213
+
214
+ ```typescript
215
+ const result = await notifyHub.email('user@example.com', {
216
+ subject: 'Welcome!',
217
+ body: 'Plain text content',
218
+ html: '<h1>Welcome!</h1><p>HTML content</p>',
219
+ });
220
+ ```
221
+
222
+ ## Generic Send
223
+
224
+ Use the `send` method for channel-agnostic sending:
225
+
226
+ ```typescript
227
+ // SMS
228
+ await notifyHub.send({
229
+ channel: 'sms',
230
+ to: '+923001234567',
231
+ message: 'Hello!',
232
+ });
233
+
234
+ // WhatsApp Template
235
+ await notifyHub.send({
236
+ channel: 'whatsapp',
237
+ to: '+923001234567',
238
+ template: {
239
+ name: 'welcome',
240
+ language: 'en',
241
+ variables: ['John'],
242
+ },
243
+ });
244
+
245
+ // Email
246
+ await notifyHub.send({
247
+ channel: 'email',
248
+ to: 'user@example.com',
249
+ email: {
250
+ subject: 'Hello',
251
+ body: 'Hello!',
252
+ },
253
+ });
254
+ ```
255
+
256
+ ## Smart Routing
257
+
258
+ Route messages to different providers based on phone number prefix:
259
+
260
+ ```typescript
261
+ const notifyHub = new NotifyHub({
262
+ sms: {
263
+ primary: { provider: 'twilio', config: { ... } },
264
+ fallback: { provider: 'local', config: { provider: 'telenor', ... } },
265
+ routing: {
266
+ '+92': 'fallback', // Pakistan numbers -> local provider (cheaper)
267
+ '+1': 'primary', // US numbers -> Twilio
268
+ '*': 'primary', // Default -> Twilio
269
+ },
270
+ },
271
+ });
272
+
273
+ // This uses local provider (Pakistan number)
274
+ await notifyHub.sms('+923001234567', 'Hello!');
275
+
276
+ // This uses Twilio (US number)
277
+ await notifyHub.sms('+14155551234', 'Hello!');
278
+ ```
279
+
280
+ ## Automatic Fallback
281
+
282
+ If the primary provider fails with a retriable error, NotifyHub automatically tries the fallback:
283
+
284
+ ```typescript
285
+ const notifyHub = new NotifyHub({
286
+ sms: {
287
+ primary: { provider: 'local', config: { ... } },
288
+ fallback: { provider: 'twilio', config: { ... } },
289
+ },
290
+ });
291
+
292
+ // If local provider fails, automatically retries with Twilio
293
+ await notifyHub.sms('+923001234567', 'Hello!');
294
+ ```
295
+
296
+ ## Custom Providers
297
+
298
+ Implement your own provider:
299
+
300
+ ```typescript
301
+ import { SmsProvider, SendResult } from '@skoolite/notify-hub';
302
+
303
+ class MySmsProvider implements SmsProvider {
304
+ readonly name = 'my-provider';
305
+
306
+ async send(to: string, message: string): Promise<SendResult> {
307
+ // Your implementation
308
+ return {
309
+ success: true,
310
+ messageId: 'my-msg-id',
311
+ channel: 'sms',
312
+ status: 'sent',
313
+ provider: this.name,
314
+ timestamp: new Date(),
315
+ };
316
+ }
317
+
318
+ async getStatus(messageId: string): Promise<MessageStatus> {
319
+ // Your implementation
320
+ return 'delivered';
321
+ }
322
+ }
323
+
324
+ const notifyHub = new NotifyHub({
325
+ sms: {
326
+ primary: {
327
+ provider: 'custom',
328
+ instance: new MySmsProvider(),
329
+ },
330
+ },
331
+ });
332
+ ```
333
+
334
+ ## NestJS Integration
335
+
336
+ ```typescript
337
+ // notify-hub.module.ts
338
+ import { Module, Global } from '@nestjs/common';
339
+ import { ConfigService } from '@nestjs/config';
340
+ import { NotifyHub } from '@skoolite/notify-hub';
341
+
342
+ @Global()
343
+ @Module({
344
+ providers: [
345
+ {
346
+ provide: NotifyHub,
347
+ useFactory: (config: ConfigService) => {
348
+ return new NotifyHub({
349
+ sms: {
350
+ primary: {
351
+ provider: 'twilio',
352
+ config: {
353
+ accountSid: config.getOrThrow('TWILIO_ACCOUNT_SID'),
354
+ authToken: config.getOrThrow('TWILIO_AUTH_TOKEN'),
355
+ fromNumber: config.getOrThrow('TWILIO_FROM_NUMBER'),
356
+ },
357
+ },
358
+ },
359
+ });
360
+ },
361
+ inject: [ConfigService],
362
+ },
363
+ ],
364
+ exports: [NotifyHub],
365
+ })
366
+ export class NotifyHubModule {}
367
+
368
+ // appointment.service.ts
369
+ import { Injectable } from '@nestjs/common';
370
+ import { NotifyHub } from '@skoolite/notify-hub';
371
+
372
+ @Injectable()
373
+ export class AppointmentService {
374
+ constructor(private readonly notifyHub: NotifyHub) {}
375
+
376
+ async sendReminder(phone: string, message: string) {
377
+ return this.notifyHub.sms(phone, message);
378
+ }
379
+ }
380
+ ```
381
+
382
+ ## Utility Functions
383
+
384
+ NotifyHub exports useful utility functions:
385
+
386
+ ```typescript
387
+ import {
388
+ validatePhoneNumber,
389
+ toE164,
390
+ isPakistanMobile,
391
+ getPakistanNetwork,
392
+ withRetry,
393
+ } from '@skoolite/notify-hub';
394
+
395
+ // Validate and parse phone number
396
+ const result = validatePhoneNumber('03001234567', 'PK');
397
+ console.log(result.e164); // '+923001234567'
398
+
399
+ // Convert to E.164 format
400
+ const e164 = toE164('0300-1234567', 'PK');
401
+ console.log(e164); // '+923001234567'
402
+
403
+ // Check if Pakistan mobile
404
+ console.log(isPakistanMobile('+923001234567')); // true
405
+
406
+ // Get Pakistan network
407
+ console.log(getPakistanNetwork('+923451234567')); // 'telenor'
408
+
409
+ // Retry with exponential backoff
410
+ const data = await withRetry(
411
+ () => fetchData(),
412
+ { maxAttempts: 3, backoffMs: 1000, backoffMultiplier: 2, jitter: true }
413
+ );
414
+ ```
415
+
416
+ ## API Reference
417
+
418
+ ### NotifyHub
419
+
420
+ | Method | Description |
421
+ |--------|-------------|
422
+ | `sms(to, message, options?)` | Send SMS |
423
+ | `bulkSms(messages, options?)` | Send bulk SMS |
424
+ | `getSmsStatus(messageId)` | Get SMS delivery status |
425
+ | `whatsapp(to, message, options?)` | Send WhatsApp text message |
426
+ | `whatsappTemplate(to, template, options?)` | Send WhatsApp template |
427
+ | `bulkWhatsApp(messages, options?)` | Send bulk WhatsApp |
428
+ | `email(to, email, options?)` | Send email |
429
+ | `bulkEmail(messages, options?)` | Send bulk email |
430
+ | `send(notification)` | Generic send |
431
+ | `initialize()` | Initialize providers (auto-called) |
432
+ | `destroy()` | Cleanup resources |
433
+
434
+ ### SendResult
435
+
436
+ ```typescript
437
+ interface SendResult {
438
+ success: boolean;
439
+ messageId?: string;
440
+ channel: 'sms' | 'whatsapp' | 'email';
441
+ status: 'queued' | 'sent' | 'delivered' | 'failed' | 'undelivered';
442
+ provider: string;
443
+ error?: { code: string; message: string; retriable: boolean };
444
+ cost?: { amount: number; currency: string };
445
+ timestamp: Date;
446
+ metadata?: Record<string, unknown>;
447
+ }
448
+ ```
449
+
450
+ ## License
451
+
452
+ MIT