nestjs-mailable
v1.8.0
Published
A comprehensive NestJS mail package with design patterns for email handling, templating, and multi-provider support
Maintainers
Readme
NestJS Mailable
Production-ready email handling for NestJS with Laravel-inspired mailable classes, fluent API, and multi-transport support.
Documentation • Examples • Contributing
Features
- Mailable Classes - Encapsulate email logic with
envelope(),content(),attachments(), andheaders()methods - Fluent API - Chainable interface:
mailService.to(email).cc(cc).send(mailable) - Multiple Transports - SMTP, AWS SES, Mailgun, Mailjet, Resend
- Template Engines - Handlebars, EJS, Pug with auto-detection
- Built-in Testing - Mock MailService and verify emails without sending
- Type-Safe - Full TypeScript support with strict typing
- Async Configuration - Dependency injection with
forRootAsync - Error Handling - Detailed transport-specific error messages
Installation
Install the core package:
npm install nestjs-mailableThen install only the dependencies you need for your chosen transport and template engine.
Transport Dependencies
# SMTP
npm install nodemailer
# AWS SES (Production with nodemailer SMTP)
npm install nodemailer aws-sdk
# AWS SES (LocalStack development)
npm install aws-sdk
# Mailgun
npm install mailgun.js axios form-data
# Mailjet
npm install node-mailjet
# Resend
npm install resendTemplate Engine Dependencies
# Handlebars (recommended)
npm install handlebars
# EJS
npm install ejs
# Pug
npm install pugExample: Minimal Installation
For SMTP with Handlebars templates:
npm install nestjs-mailable nodemailer handlebarsFor AWS SES with EJS templates:
npm install nestjs-mailable aws-sdk ejsRequirements
- Node.js >= 20.0.0
- NestJS >= 10.0.0
- TypeScript >= 5.0.0
Quick Start
1. Import MailModule
import { Module } from '@nestjs/common';
import { MailModule, TransportType, TEMPLATE_ENGINE } from 'nestjs-mailable';
@Module({
imports: [
MailModule.forRoot({
transport: {
type: TransportType.SMTP,
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS,
},
},
from: {
address: '[email protected]',
name: 'Your App',
},
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
directory: './templates',
},
}),
],
})
export class AppModule {}2. Create a Mailable Class
import {
MailableClass as Mailable,
MailableEnvelope,
MailableContent,
} from 'nestjs-mailable';
export class WelcomeEmail extends Mailable {
constructor(private user: { name: string; email: string }) {
super();
}
envelope(): MailableEnvelope {
return {
subject: `Welcome, ${this.user.name}!`,
};
}
content(): MailableContent {
return {
template: 'emails/welcome',
with: {
name: this.user.name,
loginUrl: 'https://example.com/login',
},
};
}
}3. Send Email
import { Injectable } from '@nestjs/common';
import { MailService } from 'nestjs-mailable';
import { WelcomeEmail } from './welcome.email';
@Injectable()
export class UserService {
constructor(private mailService: MailService) {}
async registerUser(userData: any) {
const user = await this.createUser(userData);
await this.mailService.to(user.email).send(new WelcomeEmail(user));
return user;
}
}4. Create Template
Create templates/emails/welcome.hbs:
<h1>Welcome, {{name}}!</h1>
<p>Get started by logging in below.</p>
<a href="{{loginUrl}}">Login to Your Account</a>Configuration
Synchronous Configuration
MailModule.forRoot({
transport: {
type: TransportType.SMTP,
host: 'smtp.example.com',
port: 587,
secure: false,
auth: {
user: 'username',
pass: 'password',
},
},
from: {
address: '[email protected]',
name: 'Your App',
},
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
directory: './templates',
},
})Asynchronous Configuration
import { ConfigModule, ConfigService } from '@nestjs/config';
MailModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
transport: {
type: configService.get('MAIL_TRANSPORT') as TransportType,
host: configService.get('MAIL_HOST'),
port: configService.get('MAIL_PORT', 587),
secure: configService.get('MAIL_SECURE', false),
auth: {
user: configService.get('MAIL_USER'),
pass: configService.get('MAIL_PASS'),
},
},
from: {
address: configService.get('MAIL_FROM_ADDRESS'),
name: configService.get('MAIL_FROM_NAME'),
},
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
directory: path.join(__dirname, '../templates'),
},
}),
inject: [ConfigService],
})Transports
SMTP
MailModule.forRoot({
transport: {
type: TransportType.SMTP,
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS,
},
},
})AWS SES
Production Mode (nodemailer SMTP):
MailModule.forRoot({
transport: {
type: TransportType.SES,
region: 'us-east-1',
credentials: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD,
},
},
})LocalStack Mode (AWS SDK):
MailModule.forRoot({
transport: {
type: TransportType.SES,
region: 'us-east-1',
endpoint: 'http://localhost:4566',
credentials: {
accessKeyId: 'test',
secretAccessKey: 'test',
},
},
})Mailgun
MailModule.forRoot({
transport: {
type: TransportType.MAILGUN,
options: {
domain: 'mg.yourdomain.com',
apiKey: process.env.MAILGUN_API_KEY,
},
},
})Mailjet
MailModule.forRoot({
transport: {
type: TransportType.MAILJET,
options: {
apiKey: process.env.MAILJET_API_KEY,
apiSecret: process.env.MAILJET_API_SECRET,
},
},
})Resend
MailModule.forRoot({
transport: {
type: TransportType.RESEND,
apiKey: process.env.RESEND_API_KEY,
},
})API
MailService
// Set recipients
mailService.to(email: string | string[])
mailService.cc(email: string | string[])
mailService.bcc(email: string | string[])
// Configure email
mailService.from(address: string | Address)
mailService.replyTo(address: string | Address)
mailService.subject(subject: string)
mailService.html(content: string)
mailService.text(content: string)
mailService.template(name: string, context?: Record<string, any>)
// Send
mailService.send(mailable?: Mailable): Promise<unknown>
// Testing
mailService.fake(): MailFake
mailService.clearSent(): voidMailable Class
export class MyEmail extends Mailable {
envelope(): MailableEnvelope {
return { subject: 'Email Subject' };
}
content(): MailableContent {
return {
template: 'email-template',
with: { variable: 'value' },
};
}
attachments(): MailableAttachment[] {
return [AttachmentBuilder.fromPath('./file.pdf').as('file.pdf').build()];
}
headers(): MailableHeaders {
return { 'X-Custom-Header': 'value' };
}
}Testing
Using MailFake
import { Test } from '@nestjs/testing';
import { MailService } from 'nestjs-mailable';
describe('UserService', () => {
let mailService: MailService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [UserService, MailService],
}).compile();
mailService = module.get<MailService>(MailService);
mailService.fake();
});
it('should send welcome email', async () => {
await userService.registerUser(user);
expect(mailService.hasSent(WelcomeEmail)).toBe(true);
expect(mailService.hasSentTo(user.email)).toBe(true);
});
});Jest Mocking
import { createMailServiceMock } from 'nestjs-mailable/testing';
const mockMailService = createMailServiceMock();
await mockMailService
.to('[email protected]')
.subject('Test')
.send();
expect(mockMailService.to).toHaveBeenCalledWith('[email protected]');See the Jest Mocking Guide and Testing Utilities Reference for comprehensive testing documentation.
Template Engines
Handlebars
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
directory: './templates',
partials: {
header: './templates/partials/header',
footer: './templates/partials/footer',
},
options: {
helpers: {
currency: (amount: number) => `$${amount.toFixed(2)}`,
},
},
},
})EJS
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.EJS,
directory: './templates',
options: {
cache: true,
compileDebug: false,
},
},
})Pug
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.PUG,
directory: './templates',
options: {
pretty: false,
compileDebug: false,
},
},
})Advanced Usage
Attachments
import { AttachmentBuilder } from 'nestjs-mailable';
export class InvoiceEmail extends Mailable {
attachments(): MailableAttachment[] {
return [
AttachmentBuilder
.fromPath('./invoices/invoice.pdf')
.as('Invoice.pdf')
.withMime('application/pdf')
.build(),
AttachmentBuilder
.fromBuffer(buffer, 'image.png')
.withMime('image/png')
.inline('logo')
.build(),
];
}
}Custom Headers
export class CustomEmail extends Mailable {
headers(): MailableHeaders {
return {
'X-Priority': '1',
'X-Custom-Header': 'custom-value',
};
}
}Dynamic Template Selection
export class LocalizedEmail extends Mailable {
constructor(private user: User, private locale: string) {
super();
}
content(): MailableContent {
return {
template: `emails/${this.locale}/welcome`,
with: { name: this.user.name },
};
}
}Performance & Best Practices
Connection Pooling
MailModule.forRoot({
transport: {
type: TransportType.SMTP,
pool: true,
maxConnections: 5,
maxMessages: 100,
rateDelta: 1000,
rateLimit: 5,
},
})Template Caching
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
options: {
cache: true,
},
},
})Async Email Sending
Consider using a queue system for non-critical emails:
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
@Injectable()
export class NotificationService {
constructor(@InjectQueue('email') private emailQueue: Queue) {}
async sendWelcomeEmail(user: User) {
await this.emailQueue.add('welcome', {
email: user.email,
name: user.name,
});
}
}Security
- Never commit credentials. Use environment variables
- Validate and sanitize all user input before sending
- Use TLS for all SMTP connections in production
- Rotate credentials regularly
- Monitor bounce rates and complaints
Troubleshooting
Template Engine Not Found
Install the missing engine:
npm install handlebars
npm install ejs
npm install pugSMTP Connection Timeout
- Verify host and port are correct
- Check firewall allows outbound SMTP connections
- Use
secure: truefor port 465,secure: falsefor port 587
AWS SES Authentication Failed
- Use SES SMTP credentials (not regular AWS credentials)
- Verify email address or domain in AWS SES console
- Ensure IAM user has
ses:SendEmailpermission - Check region matches SMTP endpoint
Template Not Found
- Verify template directory path
- Check file extension matches engine (
.hbs,.ejs,.pug) - Ensure template files are included in build output
Mailgun API Error
- Verify API key format:
key-xxxxxxxxx - Check domain is configured in Mailgun dashboard
- Verify sender email matches verified domain
Documentation
Complete documentation available at mahmudulazamshohan.github.io/nestjs-mailable
Examples
Complete working examples available in examples/nestjs-email-app:
- Basic NestJS integration
- Multiple transport configurations
- Template engine usage
- Mailable class implementations
- Testing patterns
Migration
From @nestjs-modules/mailer
// Before
await this.mailerService.sendMail({
to: user.email,
subject: 'Welcome',
template: './welcome',
context: { name: user.name },
});
// After
await this.mailService
.to(user.email)
.send(new WelcomeEmail(user));From nodemailer
// Before
const transporter = nodemailer.createTransport(config);
await transporter.sendMail({ from, to, subject, html });
// After
await this.mailService
.to(to)
.subject(subject)
.html(html)
.send();Contributing
Contributions are welcome. Please read our Contributing Guide for details on:
- Code of Conduct
- Development setup
- Submitting pull requests
- Reporting bugs
Development
# Install dependencies
yarn install
# Run tests
yarn test
# Run tests with coverage
yarn test:coverage
# Build
yarn build
# Lint
yarn lintSupport
License
MIT - see LICENSE file for details.
Copyright (c) 2024 NestJS Mailable contributors
