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

@nebutra/notifications

v0.1.2

Published

> **Status: Foundation** — Notification contracts, provider selection, runtime status, direct-provider stores, and direct delivery retry telemetry exist. `productionReady` remains `false` because production use still requires Novu credentials or injected

Readme

Status: Foundation — Notification contracts, provider selection, runtime status, direct-provider stores, and direct delivery retry telemetry exist. productionReady remains false because production use still requires Novu credentials or injected durable direct-provider adapters plus external provider-health monitoring.

@nebutra/notifications

Provider-agnostic notification center for the Nebutra platform. Supports multiple channels (in-app, email, push, SMS, chat) and multiple backends (Novu or self-hosted).

Features

  • Multi-channel: Send notifications across in-app, email, push, SMS, and chat
  • Provider-agnostic: Switch between Novu and self-hosted without changing application code
  • Multi-tenant: Built-in support for tenant isolation
  • Preferences: User-controlled notification preferences per channel
  • In-app feed: Native in-app notification center with read/unread tracking
  • Pluggable dispatchers: Direct provider accepts custom implementations for each channel

Installation

pnpm add @nebutra/notifications

Quick Start

Auto-detection (recommended)

import { getNotificationProvider, createNotification } from "@nebutra/notifications";

const notifier = await getNotificationProvider();

// Send a notification
await notifier.send(
  createNotification(
    "invoice.paid",
    "user_123",
    ["email", "in_app"],
    {
      invoiceId: "inv_456",
      amount: 99.99,
      email: "[email protected]",
    },
    "tenant_789"
  )
);

The provider is auto-detected based on environment:

  • If NOVU_API_KEY is set → Novu
  • Otherwise → Direct preview mode with in-memory stores

In production, the factory fails closed instead of silently using memory-backed direct stores. Configure Novu, inject both direct-provider stores, or set ALLOW_MEMORY_NOTIFICATIONS_IN_PRODUCTION=true as an explicit temporary escape hatch.

Explicit Configuration

Using Novu

import { createNotificationProvider } from "@nebutra/notifications";

const notifier = await createNotificationProvider({
  provider: "novu",
  apiKey: "your-novu-api-key",
  baseUrl: "https://api.novu.co", // optional, for self-hosted
});

Using Direct Provider with Custom Dispatchers and Stores

import { createNotificationProvider } from "@nebutra/notifications";
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

const notifier = await createNotificationProvider({
  provider: "direct",
  maxRetries: 1,
  deliveryObserver: {
    recordAttempt: async (attempt) => {
      await metrics.increment("notifications.delivery_attempt", {
        channel: attempt.channel,
        sent: String(attempt.result.sent),
        type: attempt.type,
      });
    },
  },
  emailDispatcher: {
    send: async (to, subject, body) => {
      const result = await resend.emails.send({
        from: "[email protected]",
        to,
        subject,
        html: body,
      });
      return {
        sent: !result.error,
        messageId: result.data?.id || "",
        error: result.error?.message,
      };
    },
  },
  inAppStore: myDurableInAppStore,
  preferenceStore: myDurablePreferenceStore,
  // Add other dispatchers as needed
});

Runtime Status

Use resolveNotificationRuntimeStatus() or the API gateway settings endpoint to drive UI and operational readiness:

  • managed: Novu provider is active.
  • self_hosted: Direct provider has durable adapters for at least one writable surface.
  • preview: Direct provider is using memory stores for development and tests.
  • degraded: A provider is selected but cannot run, such as NOTIFICATION_PROVIDER=novu without NOVU_API_KEY.

API Reference

Send Notifications

// Send to a single recipient
const result = await notifier.send(createNotification(
  type,
  recipientId,
  channels,
  data,
  tenantId
));

// Send to multiple recipients (batched)
const results = await notifier.sendBatch([
  notification1,
  notification2,
  notification3,
]);

Manage Preferences

// Get user's notification preferences
const prefs = await notifier.getPreferences(userId, tenantId);

// Update preferences for specific channels
await notifier.updatePreferences(userId, [
  { channel: "email", enabled: false },
  { channel: "sms", frequency: "daily" },
], tenantId);

In-App Notifications

// Get user's in-app feed
const feed = await notifier.getInAppNotifications(userId, {
  limit: 20,
  offset: 0,
  unreadOnly: false,
}, tenantId);
// Returns: { notifications: [...], total: 42, unreadCount: 5 }

// Mark as read
await notifier.markAsRead(notificationId, userId, tenantId);

Notification Payload

interface NotificationPayload {
  id?: string; // Auto-generated if omitted
  type: string; // Template ID (e.g., "invoice.paid")
  recipientId: string; // User ID
  tenantId?: string; // Tenant isolation
  channels: NotificationChannel[]; // Where to send
  data: Record<string, unknown>; // Template variables
  overrides?: {
    email?: { subject?: string; body?: string };
    sms?: { body?: string };
    push?: { title?: string; body?: string };
    in_app?: { title?: string; body?: string };
    chat?: { text?: string };
  };
  metadata?: Record<string, unknown>; // Debug info
}

Providers

Novu

Managed notification infrastructure with templates, delivery guarantees, and built-in preference management.

Setup:

  1. Sign up at https://novu.co
  2. Get your API key from the dashboard
  3. Create templates in Novu dashboard

Env vars:

NOVU_API_KEY=your-api-key
NOTIFICATION_PROVIDER=novu # optional

Direct

Self-hosted provider that delegates to pluggable dispatchers. Useful for custom implementations or existing email/SMS services.

Dispatchers to implement:

interface EmailDispatcher {
  send(to: string, subject: string, body: string, html?: string): Promise<{
    messageId: string;
    sent: boolean;
    error?: string;
  }>;
}

interface PushDispatcher {
  send(userId: string, title: string, body: string, data?: Record<string, string>): Promise<{
    messageId: string;
    sent: boolean;
    error?: string;
  }>;
}

interface SMSDispatcher {
  send(phoneNumber: string, body: string): Promise<{
    messageId: string;
    sent: boolean;
    error?: string;
  }>;
}

interface ChatDispatcher {
  send(webhookUrl: string, text: string, data?: Record<string, unknown>): Promise<{
    messageId: string;
    sent: boolean;
    error?: string;
  }>;
}

interface InAppNotificationStore {
  create(notification: Omit<InAppNotification, "id" | "createdAt" | "updatedAt">): Promise<InAppNotification>;
  markAsRead(notificationId: string, userId: string, tenantId?: string): Promise<void>;
  markAsReadBatch(notificationIds: string[], userId: string, tenantId?: string): Promise<void>;
  getByUserId(userId: string, options?: InAppFeedOptions, tenantId?: string): Promise<InAppFeedResult>;
  deleteOld(beforeDate: Date, tenantId?: string): Promise<number>;
}

interface PreferenceStore {
  getAll(userId: string, tenantId?: string): Promise<NotificationPreference[]>;
  getByChannel(userId: string, channel: NotificationChannel, tenantId?: string): Promise<NotificationPreference | null>;
  updateBatch(userId: string, preferences: Partial<NotificationPreference>[], tenantId?: string): Promise<void>;
}

Multi-Tenancy

All notification operations support tenantId for isolating notifications across different customers:

await notifier.send(
  createNotification(
    "invoice.paid",
    "user_123",
    ["email"],
    { invoiceId: "inv_456" },
    "tenant_789" // ← Tenant isolation
  )
);

// Preferences are also tenant-scoped
await notifier.getPreferences(userId, tenantId);

Environment Variables

# Provider selection (optional, auto-detected)
NOTIFICATION_PROVIDER=novu|direct

# Novu configuration
NOVU_API_KEY=your-api-key
NOVU_BASE_URL=https://api.novu.co # optional, for self-hosted

# Logging
LOG_LEVEL=info|debug|warn|error

# Explicit temporary escape hatch; do not use as normal production config
ALLOW_MEMORY_NOTIFICATIONS_IN_PRODUCTION=true

Examples

Send Email Notification

const notifier = await getNotificationProvider();

await notifier.send(
  createNotification(
    "payment.received",
    "user_123",
    ["email"],
    {
      amount: 99.99,
      email: "[email protected]",
    }
  )
);

Send Multi-Channel Notification

await notifier.send(
  createNotification(
    "project.shared",
    "user_123",
    ["email", "in_app", "push"],
    {
      projectName: "Q1 Planning",
      projectId: "proj_456",
      sharedBy: "Alice",
      email: "[email protected]",
      title: "Project Shared",
      body: "Alice shared Q1 Planning with you",
    }
  )
);

Batch Send

const notifications = [
  createNotification("invoice.paid", "user_1", ["email"], { ...data }),
  createNotification("invoice.paid", "user_2", ["email"], { ...data }),
  createNotification("invoice.paid", "user_3", ["email", "in_app"], { ...data }),
];

const results = await notifier.sendBatch(notifications);

Handle User Preferences

// User disables email notifications
await notifier.updatePreferences(userId, [
  {
    channel: "email",
    enabled: false,
  },
]);

// Subsequent sends to this user won't include email channel
// (even if "email" is in the channels array)

Testing

For development and deterministic tests, use the in-memory direct provider (default outside production):

import { createNotificationProvider } from "@nebutra/notifications";

const notifier = await createNotificationProvider({
  provider: "direct",
  // Uses in-memory stores — no external services needed
});

// Test your notification logic
await notifier.send(createNotification(
  "test.notification",
  "test_user",
  ["in_app"],
  { message: "Hello" }
));

License

Proprietary — Nebutra platform