Guides · Code

A Next.js contact form with no API route

Post the form straight to your endpoint, from a Server Component with no JavaScript or a Client Component with fetch. No route handler, no email service.

  • 5 minutes
  • No API route
  • Free for 250 messages a month

Updated

Step 1 of 40%

Make this guide yours

Type your email and we create your inbox, then put its endpoint in every step. No password, no card.

Terminal
npx inboxpanda init --email you@company.com --framework nextjs
  1. 1

    Option 1: a Server Component, no JavaScript

    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>
      )
    }
  2. 2

    Option 2: a Client Component, no page load

    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>
      )
    }
  3. 3

    Which one?

  4. 4

    Check it works

Questions

Can I post from a Server Action instead?

Should the endpoint go in an environment variable?

What do I get in the email?

Is it free?

Keep going

All guides