@softvence/mail
v0.0.5
Published
Mail service for softvence
Maintainers
Readme
@softvence/mail
A powerful and flexible NestJS mail service built on top of Nodemailer with built-in template support, custom template rendering, and optional queue integration.
Features
✨ 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
Installation
npm install @softvence/mail
# or
yarn add @softvence/mail
# or
pnpm add @softvence/mailPrerequisites
- Node.js >= 16
- NestJS >= 11.0.0
- Email service credentials (Gmail, SendGrid, SMTP, etc.)
Quick Start
1. Import the Module
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" <[email protected]>',
},
}),
],
})
export class AppModule {}2. Use in Your Service/Controller
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",
},
});
}
}Configuration
MailModuleOptions
| 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 |
Transport Configuration
The transport option accepts any valid Nodemailer transport configuration. Here are common examples:
Gmail
transport: {
service: 'gmail',
auth: {
user: '[email protected]',
pass: 'your-app-password', // Use App Password, not regular password
},
}SendGrid
transport: {
host: 'smtp.sendgrid.net',
port: 587,
auth: {
user: 'apikey',
pass: 'your-sendgrid-api-key',
},
}Custom SMTP
transport: {
host: 'smtp.example.com',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: 'username',
pass: 'password',
},
}MailSendOptions
| 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 |
TTL Constants
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'Usage Examples
Basic Email Sending
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>`,
});
}
}Using Built-in OTP Template
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();
}
}Sending to Multiple Recipients
async sendBulkNotification(emails: string[], message: string) {
await this.mailService.send({
to: emails, // Array of email addresses
subject: 'Important Notification',
html: `<p>${message}</p>`,
});
}Email with Attachments
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'),
},
],
});
}Email with CC and BCC
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',
});
}Custom Template Renderer
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: "[email protected]",
subject: "Welcome!",
template: "welcome",
context: { name: "John Doe" },
});
// Send password reset email
await this.mailService.send({
to: "[email protected]",
subject: "Reset Your Password",
template: "password-reset",
context: { resetLink: "https://example.com/reset?token=abc123" },
});Using Environment Variables
Create a .env file:
EMAIL_SERVICE=gmail
[email protected]
EMAIL_PASSWORD=your-app-password
EMAIL_FROM="My App" <[email protected]>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 {}Async Configuration
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 {}Complete Controller Example
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
};
}
}Built-in Templates
OTP Template
The package includes a built-in OTP (One-Time Password) template with a professional design:
await this.mailService.send({
to: "[email protected]",
subject: "Your Verification Code",
template: "otp",
context: {
otp: "123456",
expire: "5m", // Any TTL constant
},
});The OTP template features:
- Clean, responsive design
- Prominent OTP display
- Expiration time notice
- Professional styling
- Mobile-friendly layout
API Reference
MailService
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: "[email protected]",
subject: "Hello",
html: "<p>Hello World!</p>",
});Best Practices
1. Use Environment Variables
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,
},
},
});2. Use App Passwords for Gmail
If using Gmail, create an App Password instead of using your regular password:
- Enable 2-Factor Authentication
- Go to Google Account → Security → App Passwords
- Generate a new app password
- Use this password in your configuration
3. Handle Errors Gracefully
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
}4. Use Templates for Consistency
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);
};5. Validate Email Addresses
Validate email addresses before sending:
import { IsEmail } from "class-validator";
class SendEmailDto {
@IsEmail()
to: string;
subject: string;
message: string;
}6. Use Queue for Bulk Emails
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",
},
});7. Test Email Configuration
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);
}
}
}8. Use Proper From Addresses
Use a proper "from" address with display name:
defaults: {
from: '"My App Support" <[email protected]>',
}9. Implement Rate Limiting
Implement rate limiting to prevent abuse:
import { ThrottlerModule } from '@nestjs/throttler';
@Module({
imports: [
ThrottlerModule.forRoot({
ttl: 60,
limit: 10, // 10 emails per minute
}),
],
})10. Log Email Activities
Log all email activities for debugging and monitoring:
await this.mailService.send(options);
this.logger.log(`Email sent to ${options.to}: ${options.subject}`);Error Handling
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;
}TypeScript Support
All types are exported and can be imported:
import {
MailService,
MailModuleOptions,
MailSendOptions,
MailTemplateType,
MailContext,
TTL,
TTLKey,
} from "@softvence/mail";Common Email Providers
Gmail
transport: {
service: 'gmail',
auth: {
user: '[email protected]',
pass: 'your-app-password',
},
}SendGrid
transport: {
host: 'smtp.sendgrid.net',
port: 587,
auth: {
user: 'apikey',
pass: process.env.SENDGRID_API_KEY,
},
}Mailgun
transport: {
host: 'smtp.mailgun.org',
port: 587,
auth: {
user: process.env.MAILGUN_USERNAME,
pass: process.env.MAILGUN_PASSWORD,
},
}Amazon SES
transport: {
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
user: process.env.AWS_SES_USERNAME,
pass: process.env.AWS_SES_PASSWORD,
},
}Outlook/Office 365
transport: {
host: 'smtp.office365.com',
port: 587,
secure: false,
auth: {
user: '[email protected]',
pass: 'your-password',
},
}Troubleshooting
Gmail "Less Secure Apps" Error
Solution: Use App Passwords instead of regular passwords. Enable 2FA and generate an app password.
Connection Timeout
Solution: Check your firewall settings and ensure the SMTP port is not blocked.
Authentication Failed
Solution: Verify your credentials and ensure you're using the correct username/password format for your provider.
Emails Going to Spam
Solution:
- Use a verified domain
- Set up SPF, DKIM, and DMARC records
- Use a reputable email service provider
- Avoid spam trigger words in subject/content
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © Sabbir Hossain Shuvo
Repository
- GitHub: softvence-agency/softvence
- Issues: Report a bug
Support
For questions and support, please open an issue on GitHub.
Made with ❤️ by the Softvence Team
