Forms

A contact form on a static site, with no backend and no script: the form posts to Sitebin, and Sitebin emails each submission to one recipient, who agreed to receive them.

Forms are part of Pro (1 per site) and Studio (10 per site) on the hosted service. Self-hosted instances have them as soon as SITEBIN_FORMS_SMTP_HOST is set — see Self-hosting.

Add a form

On the site's edit page, open Forms and give the form a name and a recipient. The name is what the recipient sees as the sender of every message. The recipient gets one email to confirm; until they click it, the form refuses submissions. That confirmation is what keeps a form from ever mailing someone who did not ask for it.

Then copy the form's snippet into any page of the site:

<form action="/_sitebin/forms/k7f3m2q9xaw4npd6" method="post">
  <label>Name <input name="name" required></label>
  <label>Email <input name="email" type="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <input name="_gotcha" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px">
  <button type="submit">Send</button>
</form>

The form posts to its own site — the site's address or your custom domain — so there is nothing to allow and nothing to load. Style it and add fields as you like: every field is forwarded, in the order the form has them.

Fields with a meaning

FieldWhat it does
emailBecomes the mail's Reply-To, so answering the mail answers the visitor. Only when it holds exactly one address, and not when that address is in the recipient's own domain: mail filters such as Microsoft 365 treat that as phishing, so the address then only appears in the message.
_subjectThe mail's subject (one line, up to 200 characters). Without it: New message via <form name>.
_gotchaA honeypot, moved off-screen. People never fill it; bots do. A submission with it filled is answered like a success and sends nothing.
_…Any field starting with an underscore is for you and the form, and is never forwarded.

Captcha

Switch on Captcha and the snippet gains two lines: an ALTCHA widget and its script. The visitor's browser solves a small proof-of-work puzzle — no images, no tracking, no third party: the challenge and the script come from Sitebin itself. A solution is valid for five minutes, for this form only, once.

<altcha-widget challenge="/_sitebin/forms/k7f3m2q9xaw4npd6/challenge"></altcha-widget>
<script type="module" src="/_sitebin/altcha.js"></script>

If your page sets its own Content-Security-Policy, allow worker-src blob:: the widget solves in web workers.

Attachments

Switch on Attachments and the form posts multipart/form-data with a file field. Up to 5 files of 2 MB each travel with the mail. Executable files (.exe, .js, .bat, .msi and the like) are refused: mail providers reject whole messages over them.

What arrives

One email per submission, from the form's name, with an HTML and a plain-text part that lay out the fields in order, the attachments, and a stop link. Every mail also carries submission.json, for anything that reads mail by machine:

{
  "version": 1,
  "form": { "key": "k7f3m2q9xaw4npd6", "name": "Contact" },
  "site": { "id": "…", "host": "www.example.com" },
  "submitted_at": "2026-09-24T10:15:00Z",
  "fields": [
    { "name": "name", "value": "Anna Muster" },
    { "name": "email", "value": "anna@example.com" },
    { "name": "message", "value": "Hello!" }
  ],
  "files": [
    { "field": "cv", "filename": "cv.pdf", "content_type": "application/pdf",
      "size": 183244, "sha256": "…" }
  ]
}

fields is a list, so repeated names (a group of checkboxes) and the order survive. The visitor's IP address is not included. Sitebin keeps no copy: a submission is mailed and gone.

After sending

Without JavaScript the browser lands on the form's thank-you page — a path on the same site you set, such as /thanks.html — or a plain default page. If something is wrong (the form is not confirmed yet, a file is too large, too many messages in a short time), the visitor sees why, with a way back.

Submitting with your own fetch? Send Accept: application/json and get {"ok":true}, or {"error":"…"} with the status: 403 for a form that is not active or a failed captcha, 413 for too large, 429 for too many, 502 when the mail could not be sent — try again.

The recipient decides

Every mail has a Stop emails from this form link, and mail clients show their own unsubscribe button for it. A stopped form refuses submissions until the site's owner resends the confirmation and the recipient accepts again. Changing the recipient asks the new address first.

Limits

  • Forms per site: Pro 1, Studio 10. When a plan shrinks, the newest forms pause — nothing is deleted — and resume when the plan allows again.
  • 10 submissions per visitor per hour across all forms, and 60 per form per hour.
  • 50 fields of up to 10,000 characters; 5 files of 2 MB.

API and agents

Forms are managed with the site's edit password or an account token: GET/POST /api/sites/{edit_id}/forms, PUT/DELETE …/forms/{key}, and POST …/forms/{key}/confirmation to resend — see the API. Agents have the same through MCP: list_forms, add_form, update_form, remove_form and resend_form_confirmation.

Self-hosting

Forms are open source, in every edition. Point SITEBIN_FORMS_SMTP_HOST at an SMTP server and set SITEBIN_FORMS_SMTP_FROM to the address mail leaves from; each form supplies the display name. This mailer is separate from the one that sends account mail. All variables are on the configuration page.

With accounts enabled, a tier also needs "trusted": true to get forms at all: an untrusted site is served with form-action 'none', which leaves a plain form nothing to post to. See Enterprise setup for the tier fields.