for sending emails in different ways
npm install @devbro/neko-mailerA powerful and flexible email sending library for Node.js applications with support for multiple providers (SMTP, SendGrid, AWS SES, Mailgun, Postmark) and advanced features like templating, attachments, queuing, and monitoring.


- Installation
- Features
- Quick Start
- Core Concepts
- Email Providers
- Templating
- Attachments
- Advanced Features
- Real-World Examples
- Best Practices
- TypeScript Support
- API Reference
- Troubleshooting
- Contributing
- Related Packages
``bash`
npm install @devbro/neko-mailer
Depending on your email provider, install the appropriate package:
`bashSMTP (built-in with nodemailer)
npm install nodemailer
Features
- Multiple Providers: SMTP, SendGrid, AWS SES, Mailgun, Postmark
- Template Support: Handlebars, EJS, Pug, and custom template engines
- Attachments: Files, buffers, streams, and inline images
- Queue Integration: Background email sending with retry logic
- Email Validation: Built-in email address validation
- HTML & Plain Text: Automatic plain text generation from HTML
- Internationalization: Multi-language email support
- Tracking: Open and click tracking (provider-dependent)
- Testing: Preview emails without sending (development mode)
- Type Safety: Full TypeScript support with generics
- Error Handling: Comprehensive error types and retry strategies
Quick Start
$3
`typescript
import { NekoMailer, SMTPProvider } from "@devbro/neko-mailer";// Initialize mailer with SMTP
const mailer = new NekoMailer({
provider: new SMTPProvider({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: "your-email@gmail.com",
pass: "your-app-password",
},
}),
from: {
name: "My App",
email: "noreply@myapp.com",
},
});
// Send a simple email
await mailer.send({
to: "user@example.com",
subject: "Welcome to My App",
html: "
Welcome!
Thanks for signing up.
",
text: "Welcome! Thanks for signing up.",
});
`$3
`typescript
import { NekoMailer, SendGridProvider } from "@devbro/neko-mailer";const mailer = new NekoMailer({
provider: new SendGridProvider({
apiKey: process.env.SENDGRID_API_KEY!,
}),
from: {
name: "My App",
email: "noreply@myapp.com",
},
});
await mailer.send({
to: "user@example.com",
subject: "Password Reset",
html: '
Click here to reset your password.
',
data: {
resetUrl: "https://myapp.com/reset/token123",
},
});
`Core Concepts
$3
`typescript
interface MailerConfig {
provider: EmailProvider; // Email provider instance
from: EmailAddress; // Default sender
replyTo?: EmailAddress; // Default reply-to address
templateEngine?: TemplateEngine; // Template engine for rendering
queue?: QueueAdapter; // Queue for background sending
development?: boolean; // Development mode (preview only)
trackOpens?: boolean; // Enable open tracking
trackClicks?: boolean; // Enable click tracking
}interface EmailAddress {
email: string;
name?: string;
}
`$3
`typescript
interface EmailMessage {
to: string | string[] | EmailAddress | EmailAddress[];
subject: string;
html?: string; // HTML content
text?: string; // Plain text content
from?: EmailAddress; // Override default sender
cc?: string | string[]; // CC recipients
bcc?: string | string[]; // BCC recipients
replyTo?: EmailAddress; // Reply-to address
attachments?: Attachment[]; // File attachments
headers?: Record; // Custom headers
priority?: "high" | "normal" | "low";
data?: Record; // Template variables
template?: string; // Template name
tags?: string[]; // Email tags (for tracking)
}
`Email Providers
$3
`typescript
import { SMTPProvider } from "@devbro/neko-mailer";const smtp = new SMTPProvider({
host: "smtp.example.com",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: "username",
pass: "password",
},
pool: true, // Use pooled connections
maxConnections: 5,
rateDelta: 1000,
rateLimit: 5, // Max 5 emails per second
});
`$3
`typescript
import { SendGridProvider } from "@devbro/neko-mailer";const sendgrid = new SendGridProvider({
apiKey: process.env.SENDGRID_API_KEY!,
sandboxMode: false, // Enable for testing without sending
ipPoolName: "transactional", // Optional IP pool
});
`$3
`typescript
import { SESProvider } from "@devbro/neko-mailer";const ses = new SESProvider({
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
configurationSetName: "my-config-set", // Optional
});
`$3
`typescript
import { MailgunProvider } from "@devbro/neko-mailer";const mailgun = new MailgunProvider({
apiKey: process.env.MAILGUN_API_KEY!,
domain: "mg.example.com",
host: "api.eu.mailgun.net", // Use EU endpoint if needed
});
`$3
`typescript
import { PostmarkProvider } from "@devbro/neko-mailer";const postmark = new PostmarkProvider({
serverToken: process.env.POSTMARK_SERVER_TOKEN!,
messageStream: "outbound", // 'outbound' or 'broadcasts'
});
`Templating
$3
`typescript
import { NekoMailer, HandlebarsEngine } from "@devbro/neko-mailer";
import path from "path";const mailer = new NekoMailer({
provider: smtpProvider,
from: { email: "noreply@myapp.com" },
templateEngine: new HandlebarsEngine({
templatesDir: path.join(__dirname, "templates"),
partialsDir: path.join(__dirname, "templates/partials"),
defaultLayout: "main",
}),
});
// Send with template
await mailer.send({
to: "user@example.com",
subject: "Welcome {{username}}!",
template: "welcome", // loads templates/welcome.hbs
data: {
username: "John",
activationUrl: "https://myapp.com/activate/xyz",
},
});
`Template File (
templates/welcome.hbs):`handlebars
Welcome, {{username}}!
Thanks for joining our platform.
{{> footer}}
`$3
`typescript
import { EJSEngine } from "@devbro/neko-mailer";const mailer = new NekoMailer({
provider: smtpProvider,
from: { email: "noreply@myapp.com" },
templateEngine: new EJSEngine({
templatesDir: path.join(__dirname, "templates"),
}),
});
`$3
`typescript
import { TemplateEngine } from "@devbro/neko-mailer";class MyTemplateEngine implements TemplateEngine {
async render(template: string, data: Record): Promise {
// Your custom rendering logic
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || "");
}
}
const mailer = new NekoMailer({
provider: smtpProvider,
from: { email: "noreply@myapp.com" },
templateEngine: new MyTemplateEngine(),
});
`Attachments
$3
`typescript
await mailer.send({
to: "user@example.com",
subject: "Invoice #12345",
html: "Please find your invoice attached.
",
attachments: [
{
filename: "invoice.pdf",
path: "/path/to/invoice.pdf",
},
{
filename: "logo.png",
path: "/path/to/logo.png",
cid: "logo", // For inline images
},
],
});
`$3
`typescript
const pdfBuffer = await generateInvoicePDF(orderId);await mailer.send({
to: "user@example.com",
subject: "Invoice #12345",
html: "
Your invoice is attached.
",
attachments: [
{
filename: "invoice.pdf",
content: pdfBuffer,
contentType: "application/pdf",
},
],
});
`$3
`typescript
await mailer.send({
to: "user@example.com",
subject: "Newsletter",
html: Check out our latest updates!
,
attachments: [
{
filename: "header.jpg",
path: "/path/to/header.jpg",
cid: "header", // Referenced in HTML as src="cid:header"
},
],
});
`Advanced Features
$3
`typescript
import { NekoMailer } from "@devbro/neko-mailer";
import { RedisQueue } from "@devbro/neko-queue";const queue = new RedisQueue({
connection: {
host: "localhost",
port: 6379,
},
});
const mailer = new NekoMailer({
provider: smtpProvider,
from: { email: "noreply@myapp.com" },
queue: queue,
});
// Emails are now sent in background
await mailer.send({
to: "user@example.com",
subject: "Welcome!",
html: "
Welcome to our platform
",
});
// Returns immediately, email queued for sending
`$3
`typescript
const recipients = [
{ email: "user1@example.com", name: "User 1" },
{ email: "user2@example.com", name: "User 2" },
{ email: "user3@example.com", name: "User 3" },
];// Send to multiple recipients with personalization
for (const recipient of recipients) {
await mailer.send({
to: recipient,
subject:
Hello ${recipient.name},
template: "newsletter",
data: {
name: recipient.name,
unsubscribeUrl: https://myapp.com/unsubscribe/${recipient.email},
},
});
}// Or use batch send (provider-dependent)
await mailer.sendBatch({
recipients: recipients,
subject: "Newsletter",
template: "newsletter",
data: (recipient) => ({
name: recipient.name,
unsubscribeUrl:
https://myapp.com/unsubscribe/${recipient.email},
}),
});
`$3
`typescript
import { validateEmail, validateEmailList } from "@devbro/neko-mailer";// Validate single email
const isValid = validateEmail("user@example.com"); // true
const isInvalid = validateEmail("invalid-email"); // false
// Validate list of emails
const emails = ["user1@example.com", "invalid", "user2@example.com"];
const { valid, invalid } = validateEmailList(emails);
console.log(valid); // ['user1@example.com', 'user2@example.com']
console.log(invalid); // ['invalid']
`$3
`typescript
// Preview emails without actually sending
const mailer = new NekoMailer({
provider: smtpProvider,
from: { email: "noreply@myapp.com" },
development: true, // Enables preview mode
});// Email will be logged to console instead of sent
await mailer.send({
to: "user@example.com",
subject: "Test Email",
html: "
This is a test
",
});// Or use preview explicitly
const preview = await mailer.preview({
to: "user@example.com",
subject: "Test Email",
template: "welcome",
data: { username: "John" },
});
console.log(preview.html);
console.log(preview.text);
`$3
`typescript
import { NekoMailer, RetryStrategy } from "@devbro/neko-mailer";const mailer = new NekoMailer({
provider: smtpProvider,
from: { email: "noreply@myapp.com" },
retry: new RetryStrategy({
maxAttempts: 3,
delay: 1000, // 1 second
backoff: "exponential", // exponential or linear
}),
});
try {
await mailer.send({
to: "user@example.com",
subject: "Important",
html: "
This will retry on failure
",
});
} catch (error) {
console.error("Failed after 3 attempts:", error);
}
`$3
`typescript
await mailer.send({
to: "user@example.com",
subject: "Custom Headers",
html: "Email with custom headers
",
headers: {
"X-Campaign-ID": "summer-2026",
"X-User-ID": "12345",
"List-Unsubscribe": "",
},
});
`Real-World Examples
$3
`typescript
// Controller
async function registerUser(email: string, username: string) {
const user = await createUser(email, username);
const token = generateActivationToken(user.id); await mailer.send({
to: { email: user.email, name: username },
subject: "Activate Your Account",
template: "activation",
data: {
username: username,
activationUrl:
https://myapp.com/activate?token=${token},
expiresIn: "24 hours",
},
tags: ["registration", "activation"],
}); return user;
}
`Template (
templates/activation.hbs):`handlebars
Welcome, {{username}}!
Thanks for signing up. Please activate your account within
{{expiresIn}}.
If the button doesn't work, copy and paste this link:
{{activationUrl}}
`$3
`typescript
async function sendPasswordReset(email: string) {
const user = await findUserByEmail(email);
if (!user) {
// Don't reveal if email exists
return;
} const token = generateResetToken(user.id);
await mailer.send({
to: user.email,
subject: "Reset Your Password",
template: "password-reset",
data: {
username: user.username,
resetUrl:
https://myapp.com/reset-password?token=${token},
expiresIn: "1 hour",
ipAddress: getCurrentIP(),
timestamp: new Date().toLocaleString(),
},
priority: "high",
tags: ["security", "password-reset"],
});
}
`$3
`typescript
async function sendOrderConfirmation(orderId: string) {
const order = await getOrder(orderId);
const invoice = await generateInvoicePDF(orderId); await mailer.send({
to: {
email: order.customer.email,
name: order.customer.name,
},
subject:
Order Confirmation #${order.id},
template: "order-confirmation",
data: {
customerName: order.customer.name,
orderId: order.id,
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,
tax: order.tax,
shipping: order.shipping,
total: order.total,
trackingUrl: order.trackingUrl,
},
attachments: [
{
filename: invoice-${order.id}.pdf,
content: invoice,
contentType: "application/pdf",
},
],
tags: ["order", "confirmation", "transactional"],
});
}
`$3
`typescript
async function sendWeeklyNewsletter() {
const subscribers = await getActiveSubscribers();
const latestPosts = await getLatestBlogPosts(5); for (const subscriber of subscribers) {
await mailer.send({
to: subscriber.email,
subject: "Your Weekly Update",
template: "newsletter",
data: {
subscriberName: subscriber.name,
posts: latestPosts.map((post) => ({
title: post.title,
excerpt: post.excerpt,
url:
https://myapp.com/blog/${post.slug},
image: post.featuredImage,
})),
unsubscribeUrl: https://myapp.com/unsubscribe?token=${subscriber.unsubscribeToken},
},
tags: ["newsletter", "weekly"],
trackOpens: true,
trackClicks: true,
});
}
}
`Best Practices
$3
`typescript
// config/mailer.ts
import {
NekoMailer,
SMTPProvider,
SendGridProvider,
} from "@devbro/neko-mailer";export function createMailer() {
const isDevelopment = process.env.NODE_ENV === "development";
let provider;
if (process.env.SENDGRID_API_KEY) {
provider = new SendGridProvider({
apiKey: process.env.SENDGRID_API_KEY,
});
} else {
provider = new SMTPProvider({
host: process.env.SMTP_HOST!,
port: parseInt(process.env.SMTP_PORT || "587"),
auth: {
user: process.env.SMTP_USER!,
pass: process.env.SMTP_PASS!,
},
});
}
return new NekoMailer({
provider,
from: {
name: process.env.EMAIL_FROM_NAME || "My App",
email: process.env.EMAIL_FROM_ADDRESS!,
},
development: isDevelopment,
});
}
export const mailer = createMailer();
`$3
`typescript
// services/email.service.ts
import { mailer } from "../config/mailer";
import { User } from "../models/User";export class EmailService {
async sendWelcomeEmail(user: User) {
return mailer.send({
to: { email: user.email, name: user.name },
subject: "Welcome to Our Platform",
template: "welcome",
data: { username: user.name },
tags: ["onboarding", "welcome"],
});
}
async sendPasswordReset(user: User, token: string) {
return mailer.send({
to: user.email,
subject: "Reset Your Password",
template: "password-reset",
data: {
username: user.name,
resetUrl:
https://myapp.com/reset?token=${token},
},
priority: "high",
tags: ["security"],
});
} async sendOrderConfirmation(order: Order) {
const invoice = await this.generateInvoice(order);
return mailer.send({
to: order.customer.email,
subject:
Order #${order.id} Confirmed,
template: "order-confirmation",
data: { order },
attachments: [
{
filename: invoice-${order.id}.pdf,
content: invoice,
},
],
tags: ["order", "transactional"],
});
} private async generateInvoice(order: Order): Promise {
// Generate PDF invoice
return Buffer.from("...");
}
}
export const emailService = new EmailService();
`$3
`typescript
import { MailerError, ProviderError } from "@devbro/neko-mailer";try {
await mailer.send({
to: "user@example.com",
subject: "Test",
html: "
Test email
",
});
} catch (error) {
if (error instanceof ProviderError) {
console.error("Provider error:", error.provider, error.code);
// Log to monitoring service
await logger.error("Email provider failed", {
provider: error.provider,
code: error.code,
message: error.message,
});
} else if (error instanceof MailerError) {
console.error("Mailer error:", error.message);
} else {
throw error;
}
}
`$3
`typescript
// tests/email.test.ts
import { NekoMailer, MockProvider } from "@devbro/neko-mailer";
import { emailService } from "../services/email.service";describe("EmailService", () => {
let mockProvider: MockProvider;
let mailer: NekoMailer;
beforeEach(() => {
mockProvider = new MockProvider();
mailer = new NekoMailer({
provider: mockProvider,
from: { email: "test@example.com" },
});
});
it("should send welcome email", async () => {
const user = { email: "user@example.com", name: "John" };
await mailer.send({
to: user.email,
subject: "Welcome",
template: "welcome",
data: { username: user.name },
});
expect(mockProvider.sentEmails).toHaveLength(1);
expect(mockProvider.sentEmails[0].to).toBe(user.email);
expect(mockProvider.sentEmails[0].subject).toBe("Welcome");
});
});
`$3
`typescript
import pLimit from "p-limit";const limit = pLimit(10); // Max 10 concurrent emails
async function sendBulkEmails(recipients: string[]) {
const promises = recipients.map((email) =>
limit(() =>
mailer.send({
to: email,
subject: "Newsletter",
template: "newsletter",
data: {
/ ... /
},
}),
),
);
const results = await Promise.allSettled(promises);
const succeeded = results.filter((r) => r.status === "fulfilled").length;
const failed = results.filter((r) => r.status === "rejected").length;
console.log(
Sent: ${succeeded}, Failed: ${failed});
}
`TypeScript Support
$3
`typescript
interface WelcomeEmailData {
username: string;
activationUrl: string;
expiresIn: string;
}interface OrderEmailData {
orderId: string;
items: Array<{
name: string;
quantity: number;
price: number;
}>;
total: number;
}
// Type-safe email sending
async function sendTypedEmail(to: string, template: string, data: T) {
return mailer.send({
to,
subject: "Email",
template,
data,
});
}
// Usage with type checking
await sendTypedEmail("user@example.com", "welcome", {
username: "John",
activationUrl: "https://...",
expiresIn: "24 hours",
});
`$3
`typescript
import { EmailProvider, EmailMessage, SendResult } from "@devbro/neko-mailer";class CustomProvider implements EmailProvider {
async send(message: EmailMessage): Promise {
// Your implementation
return {
messageId: "custom-id",
accepted: [message.to as string],
rejected: [],
pending: [],
};
}
async verify(): Promise {
// Verify provider configuration
return true;
}
}
`API Reference
$3
#### Constructor
`typescript
new NekoMailer(config: MailerConfig)
`#### Methods
-
send(message: EmailMessage): Promise - Send an email
- sendBatch(options: BatchOptions): Promise - Send to multiple recipients
- preview(message: EmailMessage): Promise - Preview email without sending
- verify(): Promise - Verify provider configuration$3
-
SMTPProvider - Standard SMTP email sending
- SendGridProvider - SendGrid API integration
- SESProvider - AWS Simple Email Service
- MailgunProvider - Mailgun API integration
- PostmarkProvider - Postmark API integration
- MockProvider - Testing provider (captures emails without sending)$3
-
HandlebarsEngine - Handlebars template rendering
- EJSEngine - EJS template rendering
- PugEngine - Pug template rendering$3
-
validateEmail(email: string): boolean - Validate email address
- validateEmailList(emails: string[]): ValidationResult - Validate list of emails
- parseEmailAddress(address: string): EmailAddress - Parse email with nameTroubleshooting
$3
SMTP Authentication Failed
`typescript
// Use app-specific passwords for Gmail
// Enable "Less secure app access" or use OAuth2
const smtp = new SMTPProvider({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: "your-email@gmail.com",
pass: "app-specific-password", // Not your regular password
},
});
`Rate Limiting
`typescript
// Add delays between emails or use queue
import { delay } from "@devbro/neko-helper";for (const recipient of recipients) {
await mailer.send({
/ ... /
});
await delay(100); // 100ms between emails
}
`Templates Not Found
`typescript
// Ensure correct path to templates directory
import path from "path";const templateEngine = new HandlebarsEngine({
templatesDir: path.resolve(__dirname, "../templates"),
// Use absolute paths or resolve relative to __dirname
});
`Attachments Too Large
`typescript
// Check provider limits (usually 10-25MB)
// For large files, use links instead
await mailer.send({
to: "user@example.com",
subject: "Large File",
html: Download your file: here
,
});
`Contributing
We welcome contributions! Please see our Contributing Guide for details.
`bash
Clone the repository
git clone https://github.com/devbro1/pashmak.git
cd pashmak/neko-mailerInstall dependencies
npm installRun tests
npm testBuild
npm run build
``- @devbro/neko-queue - Background job processing for email queuing
- @devbro/neko-logger - Logging for email tracking and debugging
- @devbro/neko-config - Configuration management for email settings
- @devbro/neko-storage - File storage for email attachments
- @devbro/pashmak - Full-stack TypeScript framework
MIT
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
- 📖 Documentation: https://devbro1.github.io/pashmak/