A contact form is the one thing on a brochure site that looks like it needs software. It does not. It needs an HTML form and somewhere for the submission to land.
Here is the whole thing.
<form action="https://formspree.io/f/your-form-id" method="POST">
<label>Your email
<input type="email" name="email" required>
</label>
<label>Message
<textarea name="message" rows="5" required></textarea>
</label>
<button type="submit">Send</button>
</form>
Paste that into a page. Sign up with a form service, put your endpoint in the action, and the next submission arrives in your inbox. There is nothing else to install and nothing to log into afterwards.
What is actually happening
An HTML form has been able to POST to any URL since 1995. It does not care whether that URL belongs to your server. So you point it at a service whose entire job is to receive a POST, check it is not spam, and email it to you.
That is the whole trick. The work that a form plugin does — accepting the submission, storing it, mailing it, filtering spam — still happens. It just happens on somebody else's server, for free or for a few dollars a month, and you are not the one keeping it patched.
The work still happens. It just stops being yours to maintain.
The version I actually ship
The nine-line version works. This one is what I leave with a client, and it is still under thirty lines.
<form action="https://formspree.io/f/your-form-id" method="POST">
<label for="name">Your name</label>
<input id="name" type="text" name="name" required autocomplete="name">
<label for="email">Email</label>
<input id="email" type="email" name="email" required autocomplete="email">
<label for="message">What do you need?</label>
<textarea id="message" name="message" rows="6" required></textarea>
<!-- Honeypot. Real people never fill this in; bots usually do. -->
<p hidden>
<label>Leave this empty<input name="_gotcha" tabindex="-1" autocomplete="off"></label>
</p>
<!-- Where the sender lands after submitting. -->
<input type="hidden" name="_next" value="https://example.com/thanks/">
<!-- What the email's subject line says. -->
<input type="hidden" name="subject" value="Enquiry from the website">
<button type="submit">Send</button>
</form>
subject, _next and _gotcha field names are Formspree's. Other services use their own; check their docs.Four things were added, and each earns its line:
- Explicit labels with
forandid. A screen reader reads the label with the field. Wrapping works too, but this survives being restyled later. - A honeypot field. Hidden from people, visible to naive bots. It catches a surprising share of junk for one line of markup.
- A thank-you page. Write
thanks.htmlyourself so the sender lands somewhere that looks like your site instead of the form service's default page. - A subject line. So the email is findable in six months.
Picking a service
Formspree is used in the example because it is the one I have the longest history with, but there is nothing special about it. Web3Forms, Basin, Forminit (which was Getform until it renamed in January) and Netlify Forms all do the same job. If you are already hosting on Netlify, form handling is built in — though on their free plan it draws down the same monthly credit pool as your traffic does, which is worth understanding first.
Four things worth checking before you commit, in this order:
- What the free tier caps. Usually submissions per month. A plumber gets a handful a week; a busy restaurant might not fit.
- Whether submissions are stored, and for how long. If email is your only copy, a bounced email is a lost enquiry.
- Where the data is held. Relevant if you are in the UK or EU and taking personal details.
- What happens on the free tier if you exceed the cap. Queued, dropped, or upgraded automatically? They differ, and only one of those is acceptable.
I am deliberately not printing each service's current limits here, because they change and a stale number in a blog post is worse than no number. Open the pricing page and read it — it takes two minutes, and it is the only research this decision needs.
If the service disappears
It is the obvious objection, so here is the honest answer: you change one URL, in one file, and the form works again. Your markup is not tied to the vendor. The only thing you lose is submissions already stored on their side, which is why the second item on the checklist above is there.
Compare that to migrating off a form plugin, where the submissions live in a database table with the plugin's own schema and the export button is a paid feature.
Get the next teardown.
One email when there is a new post. No drip sequence, no webinar, no course.
This form is the same trick. No database, no plugin, no cookie banner.