
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.
Laravel-style mailer for Node.js — Mail.fake() + Mail.assertSent() for zero-setup email testing, provider switching via env var, works with Express/Fastify/any framework.
AdonisJS mailer, but framework-agnostic — works with Express, Fastify, or any Node.js app.
Test emails without Mailtrap or mocking setup. Switch providers with one env var. Send with Mail.to(user).send(new WelcomeEmail()).
// Zero-setup email testing — no SMTP server, no network, no mocks.
Mail.fake();
await Mail.to('user@example.com').send(new WelcomeEmail(user));
Mail.assertSent(WelcomeEmail, (mail) => mail.hasTo('user@example.com'));
| Feature | laramail | nodemailer | @sendgrid/mail | resend |
|---|---|---|---|---|
Mail.fake() + Mail.assertSent() | ✅ | ❌ | ❌ | ❌ |
| Mailable classes (OOP email objects) | ✅ | ❌ | ❌ | ❌ |
Switch provider via MAIL_DRIVER env | ✅ | ❌ | ❌ | ❌ |
| Provider failover (auto chain) | ✅ | ❌ | ❌ | ❌ |
| Queue support (Bull / BullMQ) | ✅ | ❌ | ❌ | ❌ |
| Rate limiting (sliding window) | ✅ | ❌ | ❌ | ❌ |
Staging redirect (Mail.alwaysTo()) | ✅ | ❌ | ❌ | ❌ |
| Works with Express, Fastify, any framework | ✅ | ✅ | ✅ | ✅ |
npm install laramail
Add providers and engines as needed — only install what you use:
npm install @sendgrid/mail # SendGrid
npm install @aws-sdk/client-ses # AWS SES
npm install mailgun.js form-data # Mailgun
npm install resend # Resend
npm install postmark # Postmark
npm install handlebars # Template engine
npm install ejs # Template engine
npm install pug # Template engine
npm install marked juice # Markdown emails
npm install bullmq # Queue support
import { Mail } from 'laramail';
// 1. Configure once
Mail.configure({
default: 'smtp',
from: { address: 'noreply@example.com', name: 'My App' },
mailers: {
smtp: {
driver: 'smtp',
host: process.env.SMTP_HOST,
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
},
},
});
// 2. Send emails
await Mail.to('user@example.com')
.subject('Welcome!')
.html('<h1>Hello World!</h1>')
.send();
Switch providers by changing the driver — no code changes:
mailers: {
sendgrid: { driver: 'sendgrid', apiKey: process.env.SENDGRID_API_KEY },
ses: { driver: 'ses', region: 'us-east-1', accessKeyId: '...', secretAccessKey: '...' },
mailgun: { driver: 'mailgun', domain: '...', apiKey: '...' },
resend: { driver: 'resend', apiKey: '...' },
postmark: { driver: 'postmark', serverToken: '...' },
}
Create reusable, testable email classes — just like Laravel:
import { Mailable } from 'laramail';
class WelcomeEmail extends Mailable {
constructor(private user: { name: string }) {
super();
}
build() {
return this
.subject(`Welcome, ${this.user.name}!`)
.html(`<h1>Hello ${this.user.name}!</h1>`);
}
}
await Mail.to('user@example.com').send(new WelcomeEmail(user));
Test emails without sending — Laravel-style assertions:
beforeEach(() => Mail.fake());
afterEach(() => Mail.restore());
it('sends welcome email', async () => {
await Mail.to('user@example.com').send(new WelcomeEmail('John'));
Mail.assertSent(WelcomeEmail);
Mail.assertSent(WelcomeEmail, (mail) =>
mail.hasTo('user@example.com') && mail.subjectContains('Welcome')
);
Mail.assertSentCount(WelcomeEmail, 1);
Mail.assertNotSent(PasswordResetEmail);
});
Assertions:
| Method | What it checks |
|---|---|
Mail.assertSent(Klass) | Class was sent at least once |
Mail.assertSent(Klass, fn) | Class was sent and fn returns true for at least one message |
Mail.assertSentCount(Klass, n) | Class was sent exactly N times |
Mail.assertNotSent(Klass) | Class was NOT sent |
Mail.assertNothingSent() | No emails sent at all |
AssertableMessage helpers (use inside the fn callback):
| Method | What it checks |
|---|---|
hasTo(email) | Recipient address |
hasCc(email) | CC recipient |
hasBcc(email) | BCC recipient |
hasSubject(subject) | Exact subject match |
subjectContains(text) | Subject partial match (case-insensitive) |
htmlContains(text) | HTML body content |
textContains(text) | Plain text content |
hasAttachment(filename) | Attachment by filename |
hasPriority(level) | 'high', 'normal', or 'low' |
hasHeader(name, value?) | Email header |
Full API including queue assertions → docs/testing.md
Use Handlebars, EJS, or Pug for email templates:
Mail.configure({
// ...mailer config
templates: {
engine: 'handlebars', // or 'ejs' or 'pug'
viewsPath: './views/emails',
cache: true,
},
});
await Mail.to('user@example.com')
.subject('Welcome!')
.template('welcome')
.data({ name: 'John', appName: 'My App' })
.send();
Write emails in Markdown with built-in components:
import { MarkdownMailable } from 'laramail';
class WelcomeEmail extends MarkdownMailable {
build(): this {
return this.subject('Welcome!').markdown(`
# Hello, {{name}}!
Thanks for joining.
[button url="https://example.com" color="primary"]Get Started[/button]
[panel]Need help? Contact support@example.com[/panel]
`, { name: this.user.name });
}
}
Components: [button url="..." color="primary|success|error"], [panel]...[/panel], [table]...[/table]
Automatic failover to backup providers with retries and monitoring:
Mail.configure({
default: 'smtp',
mailers: { smtp: { ... }, sendgrid: { ... }, ses: { ... } },
failover: {
chain: ['sendgrid', 'ses'],
maxRetriesPerProvider: 2,
retryDelay: 1000,
onFailover: (event) => console.log(`${event.failedMailer} → ${event.nextMailer}`),
},
});
Background sending with Bull or BullMQ:
Mail.configure({
// ...mailer config
queue: {
driver: 'bullmq',
connection: { host: 'localhost', port: 6379 },
retries: 3,
backoff: { type: 'exponential', delay: 1000 },
},
});
await Mail.to('user@example.com').queue(new WelcomeEmail(user)); // Immediate
await Mail.to('user@example.com').later(60, new WelcomeEmail(user)); // 60s delay
await Mail.to('user@example.com').at(scheduledDate, new WelcomeEmail(user)); // Scheduled
await Mail.processQueue(); // Worker
Hook into the email lifecycle:
Mail.onSending((event) => {
console.log(`Sending to ${event.options.to}`);
event.options.headers = { ...event.options.headers, 'X-Tracking': '123' };
// return false to cancel
});
Mail.onSent((event) => console.log(`Sent! ID: ${event.response.messageId}`));
Mail.onFailed((event) => console.error(`Failed: ${event.error}`));
Per-provider sliding window rate limiting:
Mail.configure({
// ...mailer config
rateLimit: { maxPerWindow: 100, windowMs: 60000 },
});
// Per-mailer override
mailers: {
smtp: {
driver: 'smtp', host: '...',
rateLimit: { maxPerWindow: 10, windowMs: 1000 },
},
}
When exceeded, returns { success: false } — never throws.
Use the log driver during development — emails are printed to console instead of sent:
Mail.configure({
default: 'log',
from: { address: 'dev@example.com', name: 'Dev' },
mailers: {
log: { driver: 'log' },
},
});
await Mail.to('user@example.com').subject('Test').html('<p>Hi</p>').send();
// Prints formatted email to console — no SMTP needed
Register your own mail provider with Mail.extend():
import { Mail } from 'laramail';
Mail.extend('custom-api', (config) => ({
async send(options) {
const res = await fetch('https://api.example.com/send', {
method: 'POST',
body: JSON.stringify(options),
});
return { success: res.ok, messageId: (await res.json()).id };
},
}));
Mail.configure({
default: 'api',
from: { address: 'noreply@example.com', name: 'App' },
mailers: { api: { driver: 'custom-api' } },
});
Redirect all emails to a single address — perfect for staging environments:
Mail.alwaysTo('dev-team@example.com');
// All emails now go to dev-team@example.com, CC/BCC cleared
// Call Mail.alwaysTo(undefined) to disable
// Or via config:
Mail.configure({
// ...
alwaysTo: 'dev-team@example.com',
});
Preview rendered emails without sending:
const preview = await Mail.to('user@example.com')
.subject('Hello')
.html('<p>Hi</p>')
.priority('high')
.preview();
console.log(preview.html, preview.headers);
await Mail.to('user@example.com')
.subject('Complete Example')
.html('<h1>Hello!</h1>')
.text('Hello!')
.from('custom@example.com')
.cc(['manager@example.com'])
.bcc('archive@example.com')
.replyTo('support@example.com')
.attachments([{ filename: 'report.pdf', path: './report.pdf' }])
.priority('high')
.headers({ 'X-Custom': 'value' })
.send();
npx laramail queue:work # Process queued emails
npx laramail queue:status # Show queue job counts
npx laramail queue:clear -s failed # Clear failed jobs
npx laramail queue:retry # Retry failed jobs
npx laramail preview --mailable ./src/mail/WelcomeEmail.ts
npx laramail send:test --to you@example.com
npx laramail make:mailable WelcomeEmail
npx laramail make:mailable NewsletterEmail --markdown
npx laramail config:check # Validate configuration
npx laramail config:check --test # Test provider connections
// laramail.config.ts
import { defineConfig } from 'laramail';
export default defineConfig({
default: 'smtp',
from: { address: 'noreply@example.com', name: 'My App' },
mailers: {
smtp: {
driver: 'smtp',
host: process.env.SMTP_HOST,
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
},
},
});
If you've used Laravel's Mail, you know how elegant it is:
// Laravel (PHP)
Mail::to($user->email)->send(new WelcomeEmail($user));
laramail brings this same elegance to Node.js:
// laramail (TypeScript)
await Mail.to(user.email).send(new WelcomeEmail(user));
Lightweight by design — base package is ~25MB (SMTP only). Add providers as needed.
git clone https://github.com/impruthvi/laramail.git
cd laramail
npm install
npm run build
npm test
See CONTRIBUTING.md for guidelines.
MIT © Pruthvi
If laramail helps you, give it a star! It helps others discover the project.
FAQs
Laravel-style mailer for Node.js — Mail.fake() + Mail.assertSent() for zero-setup email testing, provider switching via env var, works with Express/Fastify/any framework.
We found that laramail 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.