Node.js Transactional Email
Send transactional email from Node.js with the REST email API or SMTP relay, including retries, metadata, and webhooks.
TL;DR
Use fetch or your HTTP client for new Node.js transactional email. Use Nodemailer SMTP for frameworks or legacy code that already expects SMTP. In both cases, process delivery webhooks idempotently.
What you will learn
- Send transactional email from Node.js over HTTP
- Configure SMTP relay with Nodemailer when needed
- Avoid duplicate sends and webhook side effects
REST API example
await fetch("https://api.postscale.io/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POSTSCALE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Acme <alerts@example.com>",
to: ["user@example.com"],
subject: "Welcome",
html_body: "<p>Hello from Acme</p>",
text_body: "Hello from Acme",
metadata: { user_id: "usr_123" },
tags: ["onboarding"],
}),
});
Wrap this in a function such as sendTransactionalEmail(message). Do not call provider endpoints directly throughout routes and jobs.
SMTP with Nodemailer
Use SMTP relay when the app already depends on SMTP configuration:
import nodemailer from "nodemailer";
const transport = nodemailer.createTransport({
host: "smtp.postscale.io",
port: 587,
secure: false,
auth: {
user: process.env.POSTSCALE_SMTP_USERNAME,
pass: process.env.POSTSCALE_SMTP_PASSWORD,
},
});
await transport.sendMail({
from: "Acme <alerts@example.com>",
to: "user@example.com",
subject: "Welcome",
html: "<p>Hello from Acme</p>",
text: "Hello from Acme",
});
On port 587, secure: false means the connection starts plaintext and upgrades with STARTTLS. Use secure: true only for implicit TLS on port 465.
Retry design
- Queue high-value emails in a durable job system.
- Store an application event ID.
- Retry temporary provider or network failures with backoff.
- Do not retry validation errors.
- Do not create a new OTP, magic link, or reset token on every retry.
Use SMTP errors and retries for response handling.
Webhook handler baseline
- Read the raw body.
- Verify the signature.
- Insert the event ID with a unique key.
- Update message state monotonically.
- Return
2xxonly after durable acceptance.
See webhook signature verification and webhook retries and idempotency.
References
Frequently asked questions
- Should Node.js apps use SMTP or REST?
- Use REST for new product code. Use SMTP when a framework, plugin, or legacy app already uses SMTP settings.
- Can I use Node fetch without an SDK?
- Yes. A small fetch-based adapter is often enough for transactional sending.
Related guides
Put the guide into production
Postscale brings sending, inbound processing, DMARC reporting, and masked addresses behind one API so the operational pieces stay connected.