send.webforms.toRead the docs →

Frameworks and libraries

Add a contact form to Next.js

Next.js gives you a place to run server code, which is exactly why most Next.js contact forms end up as a half-finished /api/contact route with an SMTP client bolted on. WebForms replaces that route. Your form POSTs to an endpoint, we handle spam filtering, storage and fan-out to email, Slack or a webhook, and you keep your app free of mail credentials. Everything below works on the App Router, the Pages Router and a fully static `output: 'export'` build.

Get an endpointRead the docs

Setup

  1. Create a form and copy your endpoint

    Sign up, create a form and copy its endpoint URL. It looks like https://send.webforms.to/wft_your_key. There is no SDK to install and no package to add.

  2. Point your form at it

    Use the client component below for an inline success state, or the Server Action version if you want zero client JavaScript. Both send the same payload.

  3. Choose where submissions land

    In the dashboard, add destinations: an email address, a Slack channel, a Telegram chat, a webhook into your own API, or several at once. Every submission fans out to all of them.

Next.js examples

Swap in your own endpoint and these run as-is.

Client component with an inline success state. Requesting JSON keeps the browser on the page.

app/contact/contact-form.tsx
"use client";

import { useState } from "react";

const ENDPOINT = "https://send.webforms.to/wft_your_key";

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(ENDPOINT, {
      method: "POST",
      headers: { accept: "application/json" },
      body: new FormData(e.currentTarget),
    });

    setStatus(res.ok ? "sent" : "error");
  }

  if (status === "sent") {
    return <p role="status">Thanks - we&apos;ll be in touch.</p>;
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="name" placeholder="Your name" required />
      <input name="email" type="email" placeholder="you@example.com" required />
      <textarea name="message" placeholder="Message" required />

      {/* Honeypot: hidden from people, irresistible to bots. */}
      <input
        name="_gotcha"
        tabIndex={-1}
        autoComplete="off"
        style={{ position: "absolute", left: "-9999px" }}
        aria-hidden="true"
      />

      <button disabled={status === "sending"}>
        {status === "sending" ? "Sending…" : "Send"}
      </button>

      {status === "error" && <p role="alert">Something went wrong. Try again?</p>}
    </form>
  );
}

Server Action variant. No client JavaScript at all - the form still works with JS disabled.

app/contact/page.tsx
import { redirect } from "next/navigation";

export default function ContactPage() {
  async function submit(formData: FormData) {
    "use server";

    const res = await fetch("https://send.webforms.to/wft_your_key", {
      method: "POST",
      headers: { accept: "application/json" },
      body: formData,
    });

    if (!res.ok) throw new Error("Submission failed");
    redirect("/contact/thanks");
  }

  return (
    <form action={submit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}

For output: 'export' there is no server at all. A plain form POST still works - we redirect the browser to _next when it finishes.

app/contact/page.tsx (static export)
export default function ContactPage() {
  return (
    <form action="https://send.webforms.to/wft_your_key" method="POST">
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <input type="hidden" name="_next" value="https://example.com/thanks" />
      <button type="submit">Send</button>
    </form>
  );
}

Worth knowing

Next.js form FAQ

No. The form posts straight to your WebForms endpoint. You can delete the /api/contact route and the SMTP dependency that usually comes with it.

Ready to wire up your Next.js form?

Free for 300 submissions a month. Endpoint in under a minute, no card required.

Start free