To make an HTML form work without a backend, set its action attribute to a hosted form endpoint such as https://formsubmit.app/f/YOUR_FORM_ID and its method to POST. The form backend receives the submission, filters spam, stores it and emails it to you. You don't write PHP, Node.js, or any server code, and it works on any static host — GitHub Pages, Netlify, Vercel, Cloudflare Pages, S3 or plain shared hosting.
Why an HTML form can't send email on its own
A <form> element only knows how to package the values of its inputs and send them in an HTTP request to a URL. Something on the other end has to receive that request, do something useful with it and respond. Traditionally that was a PHP script on the same server, which meant configuring mail() or SMTP, fighting deliverability, and hardening the script against spam.
A mailto: action looks like a shortcut but it only opens the visitor's email client (if they have one configured) and gives you no control, no spam protection and no record of the message.
A form backend is a hosted service that plays the role of that server script for you.
Step 1: Create your endpoint
Sign in to FormSubmit with Google and click New form. You'll get an endpoint like:
https://formsubmit.app/f/k3m9p2qabxStep 2: Point your form at it
Here's a complete, accessible contact form. Replace YOUR_FORM_ID with your id:
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST">
<div>
<label for="fs-name">Name</label>
<input id="fs-name" type="text" name="name" placeholder="Jane Doe" required>
</div>
<div>
<label for="fs-email">Email</label>
<input id="fs-email" type="email" name="email" placeholder="jane@example.com" required>
</div>
<div>
<label for="fs-message">Message</label>
<textarea id="fs-message" name="message" rows="5" placeholder="How can we help?" required></textarea>
</div>
<!-- Honeypot: leave this hidden field empty to catch bots -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
<!-- Optional: where to send people after submitting -->
<!-- <input type="hidden" name="_redirect" value="https://yoursite.com/thanks"> -->
<button type="submit">Send</button>
</form>The important parts are:
action="https://formsubmit.app/f/YOUR_FORM_ID"— where the data goes.method="POST"— required.- A
nameon every input. Inputs without anameare not submitted by the browser. The name becomes the label in your email ("name" becomes "Name", "company_size" becomes "Company size"). - The hidden
_gotchainput — a honeypot that catches bots. Leave it empty and hidden.
Step 3: Style it
Want something that looks good without writing CSS? Use the version with a small embedded stylesheet:
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST" class="fs-form">
<div class="fs-field">
<label for="fs-name" class="fs-label">Name</label>
<input id="fs-name" type="text" name="name" placeholder="Jane Doe" required class="fs-input">
</div>
<div class="fs-field">
<label for="fs-email" class="fs-label">Email</label>
<input id="fs-email" type="email" name="email" placeholder="jane@example.com" required class="fs-input">
</div>
<div class="fs-field">
<label for="fs-message" class="fs-label">Message</label>
<textarea id="fs-message" name="message" rows="5" placeholder="How can we help?" required class="fs-input"></textarea>
</div>
<!-- Honeypot: leave this hidden field empty to catch bots -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
<!-- Optional: where to send people after submitting -->
<!-- <input type="hidden" name="_redirect" value="https://yoursite.com/thanks"> -->
<button type="submit" class="fs-button">Send</button>
</form>
<style>
.fs-form { max-width: 32rem; margin: 0 auto; display: grid; gap: 1.1rem; font-family: system-ui, sans-serif; }
.fs-field { display: grid; gap: 0.35rem; border: 0; padding: 0; margin: 0; }
.fs-label { font-size: 0.875rem; font-weight: 600; color: #18181b; }
.fs-input { width: 100%; box-sizing: border-box; border: 1px solid #d4d4d8; border-radius: 0.5rem; padding: 0.6rem 0.75rem; font: inherit; font-size: 0.9rem; }
.fs-input:focus { outline: 2px solid #6366f155; border-color: #6366f1; }
.fs-choice { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
.fs-help { font-size: 0.75rem; color: #71717a; margin: 0; }
.fs-button { background: #18181b; color: #fff; border: 0; border-radius: 0.5rem; padding: 0.7rem 1rem; font-weight: 600; cursor: pointer; }
.fs-button:hover { background: #27272a; }
</style>Using Tailwind? The form generator exports a Tailwind version too.
Step 4: Choose what happens after submit
By default visitors land on a clean hosted thank-you page. To send them to your own page, add a hidden _redirect field or set a redirect URL in the form's settings:
<input type="hidden" name="_redirect" value="https://yoursite.com/thanks.html">Other useful hidden fields are _subject (email subject) and _replyto. See control fields.
Step 5: Test it
Open the page, submit the form, and you should see:
- A redirect to the thank-you page.
- The submission in your FormSubmit dashboard.
- An email in your inbox with every field, where hitting Reply answers the visitor directly.
Optional: stay on the page with a little JavaScript
If you'd rather show "Thanks!" inline, intercept the submit and use fetch. FormSubmit returns JSON when you send Accept: application/json:
// Works with any existing <form> — add id="contact-form" to it.
const form = document.getElementById("contact-form");
const renderedAt = Date.now();
form.addEventListener("submit", async (event) => {
event.preventDefault();
const data = new FormData(form);
data.set("_ts", String(renderedAt));
const res = await fetch("https://formsubmit.app/f/YOUR_FORM_ID", {
method: "POST",
body: data, // or JSON.stringify(Object.fromEntries(data)) with Content-Type: application/json
headers: { Accept: "application/json" },
});
const json = await res.json();
if (json.ok) {
form.reset();
form.insertAdjacentHTML("afterend", "<p>Thanks! We'll be in touch soon.</p>");
} else {
alert(json.error?.message ?? "Something went wrong");
}
});Hardening checklist
- Add your domain to the form's allowlist so the endpoint only accepts submissions from your site.
- Keep the honeypot. For heavily targeted forms, add Cloudflare Turnstile or reCAPTCHA.
- Decide how long to keep submissions with retention.
- Connect Slack or Google Sheets if email isn't where your team works.