Notifiables
A notifiable is anything that can receive a notification. Declare per-channel addresses with @RouteFor decorators and mark the id with @NotifiableId — no routeNotificationFor switch, no manual toNotifiableRef.
A notifiable is anything that can receive a notification — a user entity, a team, a DTO, a plain object. With the decorator API you don't extend a base class or implement an interface: you annotate the properties that hold each channel's address and the recipient stays a normal object you own.
import { Notifiable, NotifiableId, RouteFor } from '@dudousxd/nestjs-notifications-core';
@Notifiable()
export class User {
@NotifiableId()
id: number;
@RouteFor('mail')
email: string;
@RouteFor('sms')
phone: string;
constructor(id: number, email: string, phone: string) {
this.id = id;
this.email = email;
this.phone = phone;
}
}That's the whole thing. @RouteFor('mail') says "this property is the mail address"; @RouteFor('sms')
says "this one is the SMS address". The recipient owns where a notification goes; the notification
owns what it sends.
@RouteFor — per-channel addresses
Put @RouteFor(channel) on the property that holds the address for that channel — an email, a phone
number, a Slack webhook, whatever the channel expects. When a notification routes to that channel, the
library reads the address straight off the property. No switch, no magic strings sprinkled across a
method.
A channel you don't decorate simply has no address; the channel will skip or fall back as documented for that channel.
@Notifiable and @NotifiableId — the async reference
Synchronous delivery passes your live object straight to the channels, so @RouteFor is all you need.
The moment a notification is queued, though, it crosses a process boundary: the worker that
delivers it has no access to your in-memory User. It only has JSON.
So a queued notifiable must serialize to a stable { type, id } reference the worker can reload. The
decorators derive it for you:
@Notifiable()marks the class and pins thetype. Pass@Notifiable({ type: 'user' })to set it explicitly; otherwise it defaults to the class name ('User').@NotifiableId()marks the property that holds the id (defaulting toidif omitted).
Together they produce the reference — { type: 'User', id: 7 } — with no toNotifiableRef() to
hand-write.
@Notifiable({ type: 'user' }) // type defaults to the class name when omitted
export class User {
@NotifiableId()
id: number;
// …
}The worker rebuilds the recipient from that reference via the resolveNotifiable function you give
NotificationsModule.forRoot(). See Async dispatch for the full
round trip.
Reach for @Notifiable() + @NotifiableId() only if the notifiable might be queued. Sync-only
apps never need them — and the library throws a clear error if you queue a notifiable that can't
build its reference.
Alternative: routeNotificationFor()
The decorator API is the recommended default, but the method-based contract still works and is the
right tool for dynamic or computed routing. Implement routeNotificationFor(channel, notification)
and it overrides the @RouteFor decorators entirely:
import type { Notification } from '@dudousxd/nestjs-notifications-core';
export class User {
// …
routeNotificationFor(channel: string, notification: Notification): unknown {
if (channel === 'mail' && notification instanceof SecurityAlert) {
return this.securityEmail ?? this.email; // per-notification choice
}
if (channel === 'mail') return this.email;
if (channel === 'sms') return this.phone;
return undefined;
}
}The second argument is the notification instance, so you can route differently per notification when
you need to. You can also mix the styles — keep @RouteFor for the static channels and add
routeNotificationFor only when a channel needs computed routing. When present, it wins.
The same applies to the async reference: an explicit toNotifiableRef() overrides @Notifiable() +
@NotifiableId(), for when the { type, id } ref must be computed.
On-demand notifiables
Sometimes there's no entity at all — you just want to email an address. You don't need a notifiable for that; use on-demand routing:
@Injectable()
export class AlertsService {
constructor(private readonly notifications: NotificationService) {}
async serverDown() {
await this.notifications.route('mail', 'ops@example.com').notify(new ServerDown());
}
}Under the hood that builds an anonymous notifiable that routes the given channel to the given value — the channels can't tell the difference.
Getting Started
Send your first notification in an existing NestJS app — install the core and a channel, write a notification class, and call send(). Synchronous by default; add a queue when you're ready.
Notifications
A notification is a plain class. Annotate each payload method with the channel handle — @Mail(), @Database() — and via() is inferred automatically. No magic strings; add the channel interface for compile-time type safety when you want it.