A contact form for a static site, without a form SaaS

A static site cannot receive a form on its own. The options, how to stop spam, the confirmation people forget, and the small gateway we run for our sites.

Published · 5 min read · By webformsCloudflare

A single envelope sliding through a slim slot in a dark glass wall, the slot outlined in blue light.
Illustration generated for this article (fal.ai, Nano Banana 2): one narrow, guarded way in.
In short
  • A static site has no server code, so the form must post to a backend: a form SaaS, a serverless function, or a shared gateway like ours.
  • Fight spam in layers: a honeypot field, a Turnstile check on the server, an allowlist of origins and rate limits.
  • Store first, email second. A failed notification must never lose a message.
  • Send the visitor a short confirmation, but only after the message is stored, only for verified humans, and with a limit per address.

A static site is a folder of HTML, CSS and images served as they are. That is why it is fast, cheap and hard to break, and it is also why a contact form on a static site does nothing by itself: there is no code on the server to receive the message. The form has to send its fields somewhere that can store them and tell you about them. This article covers the three ways to do that, the spam and confirmation details that decide whether the form is any good, and what we built so that every KH Group and Flowsmith site shares one form backend.

Three ways to receive a form

1. A form SaaS

You point the form's action at a hosted endpoint and the service emails you the submissions. It takes five minutes and needs no code. The costs come later: submission limits on cheaper plans, your visitors' messages stored on someone else's servers, branding in the confirmation emails, and one more account per site. For one personal site, it is often the right choice.

2. A serverless function per site

Most static hosts can run small functions next to the site, for example Cloudflare Pages Functions or a Worker. A function receives the POST, checks it and sends an email. You own everything, and the cost at small scale is close to zero. The downside shows up with the second and third site: each one gets its own copy of the spam checks, email sending and storage, and the copies drift apart.

3. One gateway for all your sites

This is what we run. A single Cloudflare Worker at forms.flowsmith.online receives forms for every site we operate. A page posts to /f/<site>/<form>. The gateway checks the request, stores it and notifies the right person. A form is a definition in a database (recipient, allowed origins, required fields), not code, so adding a form to a new site is a data change that takes effect immediately, without a redeploy. The forms gateway manual documents the API and the command-line tool.

Spam: layers, not a single trick

Any public form gets bot traffic within days. No single measure stops all of it, but four cheap layers together leave very little:

  • Honeypot. A field hidden from people with CSS. Humans leave it empty, many bots fill it in. Ours is called company_website, and the name has to match between the page and the gateway, or real messages get dropped.
  • Cloudflare Turnstile. A challenge that most visitors never see, with the token verified on the server. Checking it only in the browser proves nothing, because a bot can skip the browser.
  • Origin allowlist. Each form definition lists the sites allowed to post to it (apex and www). A form for one site cannot be abused from another.
  • Required fields checked on the server. The definition says which fields must be present. Browser validation is a courtesy to the visitor, not a control.

Store first, then send the email

The most common way contact forms lose enquiries is quiet: the notification email fails, and because the email was the storage, the message is gone. Nobody notices until a customer phones to ask why nobody answered.

Our gateway follows one rule that is written into its brief: email must never gate storage. Every submission is written to a Cloudflare D1 database first. Only then is the notification sent, and if sending fails the error is logged and the submission stays in the database and in the admin. Submissions are visible in a small admin protected by an emailed one-time PIN, and a service token used by scripts can manage form definitions but cannot read what people wrote.

Test it as the visitor would: submit the real form from the real page, then check three places. Is the row in the database? Did the notification arrive? Did the visitor get what they were promised? Our Academy lesson Your first automation uses the same habit: test from the other side.

The confirmation that is often forgotten

From the visitor's side, a form that says "thank you" and then stays silent for a day feels broken. A short automatic email saying the message arrived, with a copy of what they wrote, removes that doubt. It is also the part most often left out, and when it is done carelessly it turns the form into a tool for sending email to strangers.

Our gateway sends the confirmation with three guards:

  1. Only after the submission is stored. The confirmation describes a fact, not an intention.
  2. Only when the Turnstile check passed. No verified human, no outgoing email.
  3. Rate-limited. At most one confirmation per address every ten minutes and thirty per hour in total. Someone who types a stranger's address into your form cannot use it to flood that person's inbox.

A failure in the confirmation never changes the answer the visitor sees. Their message is stored either way.

Works without JavaScript

A good form still works when scripts fail to load. Ours is a normal HTML form with a _redirect field: without JavaScript the browser posts it and the gateway answers with a redirect to the thank-you page. With JavaScript, a small script submits it in the background and shows the result in place. The same endpoint serves both.

There is also a second way to put a form on a page: a dynamic embed, one placeholder element plus a script, which fetches the public form definition and renders the fields in the page's own style and language. When someone changes the form in the admin, every page that embeds it updates by itself. The static snippet is better when a form must work without JavaScript. The embed is better when the form changes often. The step-by-step guide shows both.

Which option fits you

SituationReasonable choice
One personal site, a few messages a monthA form SaaS, or a single function if you already use one
A business site where enquiries are revenueYour own function or gateway, storage first, confirmation with limits
Several sites run by the same teamOne shared gateway with form definitions as data
Enquiries feed an AI triage or drafting flowA gateway that stores structured submissions you can query

If you want to build your own, the Cloudflare Workers manual covers the moving parts: a route, a D1 binding and secrets. What happens to an enquiry after it arrives is a separate question, covered in AI inbox management for a small sales team.

Frequently asked questions

Can a static site have a contact form?

Yes, but the form has to post to something that runs code: a form service, a serverless function or a small gateway. The static page only holds the HTML; the receiving end stores the message and sends the notification.

Does a form work without JavaScript?

It can. A plain HTML form that posts to the endpoint and is redirected to a thank-you page works with JavaScript switched off. JavaScript then only improves the experience, for example by showing the result without a page reload.

Is a honeypot field enough against spam?

Not on its own. A hidden honeypot field stops the simplest bots cheaply, but you also want a challenge such as Cloudflare Turnstile checked on the server, and rate limits on anything that sends email automatically.

Where should form submissions be stored?

Somewhere you own and can query, before any email is sent. We write every submission to a database first; if the notification email fails, the message is still safe.

Written from our own production systems. Drafted with AI assistance and reviewed by Kamil Hrbáček.

Want this running in your company?

Flowsmith designs and operates automations like this for small and mid-sized teams — with a person approving anything that leaves the building.

More from the blog