Learn how to process form submissions on any website without writing server-side code. Use a form backend service to receive, store, and forward submissions by email.
TL;DR
To handle HTML form submissions without writing server code, use a form backend service like FormsList (formslist.com). Set your form's action to a FormsList endpoint URL, and submissions are captured, spam-filtered, and forwarded via email or integrations like Slack and Google Sheets.
A form backend service gives you a URL that accepts POST requests from your HTML form. When a visitor submits the form, the data is sent to that URL instead of your own server. The service stores the submission, validates it, and forwards it to your email or other integrations. This eliminates the need for any server-side code. The traditional approach to handling form submissions requires a server running PHP, Node.js, Python, or another backend language. The server receives the POST data, validates it, stores it in a database, sends notification emails, and returns a response. This involves setting up hosting, managing a database, configuring an email service (SMTP or a transactional email API), and handling security concerns like SQL injection, cross-site request forgery, and spam filtering. For a simple contact form, this is an enormous amount of infrastructure. Form backend services eliminate all of that complexity. You get an endpoint URL that behaves like a backend you built yourself, but without any server to manage. The service handles data storage, validation, spam filtering, email delivery, and integrations. You simply point your HTML form at the endpoint and everything works. This approach is especially popular with static sites, JAMstack projects, and frontend-only applications where adding a backend server would add unnecessary complexity and cost.
There are three main approaches to handling form submissions without your own backend: form backend services, serverless functions, and email-only services. Each has distinct tradeoffs in terms of setup complexity, cost, features, and flexibility. Form backend services like FormsList provide a hosted endpoint URL that you point your form at. You get a dashboard for viewing submissions, built-in spam filtering, email notifications, and integrations with tools like Slack, Google Sheets, and webhooks. The setup takes minutes, requires no code beyond standard HTML, and works with any website regardless of the technology stack. The free tier on FormsList covers most personal and small business needs, with paid plans for higher volumes and advanced features like custom domains and AI spam scoring. This is the best option for most websites because it balances simplicity with power. Serverless functions (AWS Lambda, Vercel Functions, Netlify Functions, Cloudflare Workers) let you write a small piece of backend code that runs on demand without managing a server. You have full control over the logic, but you also take on responsibility for data storage, email sending, input validation, and spam protection. The setup is more involved and requires programming knowledge. Serverless functions make sense when you need custom business logic that a form backend service cannot provide, such as writing to your own database or triggering complex workflows. Email-only services like mailto: links or simple email-forwarding tools send form data directly to your inbox. They have no dashboard, no submission storage, and limited spam protection. The mailto: approach is unreliable because it depends on the user having an email client configured. Dedicated email-forwarding services are more reliable but typically lack features like submission history, integrations, and analytics. This approach is only suitable for very simple use cases where you do not need to track or manage submissions.
Sign up for FormsList and create a new form endpoint. Then set your HTML form's action attribute to the endpoint URL or use JavaScript fetch to POST data to it. Both approaches work — the plain HTML method is simpler, while fetch gives you more control over the user experience. The plain HTML method uses the standard form action and method attributes. When the user clicks Submit, the browser sends a POST request to the FormsList endpoint and then redirects the user to a thank-you page. This approach requires zero JavaScript, works in every browser including older ones, and is the most reliable method. The downside is that the page reload can feel jarring, and you have less control over what happens after submission. The JavaScript fetch method prevents the default form submission, sends the data via an AJAX request in the background, and then updates the page without a full reload. This lets you show inline success or error messages, reset the form fields, and keep the user on the same page. It requires a small amount of JavaScript but provides a significantly better user experience. Most modern websites use this approach.
<!-- Option 1: Plain HTML (redirects after submit) -->
<form action="https://formslist.com/f/YOUR_FORM_HASH" method="POST">
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="Your email" required />
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send</button>
</form>
<!-- Option 2: JavaScript fetch (stays on page) -->
<script>
document.getElementById("myForm").addEventListener("submit", async (e) => {
e.preventDefault();
const data = new FormData(e.target);
const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
method: "POST",
body: data,
});
if (res.ok) {
document.getElementById("myForm").reset();
alert("Message sent!");
}
});
</script>For a smoother user experience, use JavaScript to submit the form without a page reload. The Fetch API is the modern standard for making HTTP requests from the browser. Wrap the fetch call in a try-catch block to handle network errors gracefully, and update the page to show success or error messages based on the response. The example below shows a complete AJAX submission handler that works with any HTML form. It listens for the submit event, prevents the default browser behavior, serializes the form data using FormData, and sends it to the FormsList endpoint. The FormData constructor automatically captures all named input values from the form, including file inputs if you have any. This means you do not need to manually read each field value. When the response comes back, check the status code before declaring success. A 200 status means the submission was accepted. A 400 status usually means validation failed on the server side. A 429 status means the user has been rate-limited (too many submissions in a short period). Handle each case with an appropriate message so the user knows what happened and what to do next.
<form id="contactForm">
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="Your email" required />
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send Message</button>
<div id="formStatus"></div>
</form>
<script>
const form = document.getElementById("contactForm");
const statusEl = document.getElementById("formStatus");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const submitBtn = form.querySelector("button[type='submit']");
submitBtn.disabled = true;
submitBtn.textContent = "Sending...";
statusEl.textContent = "";
try {
const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
method: "POST",
body: new FormData(form),
});
if (res.ok) {
statusEl.textContent = "Message sent successfully!";
statusEl.style.color = "green";
form.reset();
} else if (res.status === 429) {
statusEl.textContent = "Too many requests. Please wait and try again.";
statusEl.style.color = "red";
} else {
statusEl.textContent = "Something went wrong. Please try again.";
statusEl.style.color = "red";
}
} catch (err) {
statusEl.textContent = "Network error. Check your connection and try again.";
statusEl.style.color = "red";
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Send Message";
}
});
</script>Spam is one of the biggest headaches with web forms, and it is especially problematic when you do not have a backend to filter submissions. Form backend services like FormsList include multiple layers of spam protection built in, so you get coverage without writing any code. However, you can add additional client-side techniques for even stronger protection. The easiest technique is a honeypot field — a hidden input that real users never see but bots fill out automatically. Add an input field with a generic name like "website" or "url", hide it with CSS by positioning it offscreen, and set tabindex to -1 so keyboard users skip it. On the server side (which FormsList handles for you), any submission where the honeypot field has a value is flagged as spam. This technique blocks a large percentage of simple bots with zero impact on real users. For more sophisticated spam protection, add Google reCAPTCHA v3 or Cloudflare Turnstile. Both run invisibly in the background and score each visitor based on their behavior. reCAPTCHA is the most widely used and has the largest dataset for detecting bots. Turnstile is a newer alternative from Cloudflare that prioritizes privacy and does not use cookies. FormsList supports both services — you can enable them in your form settings and add the corresponding script tag to your page. The combination of honeypot fields, reCAPTCHA or Turnstile, and FormsList's built-in AI spam scoring provides enterprise-grade spam protection for any website.
<!-- Add a honeypot field to your form -->
<form action="https://formslist.com/f/YOUR_FORM_HASH" method="POST">
<!-- Honeypot: hidden from real users, filled by bots -->
<div style="position:absolute;left:-9999px;" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="Your email" required />
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send</button>
</form>In the FormsList dashboard, enable email notifications so every submission is delivered to your inbox. You can also connect Slack, webhooks, or Google Sheets. Submit a test entry to make sure everything works. Your form is now fully functional — no backend code, no hosting costs. When configuring email notifications, you can customize the email subject line to include field values from the submission. For example, a subject like "New contact from {{name}}" makes it easy to scan your inbox and prioritize responses. You can add multiple email recipients if your team shares responsibility for responding, and set up CC or BCC addresses for record-keeping. Beyond email, FormsList webhooks let you integrate with virtually any external service. A webhook sends a JSON payload to a URL you specify every time a new submission arrives. This enables powerful automations: you can create Trello cards, add rows to Airtable, trigger Zapier workflows, post to Discord, or update a CRM. The webhook includes all form field data plus metadata like the submission timestamp and the page URL where the form was submitted. Test the full pipeline from form submission through to the final notification to make sure nothing is lost along the way.
Add a fully functional contact form to any HTML website in under 5 minutes. No JavaScript frameworks, no server setup — just plain HTML that works everywhere.
Learn moreSet up email notifications for every form submission on your website. Receive form data directly in your inbox without running a mail server.
Learn moreAdd a fully functional contact form to any static site generator — Jekyll, Hugo, Eleventy, Astro, or plain HTML. No server-side code required.
Learn moreSet up your form backend in under a minute. No server required, no complex configuration — just a simple endpoint for your forms.