Updated 2026-10-08
How to Send Email from Node.js with Nodemailer and SMTP
To send email from Node.js with Nodemailer, call nodemailer.createTransport with host smtp.bulko.io, port 587, secure: false and auth.user and auth.pass from environment variables; Nodemailer upgrades the connection with STARTTLS. Set secure: true only when you use port 465.
Nodemailer SMTP settings
| Field | Value |
|---|---|
| host | smtp.bulko.io |
| port | 587 (or 2525 if blocked; 465 with secure: true) |
| secure | false for 587 and 2525, true only for 465 |
| requireTLS | true (optional, refuses to send if STARTTLS is not available) |
| auth.user | process.env.SMTP_USERNAME (e.g. smtp_xxxxxxxxxxxxxxxx) |
| auth.pass | process.env.SMTP_PASSWORD |
| from | An address on your domain, e.g. "Your App" <[email protected]> |
Step-by-step setup
- Buy an SMTP package on the Bulko SMTP Relay page and copy the username and password from Dashboard -> SMTP Access.
- Install the library with
npm install nodemailer. It works with bothimport nodemailer from "nodemailer"(ESM) andrequire("nodemailer")(CommonJS). - Store the credentials as environment variables
SMTP_USERNAMEandSMTP_PASSWORD(in your process manager, container or a.envfile loaded withnode --env-file=.env). - Create one transporter with
host: "smtp.bulko.io",port: 587,secure: falseand theauthobject. Reuse it across requests instead of creating a new one per email. - Understand
secure:truemeans TLS from the first byte and is correct only for port 465. WithfalseNodemailer connects in plain text and upgrades with STARTTLS if the server offers it; addrequireTLS: trueto make that upgrade mandatory. - Set
fromto an address on your own domain. For aligned SPF, DKIM and DMARC, verify the domain under Sending Domains in the Bulko dashboard. - Call
await transporter.verify()once at startup to catch wrong ports or passwords early, then send withawait transporter.sendMail({...}).
If your hosting provider blocks outbound 587 and 465 (some cloud platforms do), use port: 2525 with the same secure: false settings. The SMTP ports guide explains the differences.
send-test.mjs
// send-test.mjs (npm install nodemailer)
import nodemailer from "nodemailer";
const transporter = nodemailer.createTransport({
host: "smtp.bulko.io",
port: 587,
secure: false, // false for 587 and 2525 (STARTTLS), true only for 465
requireTLS: true, // fail instead of sending without TLS
auth: {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD,
},
});
// Port 465 (implicit TLS):
// const transporter = nodemailer.createTransport({
// host: "smtp.bulko.io",
// port: 465,
// secure: true,
// auth: { user: process.env.SMTP_USERNAME, pass: process.env.SMTP_PASSWORD },
// });
try {
await transporter.verify(); // checks DNS, TLS and login without sending
const info = await transporter.sendMail({
from: '"Your App" <[email protected]>',
to: "[email protected]",
subject: "Nodemailer SMTP test",
text: "It works.",
html: "<p>It works.</p>",
});
console.log("Sent:", info.messageId, info.response);
} catch (err) {
console.error("SMTP error:", err.responseCode, err.message);
process.exitCode = 1;
}
Send a test email
Run SMTP_USERNAME=smtp_xxx SMTP_PASSWORD=yourpass node send-test.mjs with a real recipient. On success the script prints the messageId and a response starting with 250, which means the relay accepted the message. Check the inbox within a minute or two.
If verify() fails, the error has a code such as EAUTH, ETIMEDOUT or ESOCKET, and SMTP errors include responseCode. Match them with the table below.
Troubleshooting
| Problem | Fix |
|---|---|
| Error: Invalid login: 535 (code EAUTH) | Wrong username or password, or the environment variables are undefined in the running process. Log Boolean(process.env.SMTP_PASSWORD) to check. See https://bulko.io/en/smtp-errors/535/ |
| SSL routines: wrong version number | secure: true was used with port 587 or 2525. Set secure: false for those ports, or switch to port 465 with secure: true. |
| Greeting never received or connection hangs on 465 | secure: false was used with port 465, so Nodemailer waits for a plain greeting that never comes. Set secure: true for 465. |
| ETIMEDOUT or ECONNREFUSED | Outbound SMTP ports are blocked by your host or firewall. Try port 2525 with secure: false. |
| 554 5.7.1 in err.response | The AI content filter rejected the message for phishing, malware, impersonation or scam patterns. Review content and links; see https://bulko.io/en/acceptable-use/ |
FAQ
When should secure be true in Nodemailer?
Only for port 465, where TLS starts immediately. For 587 and 2525 keep secure: false; the connection is still encrypted through STARTTLS, and requireTLS: true enforces it.
Does Nodemailer need a pool for bulk sending?
For many messages you can add pool: true to reuse connections. Stay within the default limit of 2,000 emails per day per SMTP account, or ask [email protected] to raise it.
Can I read incoming email with Nodemailer and this server?
No. Nodemailer only sends, and Bulko SMTP is outgoing only with no IMAP. Replies go to your From or Reply-To mailbox.
Does it work in Next.js, Express or NestJS?
Yes, in any server-side Node.js code. Do not run it in the browser or expose SMTP credentials to client bundles.
How much does it cost?
One-time packages: $5 for 5,000 emails, $15 for 20,000, $30 for 50,000, $60 for 150,000. Unused emails never expire, and there is no free SMTP trial.
Get SMTP credentials for Nodemailer
One-time payment by card or crypto, no subscription. Unused emails never expire. 2,000 emails per day per SMTP account.
What you can send: Acceptable Use Policy.