Send email with Next.js
Server action for your own forms, route handler for everything else — the key stays on the server either way.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
In Next.js, email is a server-side job. If the send starts from your own form, use a server action; if it starts from anything else — a fetch call, a webhook, a cron route — use a route handler. Both run @email-fast/nodejs on the server, so the API key never enters the client bundle. This page is the short version; the full Next.js quickstart walks both patterns end to end.
npm install @email-fast/nodejs// app/actions/email.ts
"use server";
import { EmailFast } from "@email-fast/nodejs";
const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY!, baseUrl: "https://api.emailfast.dev" }); // ef_sandbox_… in dev, ef_live_… at launch
export async function sendReceipt(formData: FormData) {
const email = String(formData.get("email"));
const orderId = String(formData.get("orderId"));
try {
const { id } = await ef.send({
to: email,
subject: `Your receipt for order ${orderId}`,
html: "<p>Order {{order}} is confirmed.</p>",
data: { order: orderId },
idempotency_key: `receipt-${orderId}`, // a double-submit replays, never re-sends
});
return { ok: true, id };
} catch (err) {
console.error("send failed:", err);
return { ok: false };
}
}// app/order/page.tsx
import { sendReceipt } from "../actions/email";
export default function Page() {
return (
<form action={sendReceipt}>
<input name="email" type="email" required />
<input name="orderId" required />
<button>Email my receipt</button>
</form>
);
}a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.
Gotchas
- Server action vs route handler: actions are for sends your own UI triggers. Use a route handler (
app/api/…/route.ts) when the trigger is external — a webhook, a cron ping, another service. - Never prefix the key with
NEXT_PUBLIC_— that inlines it into the client bundle. Keep it asEMAILFAST_API_KEYin.env.local. - The Node SDK needs the Node runtime. If a route opts into the Edge runtime, set
export const runtime = "nodejs"or call the REST endpoint withfetchinstead. - Derive the idempotency key from your domain (order id, user id), not
Math.random()— a random key makes every retry a brand-new send.
Next
- Full Next.js quickstart — route handlers, env setup, the frontend-only option
- The email API — batch, scheduling, message timelines
- All docs