A comprehensive NestJS mail package with design patterns for email handling, templating, and multi-provider support
npm install nestjs-mailableProduction-ready email handling for NestJS with Laravel-inspired mailable classes, fluent API, and multi-transport support.






Documentation • Examples • Contributing
- Mailable Classes - Encapsulate email logic with envelope(), content(), attachments(), and headers() 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
Install the core package:
``bash`
npm install nestjs-mailable
Then install only the dependencies you need for your chosen transport and template engine.
`bashSMTP
npm install nodemailer
$3
`bash
Handlebars (recommended)
npm install handlebarsEJS
npm install ejsPug
npm install pug
`$3
For SMTP with Handlebars templates:
`bash
npm install nestjs-mailable nodemailer handlebars
`For AWS SES with EJS templates:
`bash
npm install nestjs-mailable aws-sdk ejs
`$3
- Node.js >= 20.0.0
- NestJS >= 10.0.0
- TypeScript >= 5.0.0
Quick Start
$3
`typescript
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: 'noreply@example.com',
name: 'Your App',
},
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
directory: './templates',
},
}),
],
})
export class AppModule {}
`$3
`typescript
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
`typescript
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;
}
}
`$3
Create
templates/emails/welcome.hbs:`handlebars
Welcome, {{name}}!
Get started by logging in below.
Login to Your Account
`Configuration
$3
`typescript
MailModule.forRoot({
transport: {
type: TransportType.SMTP,
host: 'smtp.example.com',
port: 587,
secure: false,
auth: {
user: 'username',
pass: 'password',
},
},
from: {
address: 'noreply@example.com',
name: 'Your App',
},
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
directory: './templates',
},
})
`$3
`typescript
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
$3
`typescript
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,
},
},
})
`$3
Production Mode (nodemailer SMTP):
`typescript
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):
`typescript
MailModule.forRoot({
transport: {
type: TransportType.SES,
region: 'us-east-1',
endpoint: 'http://localhost:4566',
credentials: {
accessKeyId: 'test',
secretAccessKey: 'test',
},
},
})
`$3
`typescript
MailModule.forRoot({
transport: {
type: TransportType.MAILGUN,
options: {
domain: 'mg.yourdomain.com',
apiKey: process.env.MAILGUN_API_KEY,
},
},
})
`$3
`typescript
MailModule.forRoot({
transport: {
type: TransportType.MAILJET,
options: {
apiKey: process.env.MAILJET_API_KEY,
apiSecret: process.env.MAILJET_API_SECRET,
},
},
})
`$3
`typescript
MailModule.forRoot({
transport: {
type: TransportType.RESEND,
apiKey: process.env.RESEND_API_KEY,
},
})
`API
$3
`typescript
// 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)
// Send
mailService.send(mailable?: Mailable): Promise
// Testing
mailService.fake(): MailFake
mailService.clearSent(): void
`$3
`typescript
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
$3
`typescript
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.fake();
});
it('should send welcome email', async () => {
await userService.registerUser(user);
expect(mailService.hasSent(WelcomeEmail)).toBe(true);
expect(mailService.hasSentTo(user.email)).toBe(true);
});
});
`$3
`typescript
import { createMailServiceMock } from 'nestjs-mailable/testing';const mockMailService = createMailServiceMock();
await mockMailService
.to('user@example.com')
.subject('Test')
.send();
expect(mockMailService.to).toHaveBeenCalledWith('user@example.com');
`See the Jest Mocking Guide and Testing Utilities Reference for comprehensive testing documentation.
Template Engines
$3
`typescript
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)},
},
},
},
})
`$3
`typescript
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.EJS,
directory: './templates',
options: {
cache: true,
compileDebug: false,
},
},
})
`$3
`typescript
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.PUG,
directory: './templates',
options: {
pretty: false,
compileDebug: false,
},
},
})
`Advanced Usage
$3
`typescript
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(),
];
}
}
`$3
`typescript
export class CustomEmail extends Mailable {
headers(): MailableHeaders {
return {
'X-Priority': '1',
'X-Custom-Header': 'custom-value',
};
}
}
`$3
`typescript
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
$3
`typescript
MailModule.forRoot({
transport: {
type: TransportType.SMTP,
pool: true,
maxConnections: 5,
maxMessages: 100,
rateDelta: 1000,
rateLimit: 5,
},
})
`$3
`typescript
MailModule.forRoot({
templates: {
engine: TEMPLATE_ENGINE.HANDLEBARS,
options: {
cache: true,
},
},
})
`$3
Consider using a queue system for non-critical emails:
`typescript
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,
});
}
}
`$3
- 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
$3
Install the missing engine:
`bash
npm install handlebars
npm install ejs
npm install pug
`$3
- Verify host and port are correct
- Check firewall allows outbound SMTP connections
- Use
secure: true for port 465, secure: false for port 587$3
- Use SES SMTP credentials (not regular AWS credentials)
- Verify email address or domain in AWS SES console
- Ensure IAM user has
ses:SendEmail permission
- Check region matches SMTP endpoint$3
- Verify template directory path
- Check file extension matches engine (
.hbs, .ejs, .pug)
- Ensure template files are included in build output$3
- Verify API key format:
key-xxxxxxxxx
- Check domain is configured in Mailgun dashboard
- Verify sender email matches verified domainDocumentation
Complete documentation available at mahmudulazamshohan.github.io/nestjs-mailable
- Configuration Guide
- Mailable Classes
- Template Engines
- Testing Guide
- Jest Mocking
- Testing Utilities
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
$3
`typescript
// 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));
`$3
`typescript
// 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
$3
`bash
Install dependencies
yarn installRun tests
yarn testRun tests with coverage
yarn test:coverageBuild
yarn buildLint
yarn lint
``- Documentation
- GitHub Issues
- GitHub Discussions
- Stack Overflow
MIT - see LICENSE file for details.
Copyright (c) 2024 NestJS Mailable contributors