Mailan
Sign in

Submit endpoint.

One POST per submission, and it is the only endpoint your visitors ever touch. It takes urlencoded, multipart and JSON bodies, stores everything you send, mails it to the form's recipients and hands the rest to the queue — webhooks, integrations, autoresponder.

The two routes

Identical in behaviour — pick whichever fits. With the key in the path the form markup stays clean; with the key in the body a single endpoint can serve several forms.

bash

Body formats

All three are accepted. Use multipart only when there are files — it is bigger, and without an upload there is nothing to gain.

Content-Type When
application/json Programs, fetch, server-to-server.
application/x-www-form-urlencoded Classic HTML form post and AJAX without files.
multipart/form-data As soon as a file is attached.

What comes back depends on the Accept header. A request that asks for text/html — a browser posting a form — is answered with a 303 to the thank-you page or to your redirect target. Anything else gets JSON. Sending Accept: application/json or the header X-Requested-With always forces JSON.

Your own fields

Every field that is not on the reserved list is stored as submitted and shown in the notification mail, in the dashboard and in the read API. There is no schema to declare beforehand — name your fields as you like.

One convention is worth keeping: a field named email is used as the reply address of the notification mail and as the recipient of the autoresponder.

Reserved field names

These names steer the pipeline instead of being stored. They never appear in the stored data, never in the mail body and never in the read API.

Field Effect
access_key / accessKey The form's access key, when it is not in the URL.
subject Subject of the notification mail. Single line, max. 200 characters.
from_name Sender name of the notification mail. Max. 100 characters.
replyto / reply_to Reply address. Must be a single valid address.
ccemail Comma-separated CC addresses, at most five.
redirect Target for the 303 after a successful submission.
botcheck / _gotcha Honeypot. Anything filled in here marks the submission as spam.
altcha The solved ALTCHA challenge.
h-captcha-response hCaptcha token, when that provider is switched on.
cf-turnstile-response Cloudflare Turnstile token.
g-recaptcha-response Google reCAPTCHA token.
mcaptcha__token mCaptcha token.

Values the submitter controls are validated: addresses must parse, free text is stripped of line breaks and capped. An invalid value falls back to the form's own setting instead of being used. A routing rule always wins over these fields — a rule is a server-side decision, the field travels in a request anyone can craft.

HTML

File uploads

Send the request as multipart/form-data and every file part is stored, attached to the notification mail and downloadable from the dashboard. The field name is yours to choose; several files may share one name.

Limit Default Environment variable
Size per file 10 MB UPLOAD_MAX_FILE_SIZE_MB
Files per submission 5 UPLOAD_MAX_FILES

The limits are enforced while the request is being read, so an oversized part is aborted mid-stream and answered with 413 rather than buffered first. Check size and count in the browser as well — that turns a failed request into a helpful message.

bash

Captcha

ALTCHA is a proof-of-work captcha: no cookies, no third party, no puzzle for the visitor — the browser spends a moment of computing time, and that is exactly what makes bulk submission expensive for a bot. Fetch a challenge, solve it, send the solution back in the altcha field.

bash

Order matters: deliver the token first, then switch the requirement on in the form settings. The other way round every submission is rejected with 403. The client script does the whole dance for you — see its own page.

Self-hosted? ALTCHA_HMAC_KEY has to be set in the instance, otherwise the challenge endpoint answers 404 and the form setting has no effect.

After the submission

For a browser form post the server answers 303 and sends the visitor onwards. Four candidates are checked in this order: a routing rule, the redirect field, the form's configured URL, and finally our hosted thank-you page.

Every target is validated against the form's allowed domains before it is used. A rejected target is logged and the next candidate takes over — without that check the endpoint would be an open redirect on our own domain.

Errors of this endpoint

Code Cause
404 Missing access key Neither in the URL nor in the body.
404 Unknown access key No form has this key.
403 This form is blocked The form was disabled — see the dashboard.
403 Origin not permitted The request came from a domain that is not allowed.
403 Captcha verification failed Token missing, wrong or already used.
429 Per-form, per-IP or monthly limit reached.
413 An upload was larger than the configured maximum.

A submission that ran into a timeout may still have been stored. Never retry it blindly — you would create a duplicate. Read requests are safe to repeat.