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

devclassic-notify

v1.0.2

Published

Enterprise Multi-Channel Notification Engine (Email, SMS, WhatsApp, Push) for React, Next.js, and Node.js by devclassic.

Readme

devclassic-notify

Enterprise Multi-Channel Notification Engine for Modern Web & Mobile Applications

npm version License: MIT TypeScript Dual ESM/CJS Zero Heavy SDKs

Unify Email, SMS, WhatsApp, and Push notifications under a single, elegant API with automatic failover and seamless frontend/backend integration.


💡 Use It Your Way (Standalone or Full-Stack)

devclassic-notify is completely modular. You can use it only on the backend, only on the frontend, or both together:

| Usage Mode | Where It Runs | What You Use | Do You Need React? | | :--- | :--- | :--- | :--- | | 🖥️ Backend-Only | Node.js, Express, NestJS, Fastify, Serverless | createNotificationEngine, SMS, Email, WhatsApp, Push providers | No (React is an optional peer dependency) | | 🎨 Frontend-Only | React, Vite, Next.js client, CRA | <NotificationBell />, <NotificationPreferences />, useWebPush() | Yes (Connects to any custom API: Python, Go, PHP, etc.) | | ⚡ Full-Stack | Next.js (App Router), Remix | Backend dispatching + Frontend In-App notification center | Yes (Unified end-to-end) |


📖 Table of Contents


✨ Core Features

  • 🌐 Unified Multi-Channel API: Dispatch across Email, SMS, WhatsApp, and Push through one function call.
  • 🛡️ Zero-Drop Failover: Automatically retries secondary providers if your primary provider encounters rate limits or 5xx outages.
  • 🌍 African & Global Providers: Zero-heavy-SDK HTTP adapters for Gmail, Termii (Nigeria/Africa SMS & WhatsApp), Twilio, Resend, SendGrid, Meta Cloud API, FCM, and Expo.
  • 🎨 Ready-to-Use React UI: Drop-in <NotificationBell />, <NotificationPreferences /> settings card, and useWebPush() browser permission helper.
  • 📝 Smart Templating: Dot-notation nested variable interpolation ({{user.name}}, {{order.id}}).
  • 👤 Channel Preferences: Automatically respects recipient channel opt-outs (e.g. user toggles OFF SMS but keeps WhatsApp ON).
  • 🧪 Offline Mocking: Built-in test adapters for CI/CD and local development without spending real API credits.
  • 📦 Dual Build: CommonJS (.cjs) + ESM (.mjs) + Full TypeScript definitions (.d.ts).

📦 Installation

npm install devclassic-notify
# or
yarn add devclassic-notify
# or
pnpm add devclassic-notify

Note on React: If you are using devclassic-notify exclusively on the backend (Node.js/Express), you do not need to install React. React is defined as an optional peer dependency.


🚀 Quickstart by Use Case

Scenario A: Backend-Only (Node.js / Express / APIs)

No React required. Works with Express, NestJS, Fastify, AWS Lambda, etc.

// notify.ts
import {
  createNotificationEngine,
  resendProvider,
  termiiSMSProvider,
  metaWhatsAppProvider,
  expoPushProvider,
} from "devclassic-notify";

// 1. Initialize the engine with your providers
export const notify = createNotificationEngine({
  channels: {
    email: resendProvider({
      apiKey: process.env.RESEND_API_KEY!,
      from: "Alerts <[email protected]>",
    }),
    sms: termiiSMSProvider({
      apiKey: process.env.TERMII_API_KEY!,
      from: "YOURAPP", // Approved Sender ID
    }),
    whatsapp: metaWhatsAppProvider({
      accessToken: process.env.META_WA_TOKEN!,
      phoneNumberId: process.env.META_WA_PHONE_ID!,
    }),
    push: expoPushProvider(),
  },
});

// 2. Dispatch to one or multiple channels simultaneously
const result = await notify.send({
  to: {
    email: "[email protected]",
    phone: "+2348012345678",
    whatsapp: "+2348012345678",
    pushToken: "ExponentPushToken[xxxxxxxxxxxxxx]",
  },
  channels: ["email", "sms", "whatsapp", "push"],
  subject: "Order Confirmation #8491",
  content: "Hello {{name}}, your order for {{item}} has been confirmed!",
  data: { name: "Amina", item: "Solar Generator" },
});

console.log("Success:", result.success);
console.log("Channel results:", result.results);

Scenario B: Frontend-Only (React / Vite / Single Page Apps)

Use only the UI components and push hook in your React app, connecting to ANY backend (Django, Laravel, Rails, Go, etc.).

// App.tsx
import React from "react";
import {
  NotificationProvider,
  NotificationBell,
  NotificationPreferences,
  useWebPush,
} from "devclassic-notify";

export function App() {
  // 1. Browser Web Push Hook
  const { isSupported, isSubscribed, subscribe } = useWebPush({
    vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
    onSubscribe: async (subscription) => {
      // Send push token to YOUR custom backend database
      await fetch("https://api.yourdomain.com/push-tokens", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ subscription }),
      });
    },
  });

  return (
    <NotificationProvider>
      <header className="navbar">
        <h2>Dashboard</h2>

        {/* 2. Notification Bell with Unread Badge & Dropdown Feed */}
        <NotificationBell />

        {/* Push notification opt-in button */}
        {!isSubscribed && (
          <button onClick={subscribe}>Enable Browser Notifications</button>
        )}
      </header>

      <main>
        {/* 3. User Channel Preferences Settings Card */}
        <NotificationPreferences
          onChange={async (preferences) => {
            // Save preferences (e.g. { email: true, sms: false, whatsapp: true })
            await fetch("https://api.yourdomain.com/user/preferences", {
              method: "POST",
              body: JSON.stringify(preferences),
            });
          }}
        />
      </main>
    </NotificationProvider>
  );
}

Scenario C: Full-Stack (Next.js App Router)

In a Next.js full-stack application, configure the engine in a server file and use the React components on the client:

  • Server-Side (app/notify.server.ts): Initialize engine with Resend, Termii, Meta WhatsApp, FCM/Expo.
  • Client-Side (app/layout.tsx): Wrap your application in <NotificationProvider>.
  • Navbar Component (app/components/Navbar.tsx): Render <NotificationBell /> and useWebPush().
  • Route Handler (app/api/notify/route.ts): Call notify.send() from Server Actions or Route Handlers.

👉 See Complete Next.js Full-Stack App Router Guide for a full step-by-step walkthrough.


📡 Multi-Channel Dispatch API

The notify.send() method is the core dispatch interface:

const result = await notify.send({
  // Recipient info (single string phone/email or structured object)
  to: {
    email: "[email protected]",
    phone: "+2348012345678",
    whatsapp: "+2348012345678",
    pushToken: "ExponentPushToken[xxxxxxxxxxxxxx]",
    // Optional: Recipient preferences to automatically filter channels
    preferences: {
      email: true,
      whatsapp: true,
      sms: false, // SMS will automatically be skipped for this user
      push: true,
    },
  },

  // Channels to dispatch to
  channels: ["email", "whatsapp", "sms", "push"],

  // Message content (supports {{variables}})
  subject: "Audit Notification: {{facilityName}}",
  content: "Hello {{name}}, your audit scheduled for {{date}} has been approved.",

  // Template variables
  data: {
    name: "Dr. Aliyu",
    facilityName: "National Hospital",
    date: "March 15, 2026",
  },

  // Priority flag
  priority: "high", // "high" | "normal" | "low"
});

🛡️ Automatic Provider Failover

Never miss an urgent alert due to a 3rd-party provider downtime, network timeout, or rate limit:

import {
  createNotificationEngine,
  resendProvider,
  sendgridProvider,
  termiiSMSProvider,
  twilioSMSProvider,
} from "devclassic-notify";

export const notify = createNotificationEngine({
  channels: {
    // 📧 Email Failover: Try Resend first, fall back to SendGrid
    email: {
      primary: resendProvider({ apiKey: process.env.RESEND_API_KEY! }),
      fallbacks: [
        sendgridProvider({ apiKey: process.env.SENDGRID_API_KEY! }),
      ],
    },

    // 📱 SMS Failover: Try Termii (Africa), fall back to Twilio (Global)
    sms: {
      primary: termiiSMSProvider({
        apiKey: process.env.TERMII_API_KEY!,
        from: "YOURAPP",
      }),
      fallbacks: [
        twilioSMSProvider({
          accountSid: process.env.TWILIO_ACCOUNT_SID!,
          authToken: process.env.TWILIO_AUTH_TOKEN!,
          from: "+14155552671",
        }),
      ],
    },
  },
});

📝 Dynamic Variable Templates ({{variables}})

Pre-register templates with dot-notation variable interpolation:

const notify = createNotificationEngine({
  templates: {
    "otp-verification": {
      content: "Your verification code for {{appName}} is {{code}}. Valid for 10 minutes.",
    },
    "order-status": {
      subject: "Order {{order.id}} Update",
      content: "Hi {{user.name}}, your item {{order.item}} is now {{order.status}}.",
    },
  },
});

// Dispatch using template key
await notify.send({
  to: "+2348012345678",
  template: "otp-verification",
  data: { appName: "PayGuard", code: "938201" },
});

👤 User Channel Preferences & Opt-Outs

Respecting user communication choices is built into the engine. When a recipient has channel preferences, notify.send() automatically skips channels that are disabled:

await notify.send({
  to: {
    email: "[email protected]",
    phone: "+2348012345678",
    preferences: {
      email: true,
      sms: false,     // Skipped!
      whatsapp: true, // Sent!
    },
  },
  channels: ["email", "sms", "whatsapp"],
  content: "Important account alert.",
});

🔔 Frontend React Components & Hooks

1. <NotificationBell /> In-App Center

An interactive bell icon with an unread badge counter and dropdown feed:

import { NotificationProvider, NotificationBell } from "devclassic-notify";

export function Header() {
  return (
    <NotificationProvider
      initialNotifications={[
        {
          id: "1",
          title: "Inspection Approved",
          message: "Facility audit was approved successfully.",
          read: false,
          createdAt: new Date(),
        },
      ]}
    >
      <header className="flex justify-between items-center p-4">
        <h1>Dashboard</h1>
        <NotificationBell />
      </header>
    </NotificationProvider>
  );
}

2. <NotificationPreferences /> User Toggles

Pre-styled card allowing users to toggle their preferred channels:

import { NotificationPreferences } from "devclassic-notify";

export function SettingsPage() {
  const handleSave = async (preferences) => {
    // preferences = { email: true, sms: false, whatsapp: true, push: true }
    await api.savePreferences(preferences);
  };

  return (
    <div className="max-w-md p-4">
      <h3>Notification Preferences</h3>
      <NotificationPreferences onChange={handleSave} />
    </div>
  );
}

3. useWebPush() Browser Push Hook

One-click hook to request browser push permissions and retrieve a subscription token:

import { useWebPush } from "devclassic-notify";

export function PushSubscribeButton() {
  const { isSupported, isSubscribed, subscribe } = useWebPush({
    vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
    onSubscribe: async (subscription) => {
      // Save subscription to backend database
      await fetch("/api/push/subscribe", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ subscription }),
      });
    },
    onError: (err) => console.error("Push subscription failed:", err),
  });

  if (!isSupported) return null;

  return (
    <button onClick={subscribe} disabled={isSubscribed}>
      {isSubscribed ? "Push Notifications Active" : "Enable Push Notifications"}
    </button>
  );
}

🔑 How to Get VAPID Keys / Valid ID (Web Push)

What is VAPID / Valid ID?

VAPID (Voluntary Application Server Identification, RFC 8292) is the web push security standard. Browsers (Chrome, Firefox, Safari, Edge) require a valid Public VAPID Key (applicationServerKey) to ensure push notifications originate from your verified application server.

Step 1: Generate Your VAPID Key Pair (5 Seconds)

Run this command in your terminal using npx:

npx web-push generate-vapid-keys

Output:

=======================================

Public Key:
BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDZKrxZJjS5Snfflj004GTRUMMVM4zV123...

Private Key:
_87KM2EZxxK...

=======================================

(Alternatively, run npm i web-push and call webpush.generateVAPIDKeys() in Node.js).

Step 2: Set Environment Variables

Add the keys to your .env.local or .env file:

# Frontend (Client-accessible): Used by useWebPush() hook
NEXT_PUBLIC_VAPID_PUBLIC_KEY=BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDZKrxZJjS5Snfflj004GTRUMMVM4zV123...

# Backend (Keep private!): Used by your server to sign push dispatches
VAPID_PRIVATE_KEY=_87KM2EZxxK...
VAPID_SUBJECT=mailto:[email protected]

[!TIP] In Next.js, always prefix client-side variables with NEXT_PUBLIC_ (or VITE_ in Vite apps). Never expose the VAPID_PRIVATE_KEY on the client.


🆔 Provider Valid IDs & Setup Guide

Different providers require specific valid IDs and credentials to dispatch notifications:

| Channel / Provider | Required Valid ID | Purpose & Where to Obtain | | :--- | :--- | :--- | | Web Push | vapidPublicKey | Authenticates your app with browser push services. Generated via npx web-push generate-vapid-keys. | | Termii (SMS) | Valid from (Approved Sender ID) | Registered alphanumeric sender name (e.g. YOURAPP) approved by telecom regulators (e.g. NCC in Nigeria) on the Termii Dashboard. | | Twilio (SMS / WA) | Valid from phone number | Approved Twilio phone number (e.g. +14155552671 or whatsapp:+14155238886) from the Twilio Console. | | Meta WhatsApp | phoneNumberId (Valid Phone ID) | 15-digit unique ID identifying your WhatsApp Business Phone Number on the Meta for Developers Portal. | | Firebase (FCM) | serverKey | Legacy Cloud Messaging server key from the Firebase Console Project Settings. | | Expo (Push) | accessToken (optional) | Personal Access Token from Expo Settings for high-throughput mobile push. |

How to Obtain Key Provider IDs:

1. Termii Approved Sender ID (SMS)

Regulatory frameworks (e.g. NCC in Nigeria) require SMS messages to be sent from an approved Sender ID:

  1. Log in to your Termii Dashboard.
  2. In the sidebar, click Sender ID.
  3. Click Request Sender ID, enter your alphanumeric name (3 to 11 characters, e.g. YOURAPP), and submit a sample notification.
  4. Once approved, use that exact name:
    termiiSMSProvider({
      apiKey: process.env.TERMII_API_KEY!,
      from: "YOURAPP", // Valid, approved Sender ID
    })

2. Meta WhatsApp Phone Number ID

The phoneNumberId is the unique ID assigned to your WhatsApp Business phone number:

  1. Navigate to the Meta for Developers Portal and select your App.
  2. In the sidebar, go to WhatsApp > API Setup.
  3. Under Step 1: Select phone numbers, copy the numeric Phone number ID:
    metaWhatsAppProvider({
      accessToken: process.env.META_WA_TOKEN!,
      phoneNumberId: "109283746501928", // Valid Phone Number ID
    })

🔌 Supported Providers Reference

All providers use lightweight HTTP requests with zero heavy SDK dependencies:

| Channel | Built-in Providers | Key Characteristics | | :--- | :--- | :--- | | 📧 Email | resendProvidersendgridProvidergmailProvidermockEmailProvider | Zero-dependency fetch, HTML/Text support, Google OAuth2/Gmail REST API, failover support. | | 📱 SMS | termiiSMSProvidertwilioSMSProvidermockSMSProvider | Specialized for Nigeria/Africa (Termii) and Global delivery (Twilio). | | 💬 WhatsApp | metaWhatsAppProvidertwilioWhatsAppProvidermockWhatsAppProvider | Direct Meta Cloud Graph API & Twilio WhatsApp Sandbox / production. | | 🔔 Push | expoPushProviderfcmPushProvidermockPushProvider | Mobile React Native (Expo), Firebase Cloud Messaging (FCM), & Web Push. |


🧪 Offline Sandbox & Mocking

Test notifications in CI/CD or local development without spending real API credits or sending actual messages:

import {
  mockEmailProvider,
  mockSMSProvider,
  mockWhatsAppProvider,
  mockPushProvider,
} from "devclassic-notify";

const emailMock = mockEmailProvider({ logToConsole: true });
const smsMock = mockSMSProvider({ logToConsole: true });

// Inspect sent messages in test assertions:
const sent = emailMock.getSentEmails();
console.log("Sent recipient:", sent[0].payload.recipient.email);

📚 Guides & Complete Documentation


📄 License

MIT © devclassic