NestboltNestbolt

@nestbolt/notifications

Introduction

Multi-channel notification system for NestJS -- send notifications via database, email, and custom channels with a clean, class-based API.

Overview

@nestbolt/notifications is a multi-channel notification system for NestJS applications. It provides a unified, class-based API for sending notifications through multiple delivery channels -- database, email, and any custom channel you define -- all from a single notification class.

The package draws inspiration from Laravel's notification system, adapted to fit naturally within the NestJS ecosystem using TypeORM, dependency injection, and decorators.

Key Features

  • Multi-channel delivery -- Send a single notification through multiple channels (database, mail, Slack, SMS, webhooks, or anything else) by defining a via() method on your notification class.
  • Database notifications -- Store notifications in a notifications table with full read/unread tracking, querying, and bulk operations out of the box.
  • Mail notifications -- Send rich HTML and plain-text emails using the fluent MailMessage builder API, powered by nodemailer.
  • Custom channels -- Implement the NotificationChannel interface to deliver notifications via any transport: Slack, Twilio, Firebase, webhooks, or anything else.
  • Entity mixin -- Add notify(), unreadNotifications(), markAllNotificationsAsRead(), and other convenience methods directly to your TypeORM entities with the @Notifiable() decorator and NotifiableMixin.
  • Lifecycle events -- Hook into notification.sending, notification.sent, notification.failed, notification.read, and notification.all-read events using @nestjs/event-emitter.
  • Global module -- Register once with forRoot() or forRootAsync() and use NotificationService anywhere in your application without re-importing.
  • TypeORM integration -- The database channel uses TypeORM repositories, supports multiple database drivers, and includes composite indexes for efficient querying.

Architecture

The notification system is built around a few core concepts:

Notification classes

Each notification is a plain TypeScript class that extends the Notification base class. It declares which channels to use via the via() method, and provides a toDatabase(), toMail(), or custom toX() method for each channel.

Channels

A channel is a delivery mechanism. The package ships with two built-in channels:

ChannelDescription
databasePersists the notification payload to a notifications table via TypeORM.
mailSends an email via nodemailer using the MailMessage builder.

You can register any number of custom channels (Slack, SMS, push notifications, webhooks) by implementing the NotificationChannel interface.

The ChannelManager

The ChannelManager is an internal registry that maps channel names (strings like "database", "mail", "slack") to their corresponding NotificationChannel implementations. When a notification is sent, the NotificationService iterates over the channels returned by via(), resolves each one through the ChannelManager, and calls its send() method.

NotificationService

The NotificationService is the primary entry point. It handles:

  • Sending notifications to one or many recipients
  • Querying stored notifications from the database
  • Read/unread tracking and bulk operations
  • Emitting lifecycle events (when @nestjs/event-emitter is installed)

Notifiable entities

Any TypeORM entity can become a notification recipient by applying the @Notifiable() decorator and extending NotifiableMixin(BaseEntity). This adds methods like notify(), getNotifications(), unreadNotifications(), and markAllNotificationsAsRead() directly to entity instances.

How It Works

The flow for sending a notification looks like this:

  1. You create a notification class (e.g., OrderShippedNotification) that extends Notification.
  2. The via() method returns the list of channels: ["database", "mail"].
  3. You call notificationService.send(user, new OrderShippedNotification(order)) or user.notify(new OrderShippedNotification(order)).
  4. For each channel in via():
    • A notification.sending event is emitted.
    • The ChannelManager resolves the channel by name.
    • The channel's send() method is called with the notifiable entity and notification.
    • On success, a notification.sent event is emitted.
    • On failure, a notification.failed event is emitted and the error is re-thrown.

Next Steps