ZZuro Docs

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 mailer

Scaffolds 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.ts
  • lib/mailer.ts: email sender implementation (nodemailer for SMTP, resend for Resend).
  • lib/mail-templates/index.ts: template registry used by sendMail({ 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

  1. Service or route calls sendMail(options).
  2. Mailer resolves template content or uses raw html/text.
  3. Provider client (SMTP or Resend) sends the email.
  4. 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.com

Resend:

RESEND_API_KEY=re_your_api_key
MAIL_FROM=onboarding@resend.dev
  • SMTP_HOST: SMTP server hostname.
  • SMTP_PORT: SMTP server port (465 for secure SSL, 587 for 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 from lib/mail-templates/index.ts.

Errors:

  • NotFoundError: template key not found.
  • BadRequestError: neither html, text, nor template provided.

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 mailer

Example 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:

  1. Go to Google Account → Security → App passwords
  2. Generate a password for "Mail"
  3. 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

On this page