npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skoolite/notify-hub

v0.2.0

Published

Multi-channel notification hub for SMS, WhatsApp, and Email

Downloads

188

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-hub

Quick 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