New guide: how to warm up a dedicated IP without hurting your reputation. Read now →
Log in
MigoSMTP
SMTP and APIUpdated 17 Oct 2026

How to Send Email With Nodemailer and SMTP in Node.js: Settings, Attachments, Errors and Deliverability

Nodemailer is the standard module for sending email from Node.js. This guide shows a complete example, the correct secure setting for ports 587 and 465, how to verify the connection, common error codes and fixes, attachments with Cc and Bcc, connection pooling for bursts, why browser JavaScript cannot send email safely, and how to keep Node.js mail out of spam.

TK
Written by Tomasz Kowalski
Infrastructure and Reliability Engineer
Reviewed by Ishita Rao
  • Published4 Oct 2026
  • Read time7 min
  • Length1,483 words
Share this guide

Nodemailer is the most widely used module for sending email from Node.js. You install it with npm, create a transporter that holds your SMTP settings, and call sendMail. This guide shows a complete working example, the correct settings for ports 587 and 465, HTML and attachments, how to test the connection, how to handle errors, and how to avoid the mistakes that send Node.js mail to spam. It also explains why you cannot safely send email from JavaScript running in the browser.

Installnpm install nodemailer
Transporteran object that holds the SMTP host, port, security mode and credentials
sendMailone call per message, returning the accepted and rejected recipients

Install Nodemailer and send your first email

Create a project, install the package and put your SMTP details in environment variables. The values below are placeholders. Use the host, user name and password from your SMTP provider's dashboard.

npm install nodemailer
// send.js
const nodemailer = require("nodemailer");

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,            // e.g. smtp.example.com
  port: 587,
  secure: false,                          // false = STARTTLS on 587
  requireTLS: true,                       // refuse to continue without encryption
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }
});

async function main() {
  const info = await transporter.sendMail({
    from: '"Example App" <[email protected]>',
    to: "[email protected]",
    subject: "Welcome",
    text: "Thanks for signing up.",
    html: "<p>Thanks for signing up.</p>"
  });
  console.log("Accepted:", info.accepted, "Message id:", info.messageId);
}
main().catch(console.error);

Run it with node send.js. If you use ES modules, import it with import nodemailer from "nodemailer".

The correct secure setting for each port

The most common Nodemailer error comes from mixing up the secure option and the port. Match them as follows.

PortsecureWhat happens
587falseThe connection starts in plain text and upgrades with STARTTLS. Add requireTLS: true so it never continues unencrypted
465trueThe connection is encrypted from the first byte (SSL/TLS)

Setting secure: true on port 587 gives a "wrong version number" error, and so does the reverse. Our guide to email SSL and TLS explains why, and SMTP settings lists the values. For our relay, the host is smtp.migosmtp.com on port 587 with STARTTLS, and you create the credentials in the dashboard; see the SMTP relay page.

Check the connection before you send

Nodemailer can test the settings without sending a message:

transporter.verify()
  .then(() => console.log("SMTP connection OK"))
  .catch(err => console.error("SMTP problem:", err.code, err.message));

Run this at start-up or in a health check. If it fails, the error code tells you where to look.

Common Nodemailer errors

Error codeMeaningFix
EAUTHLogin failedCheck the user name, password or key, and whether the provider wants an app password
ECONNECTION or ESOCKETCould not connect, or the connection brokeCheck host, port and the secure setting; check firewalls
ETIMEDOUTThe connection or response timed outThe port may be blocked; try the other submission port your provider offers
EENVELOPEInvalid sender or recipientCheck the from and to addresses
Self-signed or hostname mismatchCertificate does not match the host nameUse the exact host name from your provider. Do not turn off certificate checks

Read SMTP connection and TLS troubleshooting and SMTP error codes for what the server's reply codes mean.

Attachments, Cc and Bcc

await transporter.sendMail({
  from: '"Billing" <[email protected]>',
  to: "[email protected]",
  cc: "[email protected]",
  bcc: "[email protected]",
  subject: "Your invoice",
  text: "Your invoice is attached.",
  attachments: [{ filename: "invoice.pdf", path: "./invoice.pdf" }]
});

Nodemailer adds each address to the SMTP envelope and writes only the To and Cc headers, so Bcc recipients stay hidden. Read what CC means and what BCC means. Keep attachments small; see email size limits and attachments.

Sending many emails efficiently

Opening a new connection for every message is slow. For bursts, turn on connection pooling and let Nodemailer reuse connections:

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST, port: 587, secure: false, requireTLS: true,
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
  pool: true, maxConnections: 3, maxMessages: 100
});

Respect your provider's rate limits, handle temporary failures with retries and back-off, and send one message per recipient. Our article on email integration patterns and rate limits and back-off cover this. For larger volumes, a queue in your application keeps sending from blocking your web requests.

Sending email from JavaScript in the browser

Many people search for "email JavaScript" hoping to send mail directly from a web page. Browsers cannot open SMTP connections, and if they could, you would have to place your SMTP password in code that every visitor can read. The safe pattern is to send a request from the page to your own server, or to a service that holds the secret on its side, and have that server send the mail. Treat anything that puts a secret in browser code as a risk, and protect forms against abuse with rate limits and a challenge, since an open form can be used to send spam in your name.

Deliverability for Node.js mail

  • Authenticate your domain. Use SPF, DKIM and DMARC. See the SPF guide, DKIM guide and DMARC guide.
  • Send from an address on that domain, not a free mailbox address.
  • Include a plain-text part as well as HTML, as in the example above.
  • Use a sending service, not a personal mailbox, for anything customers rely on. See Gmail SMTP limits.
  • Keep secrets in environment variables and rotate them. See API key security.
  • Use a test or sandbox environment in development, so you never email real customers by accident. See testing email in staging.

Never disable certificate verification to make an error go away. Options such as tls: { rejectUnauthorized: false } remove the protection that tells you that you are talking to the real server. Fix the host name or the certificate chain instead.

Structuring Nodemailer in a real application

Create the transporter once, when your application starts, and reuse it. Creating a new transporter for every request wastes connections and slows responses. Put it in a small module that exports a function such as sendEmail(to, subject, html, text), so the rest of your code never touches SMTP settings. This also gives you one place to add logging, retries, and a switch that turns real sending off in development and testing.

Keep the content of your messages separate from the sending code. Use a template engine or plain template strings to build the HTML, escape any value that comes from a user, and always generate a plain-text version as well. A message that has only an HTML part, or an HTML part with no text alternative, is more likely to be treated as spam, and is unreadable in clients that block HTML.

Handling failures and retries

Treat sending as work that can fail. A temporary failure, such as a timeout or a 4xx reply from the server, should be retried later with growing delays. A permanent failure, such as a 5xx reply that rejects an address, should not be retried; record it and stop sending to that address. Log the message id that sendMail returns, so you can find the message in your provider's logs when a customer says that it never arrived.

For anything important, such as password resets and receipts, move the call out of the web request. Add a job to a queue, return the response to the user at once, and let a worker send the message. If the SMTP server is slow or down, your site stays fast, and the queue keeps the messages until sending works again.

Using environment variables safely

Read the host, user name and password from process.env, and load them from a .env file in development with a package such as dotenv. Add the file to your .gitignore, so that it never reaches your repository. In production, set the variables in your hosting platform or secrets manager. Use a different set of credentials for development, staging and production, so that a test cannot send real mail from a production account. Fail loudly at start-up when a required variable is missing, so the problem shows up on deployment and not on the first customer email.

Sending HTML email that renders well

Email clients do not support modern web layouts. Use simple table-based layouts and inline styles for anything that must look right in every client, keep images small and always give them alt text, and make sure that the message is still understandable when images are blocked. Send a test to several clients before you ship a template. Our email templates feature can store and render templates for you, so the Node.js code only needs to supply the data.

Sending from Node.js in production? Create SMTP credentials, authenticate your domain and send your first message from your Node.js app in a few minutes. We are here if you hit a snag.

Start free

References and further reading

Frequently asked questions

How do I send an email with Nodemailer?

Install nodemailer with npm, create a transporter with your SMTP host, port, security mode and credentials, then call transporter.sendMail with from, to, subject and a text or HTML body.

What should the secure option be for port 587 and 465?

Use secure false with port 587, which upgrades with STARTTLS, and add requireTLS true. Use secure true with port 465, which is encrypted from the start. Mixing them causes a wrong version number error.

What does the Nodemailer EAUTH error mean?

The login failed. Check the user name and password or key, and whether your provider requires an app password or OAuth instead of a normal password.

Can I send email directly from browser JavaScript?

Not safely. Browsers cannot open SMTP connections, and putting credentials in browser code exposes them. Send a request to your own server or a service that holds the secret, and send the mail from there.

How do I send many emails with Nodemailer?

Use connection pooling, respect your provider's rate limits, retry temporary failures with back-off, send one message per recipient and use a queue so that sending does not block web requests.

Topics#nodemailer#node.js#smtp#javascript#email sending#attachments#error handling
Was this guide helpful?Your answer helps us improve our documentation.

Start sending in minutes.

Create an account, add your domain and send your first email.

Get started Talk to sales