Next.js
App Router client component. Drop it at app/contact/page.tsx and the route is live.
One-liner
// app/contact/page.tsx — client component 'use client'; import { useState } from 'react'; const ENDPOINT = 'https://gopigeon.dev/f/f_abc123def456xyz0'; // One-time setup: curl -X POST https://gopigeon.dev/new -d 'recipient=you@example.com' export default function Contact() { const [sent, setSent] = useState(false); async function handle(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); const body = new URLSearchParams(new FormData(e.currentTarget)).toString(); const r = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, }); if (r.ok) setSent(true); } if (sent) return <p>Thanks — we'll reply soon.</p>; return ( <form onSubmit={handle}> <input name="name" required /> <input name="email" type="email" required /> <textarea name="message" required /> <button type="submit">Send</button> </form> ); }
How it works
The 'use client' directive marks the file as a client component so useState and the onSubmit handler execute in the browser. The rest is identical to the React recipe: build a form-encoded body, fetch the endpoint, flip state on success.
If you prefer a server action over a client component, wire the same fetch call into an async server function, read formData from the action’s FormData argument, and let Next.js handle the progressive-enhancement fallback. gopigeon does not care which path the request comes from.
The email you passed as recipient above gets a one-click claim link on the first real submission — that is how you become the authenticated owner.