The usual Next.js contact form has three parts: the form, a route handler or Server Action, and an email service with its API key. You only need the first. The form posts straight to your InboxPanda endpoint, which keeps the message, sets spam aside and emails you.
Get your endpoint by typing your email on the home page and pressing Create your inbox: no password, no card. Open the link we email you to claim the inbox. You can also create it from your terminal:
npx inboxpanda init --email you@company.com --framework nextjs
1
Option 1: a Server Component, no JavaScript
A plain form works in the App Router as it is. It sends before your JavaScript loads, and with JavaScript switched off.
TSX// app/contact/page.tsx export default function Contact() { return ( <form action="https://inboxpanda.io/f/your-form-id" method="POST"> <label>Name <input name="name" autoComplete="name" required /></label> <label>Email <input name="email" type="email" autoComplete="email" required /></label> <label>Message <textarea name="message" required /></label> <input name="_honey" style={{ display: "none" }} tabIndex={-1} autoComplete="off" /> <input type="hidden" name="_redirect" value="https://yoursite.com/contact/thanks" /> <button>Send</button> </form> ) }
After sending, the visitor lands on
/contact/thanks. Make that a page of your own. The_redirectpage has to be on the same site as the form; leave it out and visitors see a thank-you page instead.2
Option 2: a Client Component, no page load
To show "Thanks" in place, send with
fetchand ask for JSON:TSX// app/contact/contact-form.tsx "use client" import { useState } from "react" export 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://inboxpanda.io/f/your-form-id", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(Object.fromEntries(new FormData(e.currentTarget))), }) setStatus(res.ok ? "sent" : "error") } if (status === "sent") return <p>Thanks. We'll get back to you soon.</p> return ( <form onSubmit={onSubmit}> <label>Name <input name="name" autoComplete="name" required /></label> <label>Email <input name="email" type="email" autoComplete="email" required /></label> <label>Message <textarea name="message" required /></label> <input name="_honey" style={{ display: "none" }} tabIndex={-1} autoComplete="off" /> <button disabled={status === "sending"}>Send</button> {status === "error" && <p>Something went wrong. Please try again.</p>} </form> ) }
Then render
<ContactForm />from any page. The endpoint accepts posts from any origin, so there's nothing to add tonext.config.3
Which one?
The Server Component if your form is a contact page: it's less code and works everywhere. The Client Component when the form sits in a modal, a footer or a page you don't want to leave.
Both work on Vercel, Netlify, a static export (
output: "export") or your own server, since the browser does the sending.4
Check it works
Run
next dev, open the page and send the form from a different email address than your own. It shows up in your inbox within a few seconds, and you get an email about it. If it lands in Spam, press Not spam: a message that only says "test" can look like spam.