Mailer
Published Oct 9, 2025 | Updated Mar 13, 2026
Add production-ready email sending with SMTP or Resend
Overview
Add production-ready transactional email sending to your backend.
Install
npx zuro add mailerScaffolds a sendMail() utility, mail templates, and env config for either SMTP (Nodemailer) or Resend.
What This Generates
src/
└ lib/
├ mailer.ts # or mailer.resend.ts based on provider
└ mail-templates/
├ index.ts
└ welcome.tslib/mailer.ts: email sender implementation (nodemailerfor SMTP,resendfor Resend).lib/mail-templates/index.ts: template registry used bysendMail({ template }).lib/mail-templates/welcome.ts: starter HTML template.
Quick Example
import { sendMail } from "../lib/mailer";
await sendMail({
to: "user@example.com",
subject: "Welcome!",
template: "welcome",
data: { name: "Ava" },
});Example request:
await sendMail({
to: "user@example.com",
subject: "Status",
html: "<p>Your job finished.</p>",
});Example response:
{
"success": true
}How It Works
- Service or route calls
sendMail(options). - Mailer resolves template content or uses raw
html/text. - Provider client (SMTP or Resend) sends the email.
- Provider response is returned.
Configuration
SMTP:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASS=your-password
MAIL_FROM=noreply@example.comResend:
RESEND_API_KEY=re_your_api_key
MAIL_FROM=onboarding@resend.devSMTP_HOST: SMTP server hostname.SMTP_PORT: SMTP server port (465for secure SSL,587for STARTTLS).SMTP_USER: SMTP username/login.SMTP_PASS: SMTP password/app password.RESEND_API_KEY: Resend API token.MAIL_FROM: default sender address for all emails.
API Reference
sendMail({ to, subject, html?, text?, template?, data? }): sends an email.to: single email or list of emails.subject: message subject.html/text: raw content payload.template+data: render a registered template fromlib/mail-templates/index.ts.
Errors:
NotFoundError: template key not found.BadRequestError: neitherhtml,text, nortemplateprovided.
Advanced Usage
Multiple recipients:
await sendMail({
to: ["alice@example.com", "bob@example.com"],
subject: "System notice",
text: "Maintenance at 02:00 UTC",
});Custom template:
// src/lib/mail-templates/reset-password.ts
export const resetPassword = (data: Record<string, unknown>) => {
const link = typeof data.link === "string" ? data.link : "#";
return `<a href="${link}">Reset password</a>`;
};// src/lib/mail-templates/index.ts
import { welcome } from "./welcome";
import { resetPassword } from "./reset-password";
export const templates = {
welcome,
"reset-password": resetPassword,
};Provider switch:
# Re-run and choose a different provider in prompt
npx zuro add mailerExample Use Cases
- User onboarding and welcome emails.
- Password reset and verification links.
- Job/queue completion notifications.
- Admin alerts and operational status emails.
Building Templates
Templates are plain functions that return an HTML string. Keep templates in src/lib/mail-templates/ and register them in index.ts.
Password reset template:
// src/lib/mail-templates/reset-password.ts
export const resetPassword = (data: Record<string, unknown>) => {
const link = typeof data.link === "string" ? data.link : "#";
const name = typeof data.name === "string" ? data.name : "there";
return `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h2>Reset your password</h2>
<p>Hi ${name},</p>
<p>Click the button below to reset your password. This link expires in 1 hour.</p>
<a href="${link}" style="
display: inline-block;
background: #10b981;
color: white;
padding: 12px 24px;
border-radius: 6px;
text-decoration: none;
">Reset password</a>
<p style="color: #6b7280; font-size: 14px;">If you didn't request this, ignore this email.</p>
</div>
`;
};Register it:
// src/lib/mail-templates/index.ts
import { welcome } from "./welcome";
import { resetPassword } from "./reset-password";
export const templates = {
welcome,
"reset-password": resetPassword,
};Send it from a route:
await sendMail({
to: user.email,
subject: "Reset your Zuro password",
template: "reset-password",
data: { name: user.name, link: `https://yourapp.com/reset?token=${token}` },
});Using with Auth Module
The most common pattern is sending emails from auth-related routes.
Send welcome email on signup:
// src/controllers/user.controller.ts
import { sendMail } from "../lib/mailer";
export const onUserCreated = async (user: { email: string; name: string }) => {
await sendMail({
to: user.email,
subject: "Welcome to the app",
template: "welcome",
data: { name: user.name },
});
};Troubleshooting
SMTP connection refused / ECONNREFUSED
Your SMTP_HOST is unreachable. Common causes:
- Wrong hostname or port in
.env - Firewall blocking outbound SMTP (common on cloud VMs — use port 587 or a relay service like Resend)
- Local dev: use Mailpit or Mailtrap as a local SMTP sink:
SMTP_HOST=localhost SMTP_PORT=1025 SMTP_USER= SMTP_PASS=
Invalid login with Gmail SMTP
Gmail requires an App Password when 2FA is enabled:
- Go to Google Account → Security → App passwords
- Generate a password for "Mail"
- Use that as
SMTP_PASS, not your Gmail password
Resend API key invalid
Ensure the key starts with re_ and was generated from your Resend dashboard. Keys are environment-specific — check you're not using a test key in production.
Template not found
The template key in sendMail({ template: "..." }) must exactly match a key in src/lib/mail-templates/index.ts. Keys are case-sensitive.
Emails going to spam For production:
- Set up SPF, DKIM, and DMARC records for your sending domain
- Use a reputable sending service (Resend, SendGrid, Postmark) rather than raw SMTP
- Avoid spam trigger words in subject lines