Next.js contact form with email (App Router)
Two good ways to send email from Next.js: a Server Action that keeps your key secret, or a client-side form that also works with output: "export".
Last updated October 25, 2026
1Create a project and get a key
Sign up, add a project, connect your SMTP account and copy the key from API & code.
2Option A: Server Action (recommended)
The key stays on the server. Add it to .env.local as FORMSMTP_KEY; leave Allowed websites empty, since requests come from your server.
// app/contact/actions.ts
"use server";
export async function sendContact(_: unknown, formData: FormData) {
const res = await fetch("https://app.formsmtp.com/api/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FORMSMTP_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "you@yourdomain.com",
replyTo: formData.get("email"),
subject: `New message from ${formData.get("name")}`,
text: String(formData.get("message")),
}),
});
const data = await res.json();
return data.success ? { ok: true } : { ok: false, error: data.error };
}// app/contact/page.tsx
"use client";
import { useActionState } from "react";
import { sendContact } from "./actions";
export default function ContactPage() {
const [state, action, pending] = useActionState(sendContact, null);
if (state?.ok) return <p>Thanks! We'll get back to you soon.</p>;
return (
<form action={action}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<button disabled={pending}>{pending ? "Sending…" : "Send"}</button>
{state?.error && <p role="alert">{state.error}</p>}
</form>
);
}3Option B: Client-side (works with static export)
No server needed, so it works with output: "export". Lock the project to your site under Allowed websites and Allowed recipients.
"use client";
import { useState } from "react";
export default function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const res = await fetch("https://app.formsmtp.com/api/v1/form/" + process.env.NEXT_PUBLIC_FORMSMTP_KEY, {
method: "POST",
headers: { Accept: "application/json" },
body: new URLSearchParams(new FormData(e.currentTarget) as any),
});
setStatus((await res.json()).success ? "sent" : "error");
}
if (status === "sent") return <p>Thanks! Message sent.</p>;
return (
<form onSubmit={onSubmit}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={status === "sending"}>Send</button>
{status === "error" && <p role="alert">Something went wrong. Please try again.</p>}
</form>
);
}FAQ
Which option should I use?
Use the Server Action whenever your Next.js app runs on a server (Vercel, Node), since the key never reaches the browser. Use the client-side form for static exports.
Why did my message land in spam?
Usually the From address and the SMTP account don’t match, or the domain has no SPF/DKIM records. Send from the address you log in with, and check the Deliverability panel in FormSMTP’s Settings.