One notification, every channel.
Write a notification once. Send it over mail, the database, a websocket, and Slack — synchronously, or queued through your own BullMQ. Type-safe payloads, on-demand routing, and a decoupled core where every channel and dispatcher is an opt-in package.
21 packages on npm · one notification, many channels · multi-tenant · sync or queued
One call
await notifications.send(
user,
new InvoicePaid(invoice),
);channel decorators infer the routes.
each channel reads its own payload.
Four channels
isolated · per-channelEverything a notification layer needs
Define once, deliver everywhere. Pick your channels, pick your dispatcher, and let the core wire them together.
Channels, decoupled
Mail, database, broadcast and Slack — each an opt-in package. The core knows only an interface; channels are discovered from the Nest container, never hard-wired.
Sync or queued
Deliver inline by default, or set shouldQueue and route through an async dispatcher — in-process events, Redis, or the BullMQ you already run. Channels and dispatch are orthogonal.
Type-safe payloads
Each channel exports an interface your notification implements, so toMail, toDatabase and toSlack are checked at compile time. Forget one and TypeScript tells you.
On-demand routing
No entity? Send straight to an address — notifications.route("mail", "ops@x.com").notify(...). Chain extra routes for one-off, multi-channel blasts.
Bring your ORM
The database channel persists through a NotificationStore interface. Ship with the in-memory store, or plug the TypeORM or MikroORM adapter — write your own in a few methods.
Faithful to Laravel
send, via, toMail, sendNow, queued notifications, on-demand routing. If you know Laravel notifications, you already know this API.
Observable
Lifecycle events for every send, plus a first-class nestjs-telescope watcher that records each delivery — channel, recipient, payload, failure reason — in the dashboard.
Resilient by default
One channel failing never blocks the others. Choose continue-on-error or fail-fast; failures surface as events so you can alert, retry or log.
Built to test
A NotificationFake swaps the real service in tests, with Laravel-style assertions — assertSentTo, assertSentOnChannel, assertNothingSent. No SMTP, no queue, no flake.
One class. That's the notification.
A notification is a plain class: annotate each payload method with the channel handle — @Mail(), @Database() — and the channels are inferred. No magic strings, no via() array to keep in sync. Register the channels once in your module, then send from anywhere.
@Notification()class InvoicePaid { @Mail() toMail() { return new MailMessage() .subject('Invoice paid') .line('Thanks for your payment.'); }}Stop wiring notifications by hand.
Define a notification once, pick your channels, and ship — sync today, queued tomorrow, without touching the call site.