How to Add a Contact Form to a Static HTML Website
A static HTML page cannot receive form submissions on its own, because there is no server behind it. The fix used in this tutorial is a hosted form backend: you create a form with a service like Form Backend Tool, point your HTML form at the endpoint it gives you, and every submission is stored in a dashboard and emailed to you. No server to run, no JavaScript required. The background is in What Is a Form Backend? and Form Backend for Static Websites; this page is the hands-on version.
What you need
- A static HTML page you can edit (your contact page, or any page the form belongs on).
- A free Form Backend account. No credit card is needed for the Free plan.
- An email address you control. Submissions are delivered there, so it has to be verified before notifications are sent to it.
Step 1: Create the form and get your endpoint
- Sign up and create a form. In the dashboard, create a form and give it a name you will recognise, for example "Website contact form".
- Set the notification email. This is the address that receives each submission. If the dashboard shows it as unverified, use the verify action and click the link in the email it sends; the service only delivers to verified addresses.
- Copy the endpoint. The form's page in the dashboard shows its endpoint URL, which looks like
https://form-backend-tool.vercel.app/api/forms/YOUR_FORM_KEY, and a ready-made HTML snippet. Every form has its own key, which is how the service knows which form a submission belongs to.
Step 2: Add the HTML form to your page
Paste this form where the contact form belongs on your page, and replace YOUR_FORM_KEY in the action with the key from your dashboard:
<form action="https://form-backend-tool.vercel.app/api/forms/YOUR_FORM_KEY" method="POST">
<label>Your name <input type="text" name="name" required></label>
<label>Your email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<!-- Optional: after a successful submit, send the visitor to your own page.
Must be an absolute http:// or https:// URL. -->
<input type="hidden" name="_redirect" value="https://your-site.example/thanks">
<!-- Honeypot: leave this empty and hidden. People never see it;
bots fill it in, and those submissions are flagged as spam. -->
<input type="text" name="_honey" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>The parts that matter:
actionis the endpoint. It is the whole mechanism: when the form is submitted, the browser sends the fields to this URL. Leave it off or mistype the key and submissions go nowhere (a wrong key answers 404, see Troubleshooting).method="POST"is required. The endpoint only accepts POST; any other method is refused with 405.- Each field's
namebecomes its label. A field namedemailshows up as "email" in your dashboard and in the notification email. Name fields for the reader who receives them, not for the page. requiredis the browser's check. It stops an empty form before anything is sent. The endpoint also rejects a submission whose fields are all empty._redirectchooses the thank-you page. After a successful submit, the visitor is sent to this absolute URL, ideally a thanks page on your own site. Without it, they see a simple built-in "Thank you" page from the service. The docs describe both._honeyis the spam trap. It is a field real visitors never see and never fill in. Bots that complete every field give themselves away, and their submissions are flagged as spam in your dashboard instead of emailed to you.
Already have a form on the page? You do not need this exact markup. Set your existing form's action to the endpoint and its method to POST, keep your name attributes, and add the two hidden fields.
Step 3: Test your form before you announce it
- Submit it like a visitor. Open the live page, fill the form with a message you will recognise, and press send. You should land on your
_redirectpage, or on the built-in thank-you page. - Check the dashboard. The test submission should be listed under Submissions, unread, with every field you sent. This proves the store half of the flow.
- Check your inbox. The same submission should arrive as an email at your notification address. If the dashboard has it but the inbox does not, the address is almost certainly not verified yet; see Troubleshooting.
This exact flow is also running in the live demo on the homepage, which posts to a real endpoint if you want to see the result before wiring up your own page.
What happens when someone submits
- The browser sends the fields as a POST request to your form's endpoint.
- The endpoint checks the submission: supported format, at most 25 KB, a rate limit of 10 submissions per 60 seconds per form per visitor, and the honeypot.
- A clean submission is stored first. Only then is it delivered, so a failed notification can never lose it.
- It appears in your dashboard (read/unread, filter, CSV export) and is emailed to your verified notification address. Spam is flagged in the dashboard and not emailed.
- Your visitor sees your thank-you page, or the built-in one.
Troubleshooting
- The form answers "Form not found" (404). The key in the
actionURL is wrong. Copy the endpoint again from the form's dashboard page instead of typing it. - "This form is not accepting submissions" (403). The form is paused or disabled in the dashboard. Re-enable it there.
- Method not allowed (405). The form is not sending POST. Check that
method="POST"is on the form tag; some site builders strip it. - Submissions reach the dashboard but no email arrives. Two usual causes: the notification address is not verified yet, or the submission was flagged as spam (it is then kept in the dashboard, marked as spam, and not emailed). If a real visitor's message keeps landing in spam, check that nothing on your page auto-fills the hidden
_honeyfield. - "Too many submissions" (429) while testing. You hit the rate limit of 10 submissions per 60 seconds from one visitor. Wait a minute and test again; real visitors do not submit that fast.
- The redirect does not happen.
_redirectmust be a complete absolute URL starting with http:// or https://, not a relative path like/thanks. - The answer does not match your problem. The error reference lists every status code the endpoint returns and what each one means, and the support page reaches the founder if you are still stuck.
Optional: submit without a page reload
The plain form above is the right default for a static HTML contact form: it works with JavaScript disabled and needs no code beyond the markup. If you would rather keep the visitor on the page and show your own confirmation, send the fields with fetch and ask for JSON back:
await fetch("https://form-backend-tool.vercel.app/api/forms/YOUR_FORM_KEY", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({
name: "Ada",
email: "ada@example.com",
message: "Hello from my static site",
}),
});A successful call answers {"ok": true, "message": "Submission received."}, and errors answer with the same codes as the error reference. The docs example covers the response format in full.
Limits of this approach, stated plainly
- File uploads are not stored. Do not use this for a form whose point is an uploaded file, such as a CV or a photo. File inputs are ignored; only text values are kept.
- No autoresponder. Your visitor's confirmation is the thank-you page, not an automatic reply email from the service.
- No outgoing webhooks. Submissions are not pushed into a CRM or other system; delivery is the dashboard and email (plus WhatsApp alerts on paid plans).
- Size and volume limits. 25 KB per submission, up to 50 fields, values up to 5,000 characters, and monthly quotas per form of 250 on the Free plan, 2,500 on Pro ($12/month) and 10,000 on Agency ($35/month), all listed in the docs limits and the not supported list.
If your "contact form" is really an application, with logins, payments or stored files, it has outgrown this approach and deserves a real backend, as the explainer discusses. For an actual contact form on an actual static site, the three steps above are the whole job.