Back to Blog

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

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 sequenzy
Terminal
npm install express resend
Terminal
npm install express @sendgrid/mail

Create an Email Client

src/email.ts
import Sequenzy from "sequenzy";

export const sequenzy = new Sequenzy();
// Reads SEQUENZY_API_KEY from env automatically
src/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_key

3. Rate Limiting

npm install express-rate-limit
import 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

  1. Express routes for API and form-based sending
  2. Async error handling with middleware
  3. Rate limiting to prevent abuse
  4. Environment variables for API keys

Pick your provider, copy the patterns, and start sending.