Tutorial 7 min read Updated April 2026

How to Send Transactional Emails in Node.js (with Code Examples)

Two approaches: Emitlo's REST API (recommended for new projects) and Nodemailer + SMTP (for existing codebases). Both use Emitlo as the sending infrastructure.

These examples use Emitlo's API. Sign up free to follow along — 12,000 emails/month, no credit card.

Method 1: Emitlo REST API (fetch)

The simplest approach — no dependencies required. Works in Node.js 18+ with the native fetch API.

// send-email.js
const response = await fetch('https://api.emitlo.com/v1/messages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.EMITLO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: '[email protected]',
    to: '[email protected]',
    subject: 'Welcome to our app',
    html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
    text: 'Welcome! Thanks for signing up.',
  }),
});

if (!response.ok) {
  const error = await response.json();
  throw new Error(`Email failed: ${error.message}`);
}

const result = await response.json();
console.log('Sent:', result.id); // msg_8f2c...

Method 2: Emitlo REST API (axios)

// npm install axios
const axios = require('axios');

async function sendEmail({ to, subject, html, text }) {
  const { data } = await axios.post(
    'https://api.emitlo.com/v1/messages',
    { from: '[email protected]', to, subject, html, text },
    {
      headers: {
        Authorization: `Bearer ${process.env.EMITLO_API_KEY}`,
      },
    }
  );
  return data; // { id: 'msg_8f2c...', status: 'queued' }
}

// Usage
await sendEmail({
  to: '[email protected]',
  subject: 'Your order is confirmed',
  html: '<h1>Order #1234 confirmed</h1>',
  text: 'Order #1234 confirmed',
});
These examples use Emitlo's API. Sign up free to follow along →

Method 3: Nodemailer + SMTP

Use this approach if you have an existing codebase using Nodemailer. Just update the SMTP credentials to use Emitlo.

// npm install nodemailer
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  host: 'smtp.emitlo.com',
  port: 587,
  secure: false, // use STARTTLS
  auth: {
    user: process.env.EMITLO_SMTP_USER,
    pass: process.env.EMITLO_SMTP_PASS,
  },
});

async function sendEmail({ to, subject, html, text }) {
  const info = await transporter.sendMail({
    from: '"Your App" <[email protected]>',
    to,
    subject,
    html,
    text,
  });
  return info.messageId;
}

// Usage
await sendEmail({
  to: '[email protected]',
  subject: 'Password reset',
  html: '<p>Click <a href="...">here</a> to reset your password.</p>',
  text: 'Visit this link to reset your password: ...',
});

Sending HTML email

Always include both HTML and plain-text versions. Some email clients display plain text by default, and spam filters look for the presence of a plain-text version.

const emailHtml = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
  <h1 style="color: #1a1a2e;">Welcome to Our App</h1>
  <p>Hi ${user.name},</p>
  <p>Thanks for signing up. Click the button below to verify your email.</p>
  <a href="${verifyUrl}" 
     style="display: inline-block; background: #6c3ff9; color: white; 
            padding: 12px 24px; border-radius: 8px; text-decoration: none;">
    Verify Email
  </a>
</body>
</html>
`;

const emailText = `
Welcome to Our App

Hi ${user.name},

Thanks for signing up. Visit this link to verify your email:
${verifyUrl}
`;

Sending with attachments

// REST API with attachment
const fs = require('fs');
const pdfBuffer = fs.readFileSync('./invoice.pdf');

await fetch('https://api.emitlo.com/v1/messages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.EMITLO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: '[email protected]',
    to: '[email protected]',
    subject: 'Your invoice #INV-1234',
    html: '<p>Please find your invoice attached.</p>',
    text: 'Please find your invoice attached.',
    attachments: [{
      filename: 'invoice-1234.pdf',
      content: pdfBuffer.toString('base64'),
      contentType: 'application/pdf',
    }],
  }),
});

Error handling

async function sendEmailSafe({ to, subject, html, text }) {
  try {
    const response = await fetch('https://api.emitlo.com/v1/messages', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.EMITLO_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ from: '[email protected]', to, subject, html, text }),
    });

    if (response.status === 429) {
      // Rate limited — retry after delay
      const retryAfter = response.headers.get('Retry-After') || 60;
      throw new Error(`Rate limited. Retry after ${retryAfter}s`);
    }

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Email API error ${response.status}: ${error.message}`);
    }

    return await response.json();
  } catch (err) {
    // Log error with context for debugging
    console.error('Email send failed', { to, subject, error: err.message });
    throw err; // Re-throw for caller to handle
  }
}

Environment variables

Never hardcode API keys. Use environment variables:

# .env
EMITLO_API_KEY=your_api_key_here
EMITLO_SMTP_USER=your_smtp_user
EMITLO_SMTP_PASS=your_smtp_password
[email protected]
// Load with dotenv
require('dotenv').config();
const apiKey = process.env.EMITLO_API_KEY;

Start sending transactional emails in Node.js — free

12,000 emails/month (400/day) · REST API + SMTP · No credit card

Frequently Asked Questions

What is the best way to send transactional email in Node.js?
For new projects, use a REST API (like Emitlo's) with the native fetch API or axios. For existing projects using Nodemailer, configure it with Emitlo's SMTP credentials. The REST API approach gives you better error handling and structured responses.
Should I use Nodemailer or a REST API for transactional email?
Both work well. Nodemailer + SMTP is easier to drop into existing codebases. REST API gives you better error handling, structured responses, and is preferred for new applications. Emitlo supports both with the same credentials and rate limits.
How do I handle email errors in Node.js?
Always wrap email sending in try/catch. Check the HTTP status code for REST API calls (200 = success, 4xx = client error, 5xx = server error). For SMTP, Nodemailer throws errors for connection failures and rejected messages. Log errors with enough context to debug (recipient, subject, error message).
How do I send HTML email with Node.js?
Pass your HTML string as the html property in the request body (REST API) or the html option in Nodemailer. Always include a plain-text alternative (text property) for email clients that don't render HTML.
How do I send email with attachments in Node.js?
With Emitlo's REST API, include an attachments array in the request body with base64-encoded file content. With Nodemailer, use the attachments option with path, content, or buffer. Emitlo's free plan supports attachments up to 10MB.

Related guides: