
My Nodemailer post from 2022 is still one of my most-read articles, so people clearly still need this. But the options have changed. Today you have three solid choices, and the right one depends on your app.
Quick comparison

- Nodemailer: an SMTP client library. You need an email provider (any SMTP server). Setup takes minutes. Best when you already have an SMTP server.
- Resend: an email API, no provider needed. Setup takes minutes. Best for fast setup and React templates.
- AWS SES: an email API, no provider needed. Setup takes longer (domain verification, leaving the sandbox). Best for high volume on AWS.
Option 1: Nodemailer
Nodemailer sends through any SMTP server: Gmail, Outlook, your host, or even SES’s SMTP interface.
npm install nodemailer
npm install -D @types/nodemailer
import nodemailer from "nodemailer";
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
secure: false, // STARTTLS on 587
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});export async function sendWithNodemailer(to: string, subject: string, html: string) {
const info = await transporter.sendMail({
from: '"My App" <no-reply@myapp.com>',
to,
subject,
html,
});
return info.messageId;
}Call await transporter.verify() once on startup to catch bad credentials early.
Option 2: Resend
Resend is an email API. No SMTP settings, just an API key.
npm install resend
import { Resend } from "resend";const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWithResend(to: string, subject: string, html: string) {
const { data, error } = await resend.emails.send({
from: "My App <no-reply@myapp.com>",
to: [to],
subject,
html,
});
if (error) throw new Error(error.message);
return data?.id;
}Note that Resend returns { data, error } instead of throwing, so always check error (Resend docs).
Option 3: AWS SES
If you’re already on AWS, SES is built for volume. Use the v3 SDK:
npm install @aws-sdk/client-sesv2
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";const ses = new SESv2Client({ region: "us-east-1" });export async function sendWithSes(to: string, subject: string, html: string) {
const res = await ses.send(
new SendEmailCommand({
FromEmailAddress: "no-reply@myapp.com",
Destination: { ToAddresses: [to] },
Content: {
Simple: {
Subject: { Data: subject },
Body: { Html: { Data: html } },
},
},
})
);
return res.MessageId;
}New SES accounts start in a sandbox where you can only send to verified addresses. Request production access before you launch.
Make switching easy

Whatever you pick, hide it behind one function. Then changing providers later is a one-line change.
type SendEmail = (to: string, subject: string, html: string) => Promise<string | undefined>;
export const sendEmail: SendEmail =
process.env.EMAIL_PROVIDER === "ses" ? sendWithSes : sendWithResend;
Don’t skip deliverability
With any option, set up SPF, DKIM and DMARC records for your sending domain. Without them, Gmail and Outlook are likely to send your emails to spam, no matter which library you use.
Which should you use?

- Side project or startup: Resend. Fastest to set up, nice developer experience.
- Existing SMTP server or company mailbox: Nodemailer.
- High volume and already on AWS: SES.
In the next posts I’ll cover Gmail with Nodemailer (app passwords and OAuth2) and templated emails with React Email.
