mailerboi
v1.0.0
Published
Production-ready email management package for NestJS with Handlebars templating and CLI tools
Maintainers
Readme
📧 Mailerboi
A production-ready email management package for NestJS with Handlebars templating, CLI tools, and comprehensive email functionality.
✨ Features
- 🚀 Full NestJS Integration - Seamless module setup with sync/async configuration
- 📧 Multiple Transport Types - SMTP and HTTP API support (ZeptoMail, Mailtrap, SendGrid, Mailgun, Postmark)
- 🌐 API Transports - Modern HTTP-based email delivery with better deliverability
- 🎨 Handlebars Templates - Rich templating with built-in helpers and partials
- 📱 Mobile Responsive - Templates optimized for all devices and email clients
- 🌙 Dark Mode Support - CSS variables for automatic dark mode compatibility
- 🌍 RTL Language Support - Built-in right-to-left language support
- 🔧 CLI Tools - Generate templates and initialize projects quickly
- 📊 Batch Processing - Send bulk emails with queue support
- 🔄 Retry Logic - Automatic retry with exponential backoff
- 🎯 Email Validation - Advanced validation with typo detection
- 🔍 Preview Mode - Development mode for testing without sending
- 📈 Connection Pooling - Optimized SMTP connection management
- 🔐 Security First - Input validation and sanitization
- 📝 TypeScript - Full TypeScript support with comprehensive types
📦 Installation
npm install mailerboi
# or
yarn add mailerboi🚀 Quick Start
1. Module Setup
SMTP Configuration
import { Module } from '@nestjs/common';
import { MailerboiModule } from 'mailerboi';
@Module({
imports: [
MailerboiModule.forRoot({
transports: {
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
},
defaults: {
from: '[email protected]',
},
template: {
dir: './templates',
cache: true,
},
}),
],
})
export class AppModule {}API Configuration (Recommended)
import { Module } from '@nestjs/common';
import { MailerboiModule } from 'mailerboi';
@Module({
imports: [
MailerboiModule.forRoot({
transports: {
type: 'api',
provider: 'zeptomail', // or 'mailtrap', 'sendgrid', 'mailgun', 'postmark'
apiKey: process.env.ZEPTOMAIL_API_KEY,
},
defaults: {
from: '[email protected]',
},
template: {
dir: './templates',
cache: true,
},
}),
],
})
export class AppModule {}2. Initialize Templates
npx mailerboi init
npx mailerboi generate welcome
npx mailerboi generate password-reset3. Send Your First Email
import { Injectable } from '@nestjs/common';
import { MailerboiService } from 'mailerboi';
@Injectable()
export class UserService {
constructor(private readonly mailerService: MailerboiService) {}
async sendWelcomeEmail(user: any) {
await this.mailerService.sendEmail({
to: user.email,
subject: 'Welcome to Our App!',
template: 'welcome',
context: {
name: user.name,
verificationUrl: `https://yourapp.com/verify/${user.token}`,
appName: 'Your App',
},
});
}
}🔧 Configuration
Synchronous Configuration
SMTP Transports
MailerboiModule.forRoot({
transports: [
{
name: 'primary',
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASS,
},
pool: true,
maxConnections: 5,
},
{
name: 'sendgrid',
host: 'smtp.sendgrid.net',
port: 587,
auth: {
user: 'apikey',
pass: process.env.SENDGRID_API_KEY,
},
},
],
defaults: {
from: 'Your App <[email protected]>',
replyTo: '[email protected]',
},
template: {
dir: './templates',
extension: '.hbs',
cache: process.env.NODE_ENV === 'production',
options: {
helpers: {
customHelper: (value) => value.toUpperCase(),
},
},
},
preview: process.env.NODE_ENV === 'development',
verifyConnection: true,
retry: {
attempts: 3,
delay: 1000,
},
globalContext: {
appName: 'Your App',
appUrl: 'https://yourapp.com',
currentYear: new Date().getFullYear(),
},
})API Transports (Recommended)
MailerboiModule.forRoot({
transports: [
// ZeptoMail API
{
type: 'api',
name: 'zeptomail',
provider: 'zeptomail',
apiKey: process.env.ZEPTOMAIL_API_KEY,
credentials: {
domain: process.env.ZEPTOMAIL_DOMAIN, // Optional
},
},
// Mailtrap API (great for testing)
{
type: 'api',
name: 'mailtrap',
provider: 'mailtrap',
apiKey: process.env.MAILTRAP_API_KEY,
},
// SendGrid API
{
type: 'api',
name: 'sendgrid',
provider: 'sendgrid',
apiKey: process.env.SENDGRID_API_KEY,
},
// Mailgun API
{
type: 'api',
name: 'mailgun',
provider: 'mailgun',
apiKey: process.env.MAILGUN_API_KEY,
credentials: {
domain: process.env.MAILGUN_DOMAIN,
region: 'us', // or 'eu'
},
},
// Postmark API
{
type: 'api',
name: 'postmark',
provider: 'postmark',
apiKey: process.env.POSTMARK_API_KEY,
},
],
defaults: {
from: 'Your App <[email protected]>',
},
template: {
dir: './templates',
cache: true,
},
retry: {
attempts: 3,
delay: 1000,
},
})Mixed Transports (API + SMTP Fallback)
MailerboiModule.forRoot({
transports: [
// Primary: API transport for better deliverability
{
type: 'api',
name: 'primary',
provider: 'zeptomail',
apiKey: process.env.ZEPTOMAIL_API_KEY,
},
// Backup: SMTP transport for fallback
{
type: 'smtp',
name: 'backup',
host: 'smtp.gmail.com',
port: 587,
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASS,
},
},
],
// Send with specific transport
// await mailerService.sendEmail({ transport: 'backup', ... })
})Asynchronous Configuration
MailerboiModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
transports: {
host: configService.get('SMTP_HOST'),
port: configService.get('SMTP_PORT'),
secure: configService.get('SMTP_SECURE') === 'true',
auth: {
user: configService.get('SMTP_USER'),
pass: configService.get('SMTP_PASS'),
},
},
template: {
dir: configService.get('TEMPLATE_DIR', './templates'),
cache: configService.get('NODE_ENV') === 'production',
},
preview: configService.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
})📧 Email Templates
Available Template Types
| Type | Description | Variables |
|------|-------------|-----------|
| welcome | Welcome email with verification | name, verificationUrl, appName |
| password-reset | Password reset request | name, resetUrl, expirationTime |
| order-confirmation | E-commerce order confirmation | orderNumber, items, total |
| newsletter | Marketing newsletter | content, unsubscribeUrl |
| notification | Generic notification | title, message, actionUrl |
| two-factor | 2FA authentication code | code, expirationTime |
| account-deletion | Account deletion confirmation | name, confirmationUrl |
| invoice | Invoice/receipt | invoiceNumber, items, total |
Template Features
- Mobile Responsive - Optimized for all screen sizes
- Dark Mode - Automatic dark mode using CSS variables
- RTL Support - Right-to-left language compatibility
- Inline CSS - Maximum email client compatibility
- Accessibility - WCAG compliant HTML structure
Custom Templates
Create your own templates using Handlebars syntax:
<!DOCTYPE html>
<html lang="{{lang}}" dir="{{dir}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{subject}}</title>
</head>
<body>
<div class="container">
<h1>Hello {{name}}!</h1>
<p>{{message}}</p>
{{#if actionUrl}}
<a href="{{actionUrl}}" class="button">{{actionText}}</a>
{{/if}}
<p>Best regards,<br>{{appName}} Team</p>
</div>
</body>
</html>🎯 API Reference
MailerboiService
sendEmail(options: EmailOptions): Promise
Send a single email.
const result = await mailerService.sendEmail({
to: '[email protected]',
subject: 'Hello World',
template: 'welcome',
context: { name: 'John' },
attachments: [
{
filename: 'document.pdf',
path: './files/document.pdf',
},
],
});sendBatchEmails(options: BatchEmailOptions): Promise<EmailSendResult[]>
Send multiple emails efficiently.
const results = await mailerService.sendBatchEmails({
template: 'newsletter',
recipients: [
{ to: '[email protected]', context: { name: 'User 1' } },
{ to: '[email protected]', context: { name: 'User 2' } },
],
batchSize: 10,
batchDelay: 1000,
});validateEmail(email: string): EmailValidationResult
Validate email addresses with typo detection.
const validation = mailerService.validateEmail('[email protected]');
// { valid: true, warnings: ['Did you mean gmail.com?'] }testConnection(transport?: string): Promise
Test SMTP connection.
const isConnected = await mailerService.testConnection();getTemplatePreview(template: string, context: any): Promise<{html: string, text: string}>
Preview templates during development.
const preview = await mailerService.getTemplatePreview('welcome', {
name: 'John',
appName: 'My App',
});🛠️ CLI Commands
Initialize Templates Directory
# Initialize with default settings
mailerboi init
# Initialize in custom directory
mailerboi init --dir ./email-templates
# Force overwrite existing files
mailerboi init --forceGenerate Templates
# Generate welcome template
mailerboi generate welcome
# Generate with custom name
mailerboi generate password-reset --name custom-reset
# Generate in specific directory
mailerboi generate order-confirmation --dir ./custom-templates
# Generate without text version
mailerboi generate newsletter --no-text
# Force overwrite existing template
mailerboi generate notification --forceList Available Templates
# List all template types
mailerboi list
# Show detailed information
mailerboi list --verbose🎨 Handlebars Helpers
Built-in Helpers
<!-- Date formatting -->
{{formatDate date 'long'}}
{{formatTime date 'short'}}
{{relativeTime date}}
<!-- Currency and numbers -->
{{formatCurrency amount 'USD'}}
{{formatNumber count}}
{{formatPercent rate}}
<!-- String manipulation -->
{{uppercase text}}
{{lowercase text}}
{{capitalize text}}
{{truncate text 100}}
<!-- Conditionals -->
{{#ifEquals status 'active'}}Active{{/ifEquals}}
{{#ifGreater count 0}}{{count}} items{{/ifGreater}}
<!-- URLs -->
{{urlEncode text}}
{{addParam url 'utm_source' 'email'}}Custom Helpers
MailerboiModule.forRoot({
template: {
options: {
helpers: {
formatPhone: (phone: string) => {
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
},
highlight: (text: string) => {
return `<mark>${text}</mark>`;
},
},
},
},
});🔐 Security Best Practices
Input Validation
import { IsEmail, IsNotEmpty } from 'class-validator';
export class SendEmailDto {
@IsEmail()
to: string;
@IsNotEmpty()
subject: string;
@IsNotEmpty()
template: string;
}Rate Limiting
MailerboiModule.forRoot({
rateLimit: {
max: 100, // 100 emails per window
windowMs: 15 * 60 * 1000, // 15 minutes
},
});Content Sanitization
All template variables are automatically escaped by Handlebars. For HTML content, use triple braces with caution:
<!-- Safe (escaped) -->
<p>{{userInput}}</p>
<!-- Unsafe (unescaped) - only use with trusted content -->
<div>{{{trustedHtmlContent}}}</div>📊 Monitoring and Logging
Built-in Logging
// Logs are automatically generated for:
// - Email send attempts and results
// - Template compilation errors
// - SMTP connection issues
// - Validation failuresCustom Logging
import { Logger } from '@nestjs/common';
@Injectable()
export class EmailService {
private readonly logger = new Logger(EmailService.name);
async sendEmail(options: EmailOptions) {
this.logger.log(`Sending email to ${options.to}`);
try {
const result = await this.mailerService.sendEmail(options);
this.logger.log(`Email sent successfully: ${result.messageId}`);
return result;
} catch (error) {
this.logger.error(`Failed to send email: ${error.message}`);
throw error;
}
}
}🧪 Testing
Unit Testing
import { Test } from '@nestjs/testing';
import { MailerboiService } from 'mailerboi';
describe('EmailService', () => {
let service: MailerboiService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
{
provide: MailerboiService,
useValue: {
sendEmail: jest.fn().mockResolvedValue({
success: true,
messageId: 'test-id',
}),
},
},
],
}).compile();
service = module.get<MailerboiService>(MailerboiService);
});
it('should send email', async () => {
const result = await service.sendEmail({
to: '[email protected]',
subject: 'Test',
template: 'welcome',
});
expect(result.success).toBe(true);
});
});Preview Mode
Use preview mode to test without sending real emails:
MailerboiModule.forRoot({
preview: true, // Emails will be logged instead of sent
// ... other options
});🔄 Migration Guide
From Nodemailer
// Before (Nodemailer)
const transporter = nodemailer.createTransporter(config);
await transporter.sendMail({
to: '[email protected]',
subject: 'Hello',
html: '<h1>Hello</h1>',
});
// After (Mailerboi)
await mailerService.sendEmail({
to: '[email protected]',
subject: 'Hello',
template: 'welcome',
context: { name: 'User' },
});From @nestjs-modules/mailer
// Before
MailerModule.forRoot({
transport: 'smtp://user:[email protected]',
defaults: { from: '[email protected]' },
template: {
dir: './templates',
adapter: new HandlebarsAdapter(),
},
});
// After
MailerboiModule.forRoot({
transports: {
host: 'smtp.example.com',
auth: { user: 'user', pass: 'pass' },
},
defaults: { from: '[email protected]' },
template: { dir: './templates' },
});📚 Examples
E-commerce Order Confirmation
async sendOrderConfirmation(order: Order) {
return this.mailerService.sendEmail({
to: order.customer.email,
subject: `Order Confirmation #${order.number}`,
template: 'order-confirmation',
context: {
customerName: order.customer.name,
orderNumber: order.number,
orderDate: order.createdAt,
items: order.items.map(item => ({
name: item.product.name,
quantity: item.quantity,
price: item.price,
total: item.quantity * item.price,
})),
subtotal: order.subtotal,
shipping: order.shipping,
tax: order.tax,
total: order.total,
currency: 'USD',
shippingAddress: order.shippingAddress,
orderUrl: `https://yourstore.com/orders/${order.id}`,
},
});
}Newsletter with Unsubscribe
async sendNewsletter(subscribers: User[], content: string) {
const results = await this.mailerService.sendBatchEmails({
template: 'newsletter',
recipients: subscribers.map(user => ({
to: user.email,
context: {
name: user.name,
content: content,
unsubscribeUrl: `https://yourapp.com/unsubscribe/${user.unsubscribeToken}`,
preferencesUrl: `https://yourapp.com/preferences/${user.id}`,
},
})),
common: {
subject: 'Weekly Newsletter - Latest Updates',
},
batchSize: 50,
batchDelay: 2000,
});
const successCount = results.filter(r => r.success).length;
console.log(`Newsletter sent to ${successCount}/${results.length} subscribers`);
}🤝 Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Nodemailer - The underlying email transport
- Handlebars - Template engine
- NestJS - The amazing Node.js framework
📞 Support
- 📖 Documentation
- 🐛 Issues
- 💬 Discussions
- 📧 Email: [email protected]
Made with ❤️ by evidenze
