| name | notifications-mail |
| description | Send email notifications in NestJS with @dudousxd/nestjs-notifications-mail. Use when registering MailChannelModule.forRoot (from, smtp, transport, renderer, resolveTransport), building the fluent MailMessage (subject/greeting/line/action/salutation/from/attach/markdown/react/mjml), implementing the MailNotification interface with a @Mail()-decorated toMail(ctx) that returns a MailMessage, choosing a renderer (DefaultMailRenderer, MarkdownMailRenderer, ReactEmailRenderer, MjmlMailRenderer), and a transport (NodemailerTransport SMTP, SesTransport). Covers per-tenant transports and attachments. |
| license | MIT |
| metadata | {"type":"core","library":"@dudousxd/nestjs-notifications-mail","library_version":"0.8.1","framework":"nestjs"} |
nestjs-notifications — mail channel
The mail channel renders a notification's MailMessage and sends it through a pluggable transport.
The recipient address comes from the notifiable's mail route (@RouteFor('mail') or
routeNotificationFor('mail')).
Setup
pnpm add @dudousxd/nestjs-notifications-mail
import { MailChannelModule } from '@dudousxd/nestjs-notifications-mail';
@Module({
imports: [
MailChannelModule.forRoot({
from: 'no-reply@example.com',
smtp: { host: 'smtp.example.com', port: 587, auth: { user: 'u', pass: 'p' } },
}),
],
})
export class AppModule {}
forRoot registers globally (global: true) and uses NodemailerTransport + DefaultMailRenderer
unless you override transport/transportInstance and renderer/rendererInstance. This module
is a channel for the core engine — it must be imported alongside NotificationsModule.forRoot().
Core patterns
1. A mail notification
Implement MailNotification (or just decorate the payload method with @Mail()); the method
returns a fluent MailMessage. The handler receives a ctx carrying the recipient and (when
configured) localization.
import { Notification } from '@dudousxd/nestjs-notifications-core';
import { Mail, MailMessage, type MailNotification } from '@dudousxd/nestjs-notifications-mail';
@Notification()
export class InvoicePaid implements MailNotification {
constructor(private readonly invoiceId: string, private readonly amount: number) {}
@Mail()
toMail(): MailMessage {
return new MailMessage()
.subject(`Invoice ${this.invoiceId} paid`)
.greeting('Thanks for your payment!')
.line(`We received your payment of $${this.amount.toFixed(2)}.`)
.action('View invoice', `https://example.com/invoices/${this.invoiceId}`)
.line('No further action is needed.');
}
}
MailMessage is a builder: .from, .subject, .greeting, .line (call repeatedly),
.action(text, url), .salutation, .attach, and the body-format setters .markdown,
.react, .mjml.
2. Pick a renderer for richer bodies
The body setter must match the configured renderer. The default renderer only understands
greeting/line/action; .markdown()/.react()/.mjml() bodies are honored only by their renderer.
import {
MailChannelModule,
MarkdownMailRenderer,
ReactEmailRenderer,
MjmlMailRenderer,
} from '@dudousxd/nestjs-notifications-mail';
MailChannelModule.forRoot({ from: 'a@b.com', renderer: MarkdownMailRenderer, smtp });
3. Attachments & per-tenant transports
new MailMessage().subject('Report').attach({ filename: 'report.pdf', content: pdfBuffer });
Give each tenant its own SMTP/provider with resolveTransport; it is used when a send runs with a
context.tenant (notifications.forTenant(id).send(...)):
MailChannelModule.forRoot({
from: 'a@b.com',
resolveTransport: (tenant) => tenantTransports[tenant],
});
For SES instead of SMTP, use SesTransport via transportInstance:
import { SesTransport } from '@dudousxd/nestjs-notifications-mail';
MailChannelModule.forRoot({ from: 'a@b.com', transportInstance: new SesTransport({ client: sesV2Client }) });
Common mistakes
A rich body with the default renderer
MailChannelModule.forRoot({ from: 'a@b.com', smtp });
class N { @Mail() toMail() { return new MailMessage().markdown('# Hi'); } }
MailChannelModule.forRoot({ from: 'a@b.com', smtp, renderer: MarkdownMailRenderer });
DefaultMailRenderer builds HTML only from greeting/lines/action/salutation; markdown/react/mjml
bodies are renderer-specific. Source: packages/mail/src/renderer.ts, packages/mail/src/mail-message.ts.
No mail route on the notifiable
class User { constructor(public id: number, public email: string) {} }
import { RouteFor } from '@dudousxd/nestjs-notifications-core';
class User { @RouteFor('mail') email!: string; constructor(public id: number, email: string) { this.email = email; } }
The channel reads the recipient from routeFor(notifiable, 'mail'); with no @RouteFor('mail')
(or routeNotificationFor) it stringifies undefined. Source: packages/mail/src/mail.channel.ts.
Importing the mail module but not the core engine
imports: [MailChannelModule.forRoot({ from: 'a@b.com', smtp })]
imports: [NotificationsModule.forRoot(), MailChannelModule.forRoot({ from: 'a@b.com', smtp })]
MailChannel is a driver discovered by the core runner; without NotificationsModule there is no
NotificationService and the channel is never invoked. Source: packages/mail/src/mail.module.ts,
README "Quick start".
Expecting from to be mandatory on every message
new MailMessage().from('a@b.com').subject('Hi');
MailChannelModule.forRoot({ from: 'no-reply@example.com', smtp });
new MailMessage().subject('Hi');
The channel falls back to options.from when message.fromAddress is unset; a per-message .from()
is an override, not a requirement. Source: packages/mail/src/mail.channel.ts, packages/mail/src/mail.module.ts.