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',
}); 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