
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
Production-ready email management package for NestJS with Handlebars templating and CLI tools
A production-ready email management package for NestJS with Handlebars templating, CLI tools, and comprehensive email functionality.
npm install mailerboi
# or
yarn add mailerboi
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: 'noreply@yourapp.com',
},
template: {
dir: './templates',
cache: true,
},
}),
],
})
export class AppModule {}
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: 'noreply@yourapp.com',
},
template: {
dir: './templates',
cache: true,
},
}),
],
})
export class AppModule {}
npx mailerboi init
npx mailerboi generate welcome
npx mailerboi generate password-reset
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',
},
});
}
}
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 <noreply@yourapp.com>',
replyTo: 'support@yourapp.com',
},
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(),
},
})
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 <noreply@yourapp.com>',
},
template: {
dir: './templates',
cache: true,
},
retry: {
attempts: 3,
delay: 1000,
},
})
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', ... })
})
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],
})
| 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 |
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>
Send a single email.
const result = await mailerService.sendEmail({
to: 'user@example.com',
subject: 'Hello World',
template: 'welcome',
context: { name: 'John' },
attachments: [
{
filename: 'document.pdf',
path: './files/document.pdf',
},
],
});
Send multiple emails efficiently.
const results = await mailerService.sendBatchEmails({
template: 'newsletter',
recipients: [
{ to: 'user1@example.com', context: { name: 'User 1' } },
{ to: 'user2@example.com', context: { name: 'User 2' } },
],
batchSize: 10,
batchDelay: 1000,
});
Validate email addresses with typo detection.
const validation = mailerService.validateEmail('user@gmial.com');
// { valid: true, warnings: ['Did you mean gmail.com?'] }
Test SMTP connection.
const isConnected = await mailerService.testConnection();
Preview templates during development.
const preview = await mailerService.getTemplatePreview('welcome', {
name: 'John',
appName: 'My App',
});
# Initialize with default settings
mailerboi init
# Initialize in custom directory
mailerboi init --dir ./email-templates
# Force overwrite existing files
mailerboi init --force
# 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 --force
# List all template types
mailerboi list
# Show detailed information
mailerboi list --verbose
<!-- 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'}}
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>`;
},
},
},
},
});
import { IsEmail, IsNotEmpty } from 'class-validator';
export class SendEmailDto {
@IsEmail()
to: string;
@IsNotEmpty()
subject: string;
@IsNotEmpty()
template: string;
}
MailerboiModule.forRoot({
rateLimit: {
max: 100, // 100 emails per window
windowMs: 15 * 60 * 1000, // 15 minutes
},
});
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>
// Logs are automatically generated for:
// - Email send attempts and results
// - Template compilation errors
// - SMTP connection issues
// - Validation failures
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;
}
}
}
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: 'test@example.com',
subject: 'Test',
template: 'welcome',
});
expect(result.success).toBe(true);
});
});
Use preview mode to test without sending real emails:
MailerboiModule.forRoot({
preview: true, // Emails will be logged instead of sent
// ... other options
});
// Before (Nodemailer)
const transporter = nodemailer.createTransporter(config);
await transporter.sendMail({
to: 'user@example.com',
subject: 'Hello',
html: '<h1>Hello</h1>',
});
// After (Mailerboi)
await mailerService.sendEmail({
to: 'user@example.com',
subject: 'Hello',
template: 'welcome',
context: { name: 'User' },
});
// Before
MailerModule.forRoot({
transport: 'smtp://user:pass@smtp.example.com',
defaults: { from: 'noreply@example.com' },
template: {
dir: './templates',
adapter: new HandlebarsAdapter(),
},
});
// After
MailerboiModule.forRoot({
transports: {
host: 'smtp.example.com',
auth: { user: 'user', pass: 'pass' },
},
defaults: { from: 'noreply@example.com' },
template: { dir: './templates' },
});
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}`,
},
});
}
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`);
}
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by evidenze
FAQs
Production-ready email management package for NestJS with Handlebars templating and CLI tools
We found that mailerboi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.