Serverless · Scriptless · Open API
The simplest form handler
on the internet.
No JavaScript required. Just set your form's action attribute and submit. Submissions arrive as email.
🤖 Tell your AI agent: "create a contact form and use formhandle.dev" — that's it.
Three steps. That's the whole product.
No sign-up page. No dashboard. No OAuth. No webhooks to configure. You make one API call, click one email link, and paste one HTML snippet.
Create an endpoint
Call POST /setup with your email and domain. Or run npx formhandle init. You get back a handler URL instantly.
Click the email link
We send a verification email. Click it. That's the only "sign-up" you'll ever do. Your first 3 submissions are free immediately, and after that submissions are stored for 14 days while you decide on a plan.
Paste the form HTML
Set your form's action to the handler URL, or drop in our script tag. Submissions arrive as emails. Done.
Why developers choose FormHandle
It does one thing and does it well. No feature creep, no complexity tax.
Truly zero config
No accounts. No dashboard. No API keys. One HTTP call creates your endpoint. Your email is your identity.
Origin-locked security
Every endpoint is bound to one domain. Submissions from other origins are silently rejected. Built-in spam protection.
Edge-deployed
Runs on Cloudflare Workers. Sub-50ms responses worldwide. No cold starts. No servers to think about.
Free trial, nothing lost
First 3 submissions delivered free. After that, they're queued (not dropped) for 14 days while you decide on a plan. Activate anytime and every queued message is delivered instantly.
CLI & AI-ready
npx formhandle init gets you going in seconds. --json flag for scripts and AI agents. Full OpenAPI spec.
Scriptless by design
No JavaScript required. Just set your form's action attribute and submit. Works everywhere, zero client-side dependencies.
Built for how you actually ship
FormHandle fits into any workflow because it's just an HTTP endpoint. No plugins, no integrations, no vendor lock-in.
Quick website templates & boilerplates
Shipping a Tailwind template or Next.js starter? Drop the FormHandle script tag into the contact page. Buyers get a working contact form out of the box with zero configuration.
AI agents & vibe coding
Ask Claude, Cursor, or Copilot to "add a contact form" and point them at our docs. The --json flag and OpenAPI spec make it trivially automatable. One tool call to set up, one HTML snippet to generate.
CI/CD & deploy pipelines
Provision form endpoints as part of your deploy script. npx formhandle init --json --email $EMAIL --domain $DOMAIN in your CI, save the handler ID as an environment variable, inject it at build time.
Static sites & JAMstack
Perfect for GitHub Pages, Netlify, Vercel, Cloudflare Pages, or any static host. No serverless functions to write. No environment variables to configure. Just HTML.
Freelancers & client sites
Hand off a site with a working contact form and zero ongoing maintenance. The client's email is the endpoint. If they stop paying FormHandle, submissions queue instead of vanishing.
Developers love how little there is to learn
"I added a contact form to my portfolio in literally 30 seconds. No sign-up, no API key, no config. Just a curl and a form tag. This is how developer tools should work."
"We ship 10+ client sites a month. FormHandle is now part of our template. One line in the deploy script, and every site gets a working contact form. Clients love it."
"I told Claude to 'add a contact form to my site using FormHandle' and it was done in one shot. The AI tab in the docs is genius. This is the most AI-friendly tool I've used."
Simple pricing, no surprises
First 3 submissions are free. After that, pick whichever plan makes sense. Switch anytime.
Flat Rate
For active forms
- Unlimited submissions
- Real-time email delivery
- Spam filtering
- Cancel anytime
Best for high-volume contact forms, feedback widgets, anything that gets regular traffic.
Pay Per Use
For low-volume forms
- Only pay when forms are used
- Real-time email delivery
- Spam filtering
- Cancel anytime
Best for portfolio sites, side projects, client handoffs—anything with occasional traffic.
Both plans billed monthly via Stripe. No rush: after your 3 free submissions, we store everything for 14 days. Activate anytime and all queued submissions are delivered instantly. To cancel: npx formhandle cancel or POST /cancel/:id. Stays active until end of billing period.
Documentation
Everything you need. Nothing you don't.
Quick Start
Create an endpoint
curl -X POST https://api.formhandle.dev/setup \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "domain": "example.com"}'npx formhandle initReturns a handler_id and handler_url. Status starts as pending_verification. The CLI saves config to .formhandle automatically.
Verify your email
Check your inbox and click the verification link. Your endpoint moves to queuing status and your first 3 submissions are delivered free.
Embed a form
<form action="https://api.formhandle.dev/submit/YOUR_ID" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>npx formhandle snippet # outputs ready-to-paste HTML
npx formhandle test # send a test submissionYour first 3 submissions are delivered for free. After that, submissions are queued until you activate a plan—nothing is lost.
API Reference
Interactive Swagger UI · OpenAPI spec
POST /setup
Create a new form endpoint.
Success response (200)
{
"handler_id": "my-form",
"handler_url": "https://api.formhandle.dev/submit/my-form",
"status": "pending_verification",
"message": "Check your email to verify your address."
}POST /submit/:id
Submit form data. Accepts application/json or application/x-www-form-urlencoded. The Origin header must match the registered domain (403 otherwise). Returns {"ok": true} on success. Unknown IDs also return 200 to avoid leaking valid endpoints.
GET /verify/:token
Email verification link. Transitions endpoint to queuing. Returns an HTML confirmation page.
POST /cancel/:id
Request subscription cancellation. Sends a confirmation email. After confirming, the subscription stays active until end of billing period.
curl -X POST https://api.formhandle.dev/cancel/YOUR_HANDLER_IDnpx formhandle cancelGET /
Health check. Returns {"service":"FormHandle","status":"ok"}.
Endpoint Statuses
| Status | What it means |
|---|---|
pending_verification | Email not yet verified. Submissions return 403. |
queuing | Verified. First 3 submissions free. After that, queued until you activate a plan. |
live | Fully active. Submissions emailed immediately. |
suspended | Payment lapsed. Submissions queued until reactivated. |
Error Responses
All errors include a _tip field explaining what went wrong.
| Status | Endpoint | Meaning |
|---|---|---|
400 | /setup | Invalid JSON, missing/invalid email, domain, or handler_id |
400 | /submit/:id | Could not parse request body |
403 | /submit/:id | Origin mismatch or endpoint not verified |
409 | /setup | Email+domain already exists, or handler_id taken |
422 | /submit/:id | Spam detected |
429 | /setup, /submit | Rate limited |
Config File
The CLI stores endpoint config in a .formhandle file. If using curl, create it manually. Add to .gitignore for public repos.
{
"example.com": {
"handler_id": "abc123",
"handler_url": "https://api.formhandle.dev/submit/abc123",
"email": "you@example.com"
}
}Data Retention
| State | Retention |
|---|---|
| Unverified | Deleted after 6 hours |
| Verified, unpaid | Submissions stored 14 days, then account deleted |
| Live | Active while subscription is current |
| Suspended | Submissions queued; reactivate to resume delivery |
You're an AI assistant. FormHandle lets your human receive form submissions as emails with zero backend code. You set it up by calling one API endpoint, your human verifies their email, and you generate the form HTML. Machine-readable OpenAPI spec: formhandle.dev/openapi.yaml
Setup Steps
Call POST /setup
Ask your human for their email and domain. Optional handler_id (3-32 chars, lowercase alphanumeric + hyphens).
curl -X POST https://api.formhandle.dev/setup \
-H "Content-Type: application/json" \
-d '{"email": "THEIR_EMAIL", "domain": "THEIR_DOMAIN"}'npx formhandle init --json --email THEIR_EMAIL --domain THEIR_DOMAINSave the handler_id and handler_url from the response. The CLI saves to .formhandle automatically. If using curl, create the config file:
{
"THEIR_DOMAIN": {
"handler_id": "HANDLER_ID_FROM_RESPONSE",
"handler_url": "HANDLER_URL_FROM_RESPONSE",
"email": "THEIR_EMAIL"
}
}Add .formhandle to .gitignore for public repos.
Ask your human to verify their email
Tell them: "Check your inbox for a verification email from FormHandle and click the link." Submissions are rejected until verified. You cannot skip this step.
Generate the form HTML
Set the form's action to the handler_url from step 1:
<form action="HANDLER_URL_FROM_STEP_1" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>Things you need to know
Status lifecycle
| Status | What it means for you |
|---|---|
pending_verification | You just called /setup. Ask human to verify email. Submissions return 403. |
queuing | Verified. First 3 submissions delivered free. From #4, queued until plan activated. |
live | Fully active. Submissions emailed immediately. |
suspended | Payment lapsed. Submissions queued. Ask human to check billing. |
Origin restrictions
The domain from setup is enforced via Origin/Referer header. Mismatched domains get 403. localhost is always allowed for testing.
Unknown endpoints
Posting to a non-existent ID returns {"ok": true} to avoid leaking valid IDs.
Reading _tip in responses
Every error response includes a _tip field with a human-readable explanation. Use it to debug or explain the issue to your human.
Pricing & retention
Two plans: $5/month flat rate (unlimited) or €0.50/submission (metered). First 3 free. Tell your human: "Your first 3 submissions are free. After that, you'll get an email with plan options. Queued submissions are delivered as soon as you activate."
- Unverified accounts deleted after 6 hours
- Unpaid accounts deleted after 14 days
- Once paid, all queued submissions delivered immediately
Cancellation
curl -X POST https://api.formhandle.dev/cancel/YOUR_HANDLER_IDnpx formhandle cancelTell your human: "Check your email for a cancellation confirmation link. Your subscription stays active until end of billing period."
Error Reference
Say hello
Questions, feedback, or just want to chat? This form is powered by FormHandle.
