Send email with Node.js (Nodemailer) over SMTP

Nodemailer SMTP transport: createTransport with host/port/secure, auth from environment variables, pooled connections, HTML + text, attachments, verify() and error handling. Same code works for Gmail, SES and Office 365 with a different host.

Install

shell
npm install nodemailer

Complete example

Node.js — STARTTLS on 587, credentials from the environment
import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST || "smtp.queensmtp.com",
  port: Number(process.env.SMTP_PORT || 587),
  secure: Number(process.env.SMTP_PORT) === 465,   // true = implicit TLS (465); false = STARTTLS (587/2525)
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
  pool: true,            // reuse connections for batches
  maxConnections: 3,
  maxMessages: 100,
});

await transporter.verify();   // throws if host/port/auth are wrong — do this at startup

const info = await transporter.sendMail({
  from: '"Your App" <hello@yourdomain.com>',      // verified sending domain
  to: "customer@example.com",
  replyTo: "support@yourdomain.com",
  subject: "Your receipt",
  text: "Thanks for your order #1042",
  html: "<h1>Thanks for your order</h1><p>#1042 is confirmed.</p>",
  // attachments: [{ filename: "invoice-1042.pdf", path: "/tmp/invoice-1042.pdf" }],
});
console.log("queued as", info.messageId, info.response);

What to know

  • secure: true is only for port 465. On 587 leave it false — Nodemailer upgrades with STARTTLS automatically (set requireTLS: true to refuse plaintext).
  • pool: true keeps connections open; call transporter.close() on shutdown.
  • Errors: EAUTH = bad credentials (535), ECONNECTION/ETIMEDOUT = wrong host/port or a blocked port, EENVELOPE = recipient/sender rejected.
  • Serverless (Vercel, Lambda, Cloudflare Workers) often has no outbound SMTP sockets — use the REST API there.

Same code, different provider

Only the host, port and credentials change. The rules per provider are on the SMTP settings hub.

ProviderHostPortUsernamePasswordNote
QueenSMTP smtp.queensmtp.com587 (STARTTLS) / 465 (SSL) / 2525SMTP username from the dashboardSMTP password$5/year + $0.10 per 1,000; 100 free/day
Gmail smtp.gmail.com587 / 465you@gmail.comApp Password500/day, From must be the account
Amazon SES email-smtp.<region>.amazonaws.com587 / 465 / 2587SES SMTP usernameSES SMTP passwordsandbox 200/day until approved
Office 365 smtp.office365.com587user@yourdomain.commailbox passwordSMTP AUTH must be enabled; 30/min
SendGrid smtp.sendgrid.net587 / 465 / 2525apikeyAPI keyno free plan since 2025

Credentials for the example above

Create a free account, verify your sending domain (SPF + DKIM shown in the dashboard), add an SMTP credential, and paste it into the code. 100 emails a day free; $5/year plus $0.10 per 1,000 after that. Prefer HTTPS? The same account has a REST email API.

Get SMTP credentials Test your settings first

Other languages and frameworks

Examples tested 2026-08-30. If a library changed its API, tell us.

Node.js SMTP — questions

The SMTP host presents a certificate your Node trust store does not know. Fix the host/hostname (use the provider's real hostname), not tls.rejectUnauthorized: false.

host smtp.gmail.com, port 587, secure false, auth user = your Gmail address, pass = an App Password. Limits are 500/day.