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
npm install nodemailer
Complete example
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.
| Provider | Host | Port | Username | Password | Note |
|---|---|---|---|---|---|
| QueenSMTP | smtp.queensmtp.com | 587 (STARTTLS) / 465 (SSL) / 2525 | SMTP username from the dashboard | SMTP password | $5/year + $0.10 per 1,000; 100 free/day |
| Gmail | smtp.gmail.com | 587 / 465 | you@gmail.com | App Password | 500/day, From must be the account |
| Amazon SES | email-smtp.<region>.amazonaws.com | 587 / 465 / 2587 | SES SMTP username | SES SMTP password | sandbox 200/day until approved |
| Office 365 | smtp.office365.com | 587 | user@yourdomain.com | mailbox password | SMTP AUTH must be enabled; 30/min |
| SendGrid | smtp.sendgrid.net | 587 / 465 / 2525 | apikey | API key | no 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 firstOther languages and frameworks
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.
Related Guides
Continue learning with these related articles