How to add a contact form to a static site (no backend, no signup walls)
A practical guide to wiring an HTML form to a hosted endpoint when you do not want to spin up a server, write a route handler, or babysit a database.
You built a static site. Maybe it's a Next.js export, an Astro build, a hand-rolled HTML page on Cloudflare Pages, or something a designer handed you to deploy. Everything works fine, until someone asks you to add a contact form. Suddenly you need a server. Or do you?
You don't need a backend to collect form submissions. You need an endpoint.
The four bad answers most tutorials give
Search “static site contact form” and you'll get four well-meaning suggestions, all with sharp edges:
- A
mailto:link. Opens the visitor's default mail client, which on mobile might be a half-configured Gmail web view, and on desktop might be no client at all. Conversion rate from a real form: a fraction of one percent. - An embedded Google Form. Free, but the iframe ruins your design and trains visitors that you're fine with their data ending up in a spreadsheet they have no relationship with.
- Spin up a server. Now you own a route handler, a database, an SMTP setup, an SSL cert, a deploy pipeline, and a backup story. For a contact form.
- A no-code SaaS with five upsells. Drag- and-drop form builders that own your design, lock you to their CDN, and try to upsell webhooks at $29/month.
What you actually want
For 95% of static-site contact forms, the requirements are short:
- Keep the HTML form you already designed.
- Submissions land somewhere you can read them later.
- You get an email when one comes in.
- Spam stays out by default.
- No server to run.
That set of requirements has a category of solution called a form backend service: a hosted endpoint you POST your form to. SaveForm is one. Formspree, FormSubmit, Web3Forms, and Netlify Forms are others. The shape is the same: you point your form's action at a URL and their server takes care of the rest.
The 5-minute version (no JavaScript)
Here's the entire integration if you don't want to write a single line of JavaScript:
<form action="https://saveform.io/api/submit/YOUR_FORM_ID" method="POST"> <!-- Where the notification email lands --> <input type="hidden" name="_emailTo" value="you@example.com" /> <!-- Your real fields --> <input type="text" name="name" required /> <input type="email" name="email" required /> <textarea name="message" required></textarea> <button type="submit">Send</button> </form>
That's the whole thing. The browser handles the POST natively, the user gets a clean success page, and you get an email. No JavaScript, no fetch handler, no error states to write.
When you do want JavaScript
The HTML-only version is great until you want a custom “Thanks, we'll be in touch” UI without a full page navigation. Then you reach for fetch:
const form = document.querySelector('#contact-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(form));
const res = await fetch('https://saveform.io/api/submit/YOUR_FORM_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(data),
});
if (res.ok) showSuccessUI();
else showErrorUI();
});Send Accept: application/json and the response comes back as JSON instead of an HTML success page. From there, swap the form out for a thank-you message, fire your analytics event, whatever you want.
Things that bite you 90 days in
The 5-minute setup gets you a working form. The interesting problems show up later, and they're the same regardless of which service you pick:
- Spam. Two weeks after launching a public form, bots will find it. Pick a service that does honeypot or content scoring out of the box (there's a separate post on this).
- Where submissions live. Email notifications are great until you need to find “that one submission from three months ago”. A real dashboard you can search and export beats grepping your inbox.
- Success UX. The default success page works, but if you want analytics events firing, custom redirects, or a thank-you page that matches your design, plan for it (post on the four ways to do this).
- Routing logic. One day you'll want the contact form to also ping your team's Slack channel, create a Linear issue, or trigger a Zapier workflow. Make sure your service supports webhooks before you commit.
A boring recommendation
Pick the simplest option that covers spam, dashboard storage, email notifications, and webhooks. Avoid anything that ties you to a drag-and-drop form builder. Your HTML is fine; you don't need to rebuild it inside someone else's WYSIWYG. And avoid the mailto: trick. It feels free; the cost is the silently-lost submissions you never know about.
A form endpoint in five minutes
SaveForm gives you a form action URL, a dashboard, email notifications, honeypot spam filtering, and webhooks. 100 submissions/month on the free tier, no credit card.