
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@softvence/mail
Advanced tools
A powerful and flexible NestJS mail service built on top of Nodemailer with built-in template support, custom template rendering, and optional queue integration.
✨ Easy Integration - Seamlessly integrates with NestJS applications
📧 Nodemailer Powered - Built on the reliable Nodemailer library
🎨 Template System - Built-in OTP template with customizable template renderer
🔧 Flexible Configuration - Support for all major email providers (Gmail, SendGrid, etc.)
📬 Rich Email Options - Support for attachments, CC, BCC, priority, and custom headers
⚡ Queue Support - Optional mail queue integration for async processing
📦 TypeScript Support - Fully typed with TypeScript for better developer experience
npm install @softvence/mail
# or
yarn add @softvence/mail
# or
pnpm add @softvence/mail
Import MailModule in your application module and configure it with your email service credentials:
import { Module } from "@nestjs/common";
import { MailModule } from "@softvence/mail";
@Module({
imports: [
MailModule.forRoot({
transport: {
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
},
defaults: {
from: '"My App" <noreply@myapp.com>',
},
}),
],
})
export class AppModule {}
Inject MailService and start sending emails:
import { Injectable } from "@nestjs/common";
import { MailService } from "@softvence/mail";
@Injectable()
export class AuthService {
constructor(private readonly mailService: MailService) {}
async sendOTP(email: string, otp: string) {
await this.mailService.send({
to: email,
subject: "Your OTP Code",
template: "otp",
context: {
otp,
expire: "5m",
},
});
}
}
| Option | Type | Required | Description |
|---|---|---|---|
transport | SMTPTransport | SMTPTransport.Options | ✅ | Nodemailer transport configuration |
defaults | nodemailer.Transporter['options'] | ❌ | Default mail options (from, etc.) |
templateRenderer | (type, context) => string | ❌ | Custom template renderer function |
mailQueue | boolean | QueueConfig | ❌ | Mail queue configuration |
The transport option accepts any valid Nodemailer transport configuration. Here are common examples:
transport: {
service: 'gmail',
auth: {
user: 'your-email@gmail.com',
pass: 'your-app-password', // Use App Password, not regular password
},
}
transport: {
host: 'smtp.sendgrid.net',
port: 587,
auth: {
user: 'apikey',
pass: 'your-sendgrid-api-key',
},
}
transport: {
host: 'smtp.example.com',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: 'username',
pass: 'password',
},
}
| Option | Type | Description |
|---|---|---|
to | string | string[] | Recipient email address(es) |
subject | string | Email subject |
template | MailTemplateType | Built-in template name (e.g., 'otp') |
context | MailContext | Template context data |
html | string | Raw HTML content (overrides template) |
text | string | Plain text content |
from | string | Sender email (overrides default) |
cc | string | string[] | CC recipients |
bcc | string | string[] | BCC recipients |
replyTo | string | Reply-to address |
priority | 'high' | 'normal' | 'low' | Email priority |
attachments | any[] | Email attachments |
headers | Record<string, string> | Custom email headers |
The package includes predefined TTL (Time To Live) constants for OTP expiration:
import { TTL } from "@softvence/mail";
// Seconds: '10s', '30s'
// Minutes: '1m', '2m', '3m', '4m', '5m', '10m', '15m', '30m'
// Hours: '1h', '2h', '6h', '12h'
// Days: '1d', '3d', '7d', '14d', '30d'
// Weeks: '1w', '2w', '4w'
// Months: '1mo', '3mo', '6mo'
import { Injectable } from "@nestjs/common";
import { MailService } from "@softvence/mail";
@Injectable()
export class NotificationService {
constructor(private readonly mailService: MailService) {}
async sendWelcomeEmail(email: string, name: string) {
await this.mailService.send({
to: email,
subject: "Welcome to Our Platform!",
html: `<h1>Hello ${name}!</h1><p>Welcome to our platform.</p>`,
});
}
}
import { Injectable } from "@nestjs/common";
import { MailService } from "@softvence/mail";
@Injectable()
export class AuthService {
constructor(private readonly mailService: MailService) {}
async sendOTP(email: string) {
const otp = this.generateOTP(); // Your OTP generation logic
await this.mailService.send({
to: email,
subject: "Your Verification Code",
template: "otp",
context: {
otp,
expire: "5m", // OTP expires in 5 minutes
},
});
}
private generateOTP(): string {
return Math.floor(100000 + Math.random() * 900000).toString();
}
}
async sendBulkNotification(emails: string[], message: string) {
await this.mailService.send({
to: emails, // Array of email addresses
subject: 'Important Notification',
html: `<p>${message}</p>`,
});
}
import * as fs from 'fs';
async sendInvoice(email: string, invoicePath: string) {
await this.mailService.send({
to: email,
subject: 'Your Invoice',
html: '<p>Please find your invoice attached.</p>',
attachments: [
{
filename: 'invoice.pdf',
path: invoicePath,
},
{
filename: 'logo.png',
content: fs.readFileSync('./assets/logo.png'),
},
],
});
}
async sendReport(to: string, cc: string[], bcc: string[]) {
await this.mailService.send({
to,
cc,
bcc,
subject: 'Monthly Report',
html: '<h1>Monthly Report</h1><p>Report content here...</p>',
priority: 'high',
});
}
Create custom templates by providing a templateRenderer function:
import { Module } from "@nestjs/common";
import { MailModule } from "@softvence/mail";
@Module({
imports: [
MailModule.forRoot({
transport: {
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
},
templateRenderer: (type, context) => {
switch (type) {
case "otp":
return `
<div style="font-family: Arial, sans-serif;">
<h1>Your OTP Code</h1>
<p>Your verification code is: <strong>${context.otp}</strong></p>
<p>Expires in: ${context.expire}</p>
</div>
`;
case "welcome":
return `
<div style="font-family: Arial, sans-serif;">
<h1>Welcome ${context.name}!</h1>
<p>Thank you for joining our platform.</p>
</div>
`;
case "password-reset":
return `
<div style="font-family: Arial, sans-serif;">
<h1>Password Reset Request</h1>
<p>Click the link below to reset your password:</p>
<a href="${context.resetLink}">Reset Password</a>
</div>
`;
default:
throw new Error(`Unknown template type: ${type}`);
}
},
}),
],
})
export class AppModule {}
Then use your custom templates:
// Send welcome email
await this.mailService.send({
to: "user@example.com",
subject: "Welcome!",
template: "welcome",
context: { name: "John Doe" },
});
// Send password reset email
await this.mailService.send({
to: "user@example.com",
subject: "Reset Your Password",
template: "password-reset",
context: { resetLink: "https://example.com/reset?token=abc123" },
});
Create a .env file:
EMAIL_SERVICE=gmail
EMAIL_USER=your-email@gmail.com
EMAIL_PASSWORD=your-app-password
EMAIL_FROM="My App" <noreply@myapp.com>
Configure the module:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { MailModule } from "@softvence/mail";
@Module({
imports: [
ConfigModule.forRoot(),
MailModule.forRoot({
transport: {
service: process.env.EMAIL_SERVICE,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
},
defaults: {
from: process.env.EMAIL_FROM,
},
}),
],
})
export class AppModule {}
For dynamic configuration using ConfigService:
import { Module } from "@nestjs/module";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { MailModule } from "@softvence/mail";
@Module({
imports: [
ConfigModule.forRoot(),
{
module: MailModule,
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
transport: {
service: configService.get("EMAIL_SERVICE"),
auth: {
user: configService.get("EMAIL_USER"),
pass: configService.get("EMAIL_PASSWORD"),
},
},
defaults: {
from: configService.get("EMAIL_FROM"),
},
}),
inject: [ConfigService],
},
],
})
export class AppModule {}
import { Controller, Post, Body, BadRequestException } from "@nestjs/common";
import { MailService } from "@softvence/mail";
class SendEmailDto {
to: string;
subject: string;
message: string;
}
class SendOTPDto {
email: string;
}
@Controller("mail")
export class MailController {
constructor(private readonly mailService: MailService) {}
@Post("send")
async sendEmail(@Body() dto: SendEmailDto) {
try {
await this.mailService.send({
to: dto.to,
subject: dto.subject,
html: `<p>${dto.message}</p>`,
});
return {
success: true,
message: "Email sent successfully",
};
} catch (error) {
throw new BadRequestException("Failed to send email");
}
}
@Post("send-otp")
async sendOTP(@Body() dto: SendOTPDto) {
const otp = Math.floor(100000 + Math.random() * 900000).toString();
await this.mailService.send({
to: dto.email,
subject: "Your OTP Code",
template: "otp",
context: {
otp,
expire: "5m",
},
});
return {
success: true,
message: "OTP sent successfully",
// In production, don't return the OTP
// Store it in cache/database instead
};
}
}
The package includes a built-in OTP (One-Time Password) template with a professional design:
await this.mailService.send({
to: "user@example.com",
subject: "Your Verification Code",
template: "otp",
context: {
otp: "123456",
expire: "5m", // Any TTL constant
},
});
The OTP template features:
send(options: MailSendOptions): Promise<void>Sends an email with the specified options.
Parameters:
options - Mail send options (see MailSendOptions table above)Returns: Promise that resolves when email is sent
Throws: BadGatewayException if email sending fails
Example:
await this.mailService.send({
to: "user@example.com",
subject: "Hello",
html: "<p>Hello World!</p>",
});
Never hardcode email credentials. Always use environment variables:
MailModule.forRoot({
transport: {
service: process.env.EMAIL_SERVICE,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
},
});
If using Gmail, create an App Password instead of using your regular password:
Always wrap email sending in try-catch blocks:
try {
await this.mailService.send(options);
this.logger.log("Email sent successfully");
} catch (error) {
this.logger.error("Failed to send email", error);
// Handle error appropriately
}
Create reusable templates for common email types:
templateRenderer: (type, context) => {
const templates = {
otp: generateOtpTemplate,
welcome: generateWelcomeTemplate,
"password-reset": generatePasswordResetTemplate,
};
const template = templates[type];
if (!template) {
throw new Error(`Unknown template: ${type}`);
}
return template(context);
};
Validate email addresses before sending:
import { IsEmail } from "class-validator";
class SendEmailDto {
@IsEmail()
to: string;
subject: string;
message: string;
}
For sending bulk emails, consider using a queue system to avoid blocking:
MailModule.forRoot({
transport: {
/* ... */
},
mailQueue: {
queueName: "mail-queue",
host: "localhost",
port: 6379,
username: "default",
password: "default",
},
});
Test your email configuration in development:
@Injectable()
export class MailService {
async testConnection() {
try {
await this.transporter.verify();
console.log("✅ Email server connection successful");
} catch (error) {
console.error("❌ Email server connection failed", error);
}
}
}
Use a proper "from" address with display name:
defaults: {
from: '"My App Support" <support@myapp.com>',
}
Implement rate limiting to prevent abuse:
import { ThrottlerModule } from '@nestjs/throttler';
@Module({
imports: [
ThrottlerModule.forRoot({
ttl: 60,
limit: 10, // 10 emails per minute
}),
],
})
Log all email activities for debugging and monitoring:
await this.mailService.send(options);
this.logger.log(`Email sent to ${options.to}: ${options.subject}`);
The service throws BadGatewayException when email sending fails:
import { BadGatewayException } from "@nestjs/common";
try {
await this.mailService.send(options);
} catch (error) {
if (error instanceof BadGatewayException) {
console.error("Email service error:", error.message);
// Handle email service errors
}
throw error;
}
All types are exported and can be imported:
import {
MailService,
MailModuleOptions,
MailSendOptions,
MailTemplateType,
MailContext,
TTL,
TTLKey,
} from "@softvence/mail";
transport: {
service: 'gmail',
auth: {
user: 'your-email@gmail.com',
pass: 'your-app-password',
},
}
transport: {
host: 'smtp.sendgrid.net',
port: 587,
auth: {
user: 'apikey',
pass: process.env.SENDGRID_API_KEY,
},
}
transport: {
host: 'smtp.mailgun.org',
port: 587,
auth: {
user: process.env.MAILGUN_USERNAME,
pass: process.env.MAILGUN_PASSWORD,
},
}
transport: {
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
user: process.env.AWS_SES_USERNAME,
pass: process.env.AWS_SES_PASSWORD,
},
}
transport: {
host: 'smtp.office365.com',
port: 587,
secure: false,
auth: {
user: 'your-email@outlook.com',
pass: 'your-password',
},
}
Solution: Use App Passwords instead of regular passwords. Enable 2FA and generate an app password.
Solution: Check your firewall settings and ensure the SMTP port is not blocked.
Solution: Verify your credentials and ensure you're using the correct username/password format for your provider.
Solution:
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Sabbir Hossain Shuvo
For questions and support, please open an issue on GitHub.
Made with ❤️ by the Softvence Team
FAQs
Mail service for softvence
We found that @softvence/mail 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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.