@suaveplan/notification-channel - Multi-channel notification router with templates, sliding-window rate limiting, retry + dead-letter, scheduler, preferences, hooks, OpenTelemetry instrumentation, and Zod-validated boundaries. Universal: runs unchanged in Bun, Node, and the browser.
Multi-channel notification router with templates, sliding-window rate limiting, retry + dead-letter, scheduler, preferences, hooks, OpenTelemetry instrumentation, and Zod-validated boundaries. Universal: runs unchanged in Bun, Node, and the browser.
Documentation tree
- delivery-channel —
delivery-channel.tsdefines the structural contract every concrete delivery channel implements. The contract is inten… - email-channel —
email-channel.tsships theEmailChannelDeliveryChannelimplementation. The class is provider-neutral: the integr… - in-app-channel —
in-app-channel.tsships theInAppChannelimplementation that stores notifications for an in-app inbox / unread badg… - push-channel —
push-channel.tsships thePushChannelimplementation that delivers notifications via the Web Push Protocol (RFC 803… - sms-channel —
sms-channel.tsships theSmsChannelDeliveryChannelimplementation. Like the email channel, it is provider-neutra… - webhook-channel —
webhook-channel.tsships theWebhookChannelimplementation that POSTs notifications to an arbitrary HTTPS endpoint.… - digest —
digest-channel.tsships theDigestChannelimplementation that batches notifications into periodic summaries deliver…- digest-channel —
digest-channel.tsships theDigestChannelimplementation that batches notifications into periodic summaries deliver…
- digest-channel —
- error-codes —
error-codes.tsis the single source of truth for every error the@suaveplan/notification-channelpackage can throw.…- error-codes —
error-codes.tsis the single source of truth for every error the@suaveplan/notification-channelpackage can throw.…
- error-codes —
- error-mapping —
error-mapping.tsis the canonical place every reason code inNOTIFICATION_CHANNEL_CODESis paired with its typed su…- error-mapping —
error-mapping.tsis the canonical place every reason code inNOTIFICATION_CHANNEL_CODESis paired with its typed su…
- error-mapping —
- fakes —
fake-fetch.tsships theFakeFetchtest double — aFetchLikethat consumes scripted responses from a FIFO queue an…- fake-fetch —
fake-fetch.tsships theFakeFetchtest double — aFetchLikethat consumes scripted responses from a FIFO queue an…
- fake-fetch —
- hooks —
hooks.tsexposes a small typed hook bus that runs beforeNotificationRoutercommits a notification to a channel. Th…- hooks —
hooks.tsexposes a small typed hook bus that runs beforeNotificationRoutercommits a notification to a channel. Th…
- hooks —
- otel —
otel/metrics.tsregisters the package's four OpenTelemetry instruments on a meter named `@suaveplan/notification-chan…- metrics —
otel/metrics.tsregisters the package's four OpenTelemetry instruments on a meter named `@suaveplan/notification-chan…
- metrics —
- preferences —
preference-manager.tsships thePreferenceManagerclass that answers, at every send-time, "should this notification…- preference-manager —
preference-manager.tsships thePreferenceManagerclass that answers, at every send-time, "should this notification…
- preference-manager —
- rate-limiter —
channel-rate-limiter.tsships theChannelRateLimiterclass — a per-channel + per-user sliding-window rate limiter.…- channel-rate-limiter —
channel-rate-limiter.tsships theChannelRateLimiterclass — a per-channel + per-user sliding-window rate limiter.…
- channel-rate-limiter —
- retry —
delivery-retry.tsships theDeliveryRetrywrapper that retries aDeliveryChannel.sendcall with exponential backo…- delivery-retry —
delivery-retry.tsships theDeliveryRetrywrapper that retries aDeliveryChannel.sendcall with exponential backo…
- delivery-retry —
- router —
notification-router.tsships theNotificationRouterclass — the primary entrypoint a consumer talks to. It resolves…- notification-router —
notification-router.tsships theNotificationRouterclass — the primary entrypoint a consumer talks to. It resolves…
- notification-router —
- scheduler —
notification-scheduler.tsships theNotificationSchedulerclass for scheduling notifications for future delivery. T…- notification-scheduler —
notification-scheduler.tsships theNotificationSchedulerclass for scheduling notifications for future delivery. T…
- notification-scheduler —
- schemas —
schemas.tsexposes a Zod schema for every public option bundle in the package so callers can validate untrusted input…- schemas —
schemas.tsexposes a Zod schema for every public option bundle in the package so callers can validate untrusted input…
- schemas —
- templates —
template-engine.tsships theTemplateEngineregistry that turns named templates into channel-specific rendered outp…- template-engine —
template-engine.tsships theTemplateEngineregistry that turns named templates into channel-specific rendered outp…
- template-engine —
Install
bun add @suaveplan/notification-channel
Quick start
import {
EmailChannel,
NotificationRouter,
} from "@suaveplan/notification-channel";
const email = new EmailChannel({
from: "noreply@example.com",
sendFn: async (msg) => ({ messageId: await provider.send(msg) }),
});
const router = new NotificationRouter({
channels: [{ channel: "email", enabled: true, rateLimit: 100 }],
defaultChannel: "email",
deliveryChannels: [email],
});
router.on("notification.failed", (n) => audit.record(n));
await router.send({
recipient: "user@example.com",
body: "Welcome to Suaveplan",
subject: "Welcome",
});
Surface
- Five sending channels —
EmailChannel,SmsChannel,PushChannel,InAppChannel,WebhookChannel. - One aggregating channel —
DigestChannel(wraps any sending channel). - Router —
NotificationRouterwith templates, routing rules, lifecycle events, OTel meters. - Composables —
ChannelRateLimiter,DeliveryRetry,NotificationScheduler,PreferenceManager,TemplateEngine. - Hooks —
notification:routingpre-routing pipeline (onNotificationRouting). - Errors — stable
NOTIFICATION_CHANNEL_CODESregistry with eleven reasons. - Test double —
FakeFetchfor deterministicFetchLikeinjection. - OpenTelemetry — four instruments under the
@suaveplan/notification-channelmeter.
Modules
error-codes—NOTIFICATION_CHANNEL_CODES+ typed error subclasses.hooks/hooks— generic hook bus +notification:routingpipeline.schemas/schemas— Zod schemas for every public option bundle.channels/delivery-channel— common transport contract.channels/email-channel— email delivery.channels/sms-channel— SMS delivery.channels/push-channel— Web Push delivery (RFC 8030).channels/in-app-channel— in-app inbox delivery.channels/webhook-channel— HTTP webhook delivery.digest/digest-channel— digest / summary delivery.templates/template-engine— template registry + renderer.rate-limiter/channel-rate-limiter— sliding-window rate limits.retry/delivery-retry— retry wrapper + dead-letter queue.scheduler/notification-scheduler— future-delivery scheduler.preferences/preference-manager— opt-in/out + quiet hours.router/notification-router— primary multi-channel router.otel/metrics— OpenTelemetry instruments.fakes/fake-fetch— deterministicFetchLiketest double.
Architecture
Every dependency is injectable: Clock and FetchLike come from @suaveplan/types, the storage adapter behind PreferenceManager is caller-supplied, and every channel takes a sendFn callback so the package never bundles a specific provider SDK. The router uses EventBus<NotificationEventMap> so lifecycle events flow through the same typed-pub/sub primitive as the rest of the project.
The 11-code defineCodes("NOTIFICATION_CHANNEL", […]) registry preserves literal-typed unions through the public surface, so an exhaustive switch (err.code) reports unhandled codes at type-check time. OpenTelemetry meters fire on every router send and every dead-letter event so dashboards reflect real production behaviour out of the box.
License
MIT