How to Send Emails in Express.js (2026 Guide)

Nik@nikpolale
11 min read
Express is the most popular Node.js framework. Email sending is straightforward: create a route handler, call an email API, return the response. API keys stay on the server.
This guide covers Express routes, middleware patterns, and error handling for email sending.
Install
Terminal
npm install express sequenzyTerminal
npm install express resendTerminal
npm install express @sendgrid/mailCreate an Email Client
src/email.ts
import Sequenzy from "sequenzy";
export const sequenzy = new Sequenzy();
// Reads SEQUENZY_API_KEY from env automaticallysrc/email.ts
import { Resend } from "resend";
export const resend = new Resend(process.env.RESEND_API_KEY);src/email.ts
import sgMail from "@sendgrid/mail";
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
export { sgMail };Send from a Route
src/routes/email.ts
import { Router, Request, Response } from "express";
import { sequenzy } from "../email";
const router = Router();
router.post("/api/send-welcome", async (req: Request, res: Response) => {
const { email, name } = req.body;
if (!email || !name) {
return res.status(400).json({ error: "email and name required" });
}
try {
const result = await sequenzy.transactional.send({
to: email,
subject: `Welcome, ${name}`,
body: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
});
res.json({ jobId: result.jobId });
} catch {
res.status(500).json({ error: "Failed to send" });
}
});
router.post("/api/contact", async (req: Request, res: Response) => {
const { email, message } = req.body;
if (!email || !message) {
return res.status(400).json({ error: "email and message required" });
}
try {
await sequenzy.transactional.send({
to: "you@yourcompany.com",
subject: `Contact from ${email}`,
body: `<p><strong>From:</strong> ${email}</p><p>${message}</p>`,
});
res.json({ sent: true });
} catch {
res.status(500).json({ error: "Failed to send" });
}
});
export default router;src/routes/email.ts
import { Router, Request, Response } from "express";
import { resend } from "../email";
const router = Router();
const FROM = "Your App <noreply@yourdomain.com>";
router.post("/api/send-welcome", async (req: Request, res: Response) => {
const { email, name } = req.body;
if (!email || !name) {
return res.status(400).json({ error: "email and name required" });
}
const { data, error } = await resend.emails.send({
from: FROM,
to: email,
subject: `Welcome, ${name}`,
html: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
});
if (error) {
return res.status(500).json({ error: error.message });
}
res.json({ id: data?.id });
});
export default router;src/routes/email.ts
import { Router, Request, Response } from "express";
import { sgMail } from "../email";
const router = Router();
router.post("/api/send-welcome", async (req: Request, res: Response) => {
const { email, name } = req.body;
if (!email || !name) {
return res.status(400).json({ error: "email and name required" });
}
try {
await sgMail.send({
to: email,
from: "noreply@yourdomain.com",
subject: `Welcome, ${name}`,
html: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
});
res.json({ sent: true });
} catch {
res.status(500).json({ error: "Failed to send" });
}
});
export default router;Wire It Up
// src/app.ts
import express from "express";
import emailRoutes from "./routes/email";
const app = express();
app.use(express.json());
app.use(emailRoutes);
app.listen(3000, () => {
console.log("Server running on port 3000");
});Error Handling Middleware
// src/middleware/async-handler.ts
import { Request, Response, NextFunction } from "express";
export function asyncHandler(
fn: (req: Request, res: Response, next: NextFunction) => Promise<void>
) {
return (req: Request, res: Response, next: NextFunction) => {
fn(req, res, next).catch(next);
};
}
// Usage:
router.post("/api/send-welcome", asyncHandler(async (req, res) => {
const result = await sequenzy.transactional.send({
to: req.body.email,
subject: "Welcome",
body: "<h1>Welcome!</h1>",
});
res.json(result);
}));Going to Production
1. Verify Your Domain
Add SPF, DKIM, DMARC DNS records.
2. Use Environment Variables
export SEQUENZY_API_KEY=sq_your_key3. Rate Limiting
npm install express-rate-limitimport rateLimit from "express-rate-limit";
const emailLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { error: "Too many requests" },
});
router.post("/api/contact", emailLimiter, asyncHandler(async (req, res) => {
// ...
}));Beyond Transactional
Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one SDK. Native Stripe integration for SaaS.
Wrapping Up
- Express routes for API and form-based sending
- Async error handling with middleware
- Rate limiting to prevent abuse
- Environment variables for API keys
Pick your provider, copy the patterns, and start sending.