Form endpoint
Every project has a form endpoint: a URL that accepts POST requests from any origin and drops each submission into your project's inbox. Point a plain HTML form at it, or call it with fetch: no backend, no email service, no API key.
Your endpoint looks like this, with your own form id at the end:
POST https://mailcontact.app/api/forms/your-form-idIt accepts both FormData (what a native HTML form sends) and application/json. Each accepted submission lands in your inbox and triggers your notification channels.
Fields
| field | required | notes |
|---|---|---|
name | optional | Shown as the sender. Falls back to the email if empty. Truncated at 200 characters. |
email | required | The sender's email. Validated: invalid or missing addresses are rejected with a 400. |
subject | optional | Truncated at 300 characters. |
message | required | The message body. Must be non-empty. Truncated at 5,000 characters. |
One special field: a non-empty _gotcha marks the submission as bot traffic and it is silently dropped (that's the honeypot spam trick). Every other unknown field is ignored.
Plain HTML form
The zero-JavaScript version. Name your inputs after the fields above and the browser does the rest:
<form action="https://mailcontact.app/api/forms/your-form-id" method="POST">
<input type="text" name="name" placeholder="Your name" />
<input type="email" name="email" placeholder="you@email.com" required />
<input type="text" name="subject" placeholder="Subject" />
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send</button>
</form>One caveat in the beta: there's no redirect after a native form post yet, so the browser navigates to the endpoint's JSON response. A configurable redirect back to your site is coming with the launch. Until then, submit with fetch (below) to keep visitors on the page and show an inline success state.
JSON with fetch
Send application/json with a Content-Type header. Same fields, same validation:
const res = await fetch("https://mailcontact.app/api/forms/your-form-id", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Ada",
email: "ada@example.com",
subject: "Hello",
message: "Trying out the endpoint.",
}),
});
const data = await res.json();
if (data.ok) {
// show your inline "thanks, message sent" state
}You can also POST a FormData object from JavaScript: skip the Content-Type header and pass new FormData(formElement) as the body.
CORS
CORS is open: the endpoint answers with Access-Control-Allow-Origin: * and handles the OPTIONS preflight, so browser submissions work from any origin: your domain, localhost, a staging URL, or a static host like GitHub Pages. Nothing to allowlist.
Responses and errors
On success the endpoint returns 200 with {"ok": true}. Every error is JSON too, shaped {"ok": false, "error": "..."}:
| status | error | when |
|---|---|---|
400 | A valid email is required. | email is missing, invalid, or longer than 320 characters |
400 | Message cannot be empty. | message is missing or blank |
404 | Unknown form. | the form id in the URL doesn't exist |
413 | Payload too large. | request body over 64 KB |
429 | Too many requests. | more than 20 requests per minute from one IP |
Limits
- Request bodies are capped at 64 KB.
- Rate limit: 20 requests per minute per IP, answered with a 429 beyond that.
- Field lengths:
name200,subject300,message5,000 characters (longer values are truncated); emails over 320 characters are rejected.
These are abuse controls, not a quota system, and they're generous for a contact form. If you expect real volume, that's Pro-plan territory; see pricing.
Related
New here? Start with Getting started. Your project also has a real email address feeding the same inbox, and Spam protection covers keeping junk out of an open endpoint. For a ready-made snippet, try the contact form generator or the framework guides.