Back to blog
/8 min read

How to Send Email from Node.js (Transactional, Not Bulk)

Learn how to send email from Node.js with a transactional API, including a working fetch example, SMTP tradeoffs, and why bulk sends differ in production.

If you need to send email from Node.js, you are usually trying to deliver one important message at a time: a password reset, a login code, an invoice, a receipt, or an alert. That is transactional email. It is different from bulk email, and the distinction matters for deliverability, compliance, and architecture.

This guide shows a real API call from a Node.js app, explains when an API is a better fit than SMTP, and highlights the guardrails that keep transactional email reliable. ZidiMail is built around this exact use case: simple sending from your own domain, with authentication records handled during setup.

Transactional Email vs Bulk Email

Transactional email is triggered by a user action or system event. A user requests a reset link, completes checkout, invites a teammate, or trips an uptime alert. The content is expected, specific to that recipient, and usually time sensitive.

Bulk email is one-to-many sending: newsletters, announcements, lifecycle campaigns, promotions, or product updates. Those messages need unsubscribe handling, preference centers, audience segmentation, and marketing compliance flows. Mixing bulk campaigns into the same channel as password resets can hurt the reputation of the domain and infrastructure that your product relies on for critical delivery.

  • Use transactional email for account verification, password resets, receipts, alerts, invites, and security notifications.
  • Use marketing email tools for newsletters, promotions, broadcasts, drip campaigns, and audience segmentation.
  • Keep the two streams separate so product-critical mail is not affected by campaign engagement.

Send an Email from Node.js with fetch()

Modern Node.js includes fetch, so you can send through a transactional email API without installing an SMTP client. The exact endpoint and payload should match your ZidiMail account and API docs, but the pattern is intentionally simple: authenticate with an API key, send a JSON payload, and handle non-2xx responses.

const response = await fetch('https://api.zidimails.com/v1/emails', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ZIDIMAIL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: 'Acme App <no-reply@acme.com>',
    to: ['user@example.com'],
    subject: 'Reset your password',
    html: '<p>Click the link below to reset your password.</p>',
    text: 'Click the link below to reset your password.',
  }),
})

if (!response.ok) {
  const error = await response.text()
  throw new Error(`Email send failed: ${response.status} ${error}`)
}

const result = await response.json()
console.log(result)

In production, keep the API key in an environment variable, never in client-side code. Trigger this call from a server route, background job, queue worker, or API handler. If the email is part of a user-facing request, consider writing the event to a queue and returning quickly, then retrying delivery in the background when needed.

API vs Nodemailer and SMTP

Nodemailer is a popular Node.js package for speaking SMTP. It works well when you already have an SMTP relay and need compatibility with older infrastructure. A transactional email API is usually easier for new web apps because it uses the same HTTP patterns as the rest of your backend.

ApproachBest forDeveloper tradeoff
ZidiMail APIModern apps, serverless functions, queues, product eventsSimple JSON requests, API keys, easier error handling
Nodemailer + SMTPLegacy systems, SMTP-only environments, self-hosted relaysConnection management, SMTP credentials, more transport edge cases
Marketing platformNewsletters and campaignsGreat campaign tools, not ideal for product-critical events

Where to Put Email Sending Code

A common mistake is sending email directly from the browser. That exposes credentials and lets anyone abuse your sending account. Keep all sending code on the server. For Next.js, use a Route Handler or Server Action. For Express, put the API call in a controller or service. For background-heavy systems, push an email job into a queue after the database transaction succeeds.

The safest pattern is to persist the business event first, then send the email. For example, create the password reset token in your database, commit it, and only then call the email API. That way the link in the email always points to a real token, and retries do not create inconsistent state.

Delivery Requirements Developers Forget

Sending is only half the work. Receiving providers also check whether your domain is allowed to send. SPF, DKIM, and DMARC tell Gmail, Outlook, and other mailbox providers that the message is legitimate. Without those records, you may see mail land in spam or get rejected outright.

ZidiMail guides you through domain verification and authentication records so your application can send from its own domain. For implementation details, see the ZidiMail docs. You should also send both HTML and plain text, use clear subjects, avoid misleading sender names, and monitor bounces.

A Practical Checklist

  • Use a server-side API key stored in environment variables.
  • Send from a verified domain, not a personal inbox.
  • Include plain text alongside HTML for critical emails.
  • Retry transient failures, but do not retry invalid recipient errors forever.
  • Keep transactional and marketing email streams separate.

Send your first Node.js email with ZidiMail

Create a free account, verify your domain, and call the API from your Node.js backend. ZidiMail is designed for transactional email, not bulk campaigns.

Start sending free