Send

Node.js Transactional Email

Send transactional email from Node.js with the REST email API or SMTP relay, including retries, metadata, and webhooks.

Updated

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

  1. Queue high-value emails in a durable job system.
  2. Store an application event ID.
  3. Retry temporary provider or network failures with backoff.
  4. Do not retry validation errors.
  5. 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

  1. Read the raw body.
  2. Verify the signature.
  3. Insert the event ID with a unique key.
  4. Update message state monotonically.
  5. Return 2xx only 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.