Send email from Next.js (App Router) with Nodemailer
A Next.js route handler that sends mail over SMTP with Nodemailer, credentials in .env.local, running on the Node runtime (not Edge), with a contact-form example and the serverless caveats.
Install
shell
npm install nodemailer
Complete example
Next.js — STARTTLS on 587, credentials from the environment
// .env.local
SMTP_HOST=smtp.queensmtp.com
SMTP_PORT=587
SMTP_USER=your-smtp-username
SMTP_PASS=your-smtp-password
// app/api/contact/route.ts
import nodemailer from "nodemailer";
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // SMTP needs the Node runtime, not Edge
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT || 587),
secure: Number(process.env.SMTP_PORT) === 465,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});
export async function POST(req: Request) {
const { name, email, message } = await req.json();
if (!email || !message) return NextResponse.json({ ok: false }, { status: 400 });
await transporter.sendMail({
from: '"Website" <hello@yourdomain.com>', // your verified domain — NOT the visitor's address
replyTo: email,
to: "support@yourdomain.com",
subject: `Contact form: ${name}`,
text: message,
});
return NextResponse.json({ ok: true });
}What to know
- Edge runtime and Cloudflare Pages cannot open SMTP sockets — export const runtime = "nodejs", or call the relay's HTTPS API.
- Never put the visitor's address in From (it fails SPF/DMARC); use replyTo.
- Vercel functions time out at 10 s on Hobby — keep the SMTP timeout below that or send from a queue.
Same code, different provider
Only the host, port and credentials change. The rules per provider are on the SMTP settings hub.
| Provider | Host | Port | Username | Password | Note |
|---|---|---|---|---|---|
| QueenSMTP | smtp.queensmtp.com | 587 (STARTTLS) / 465 (SSL) / 2525 | SMTP username from the dashboard | SMTP password | $5/year + $0.10 per 1,000; 100 free/day |
| Gmail | smtp.gmail.com | 587 / 465 | you@gmail.com | App Password | 500/day, From must be the account |
| Amazon SES | email-smtp.<region>.amazonaws.com | 587 / 465 / 2587 | SES SMTP username | SES SMTP password | sandbox 200/day until approved |
| Office 365 | smtp.office365.com | 587 | user@yourdomain.com | mailbox password | SMTP AUTH must be enabled; 30/min |
| SendGrid | smtp.sendgrid.net | 587 / 465 / 2525 | apikey | API key | no free plan since 2025 |
Credentials for the example above
Create a free account, verify your sending domain (SPF + DKIM shown in the dashboard), add an SMTP credential, and paste it into the code. 100 emails a day free; $5/year plus $0.10 per 1,000 after that. Prefer HTTPS? The same account has a REST email API.
Get SMTP credentials Test your settings firstOther languages and frameworks
PHPsend email over SMTP
Laravelsend email over SMTP
WordPresssend email over SMTP
Pythonsend email over SMTP
Djangosend email over SMTP
Flasksend email over SMTP
Node.jssend email over SMTP
Javasend email over SMTP
C# / .NETsend email over SMTP
Gosend email over SMTP
Ruby on Railssend email over SMTP
Postfix (Linux relayhost)send email over SMTP
curl / Bashsend email over SMTP
Next.js SMTP — questions
Usually the Edge runtime (no SMTP), missing environment variables in the Vercel project, or a blocked port. Force runtime = "nodejs" and use 587 or 465.
Related Guides
Continue learning with these related articles