How to Send Email in Node.js
Send email in Node.js with TypeScript. Complete guide with code examples for sending, batch sending, error handling, queuing, templates, and local development.
To send email in Node.js, install the SendPigeon SDK, create a client, and call pigeon.send() with your recipient, subject, and content. This guide walks through every common pattern with full TypeScript examples you can copy into your project.
Quick setup:
npm install sendpigeon- Add
SENDPIGEON_API_KEYto your.env - Call
pigeon.send()from any Node.js context
Best for: Password resets, welcome emails, order confirmations, notifications, digests.
Install the SDK
npm install sendpigeon
Set Up Your Project
Store your API key in .env:
SENDPIGEON_API_KEY=sp_live_your_key_here
Create a shared client:
// lib/email.ts
import { SendPigeon } from "sendpigeon";
export const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);
Send Your First Email
import { pigeon } from "./lib/email";
async function sendWelcomeEmail(email: string, name: string) {
const { data, error } = await pigeon.send({
from: "welcome@yourdomain.com",
to: email,
subject: `Welcome, ${name}!`,
html: `
<h1>Welcome to our platform</h1>
<p>Hi ${name}, thanks for signing up.</p>
`,
});
if (error) {
console.error("Failed to send:", error.message);
return null;
}
return data.id;
}
The SDK returns { data, error } instead of throwing exceptions. This makes error handling explicit — you always know when a send failed and why.
Send with Templates
Use SendPigeon templates instead of building HTML in your code:
async function sendOrderConfirmation(order: {
id: string;
total: string;
customer: { name: string; email: string };
}) {
const { data, error } = await pigeon.send({
from: "orders@yourdomain.com",
to: order.customer.email,
subject: `Order Confirmation #${order.id}`,
templateId: "tmpl_order_confirmation",
variables: {
customerName: order.customer.name,
orderNumber: order.id,
orderTotal: order.total,
},
});
if (error) {
console.error("Order confirmation failed:", error.message);
}
return data;
}
Templates keep your email design separate from your application logic. Edit them in the SendPigeon dashboard without redeploying your app.
Batch Sending
Send up to 100 emails per request with sendBatch:
type Subscriber = {
email: string;
preferences: Record<string, unknown>;
};
async function sendWeeklyDigest(subscribers: Subscriber[]) {
const emails = subscribers.map((sub) => ({
from: "digest@yourdomain.com",
to: sub.email,
subject: "Your Weekly Summary",
html: generateDigestHTML(sub.preferences),
}));
// sendBatch accepts up to 100 emails per request
const chunkSize = 100;
for (let i = 0; i < emails.length; i += chunkSize) {
const chunk = emails.slice(i, i + chunkSize);
const { data, error } = await pigeon.sendBatch(chunk);
if (error) {
console.error("Batch failed:", error.message);
continue;
}
console.log(
`Batch sent: ${data.summary.sent}/${data.summary.total} succeeded`
);
}
}
Each email in a batch gets its own status in the response. If 98 out of 100 succeed, you get individual results for all 100 so you can retry just the failures.
Queue Emails for Production
Production applications should queue emails rather than sending synchronously. This prevents user-facing request timeouts:
import { SendPigeon } from "sendpigeon";
import { Queue, Worker } from "bullmq";
const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);
type EmailJob = {
from: string;
to: string;
subject: string;
html: string;
};
const emailQueue = new Queue<EmailJob>("email-queue");
async function queueEmail(emailData: EmailJob) {
await emailQueue.add("send", emailData, {
attempts: 3,
backoff: {
type: "exponential",
delay: 2000,
},
});
}
new Worker<EmailJob>("email-queue", async (job) => {
const { data, error } = await pigeon.send(job.data);
if (error) {
throw new Error(`Email send failed: ${error.message}`);
}
console.log("Email sent:", data.id);
});
Usage in a route handler:
app.post("/api/users/signup", async (req, res) => {
const user = await createUser(req.body);
await queueEmail({
from: "welcome@yourdomain.com",
to: user.email,
subject: "Welcome aboard!",
html: generateWelcomeEmail(user.name),
});
res.json({ success: true });
});
Error Handling
The SDK returns structured errors with codes you can act on:
type EmailData = {
from: string;
to: string;
subject: string;
html: string;
};
async function sendEmailSafely(emailData: EmailData) {
const { data, error } = await pigeon.send(emailData);
if (error) {
switch (error.code) {
case "api_error":
// Invalid email address, domain not verified, etc.
console.error("API error:", error.apiCode, error.message);
return { success: false, error: error.apiCode };
case "network_error":
// Connection failed — safe to retry
console.warn("Network error, queuing for retry");
await queueEmail(emailData);
return { success: false, error: "network_error" };
case "timeout_error":
// Request timed out — safe to retry
console.warn("Timeout, retrying");
await queueEmail(emailData);
return { success: false, error: "timeout" };
default:
console.error("Unknown error:", error);
return { success: false, error: "unknown" };
}
}
return { success: true, messageId: data.id };
}
The SDK automatically retries on 429 (rate limit) and 5xx errors with exponential backoff. You only need to handle errors that exhaust all retries.
Local Development
Use SendPigeon's dev mode to capture emails locally without sending them:
npx @sendpigeon-sdk/cli dev
# In your .env
SENDPIGEON_DEV=true
When SENDPIGEON_DEV=true is set, the SDK routes requests to localhost:4100 where the CLI captures them for inspection. No emails leave your machine during development.
Start Sending
Install the SDK, add your API key, and send your first email in under a minute:
npm install sendpigeon
import { SendPigeon } from "sendpigeon";
const pigeon = new SendPigeon("sp_live_your_key_here");
const { data, error } = await pigeon.send({
from: "hello@yourdomain.com",
to: "user@example.com",
subject: "Hello from Node.js",
html: "<p>Email sending, solved.</p>",
});
The free tier covers 3,000 emails per month — enough to build and ship without worrying about costs. Scale up when you need to, without rewriting your integration.
Next Steps
- Set up authentication: Email Authentication Setup Guide — SPF, DKIM, DMARC
- Improve deliverability: Email Deliverability Checklist
- Test locally: Local Email Server — catch emails during development
- Browse templates: Email Templates — ready-to-use HTML templates
- Try other frameworks: Framework Guides — Next.js, Remix, SvelteKit, and more