Guide

How to send HTML form submissions to email without a backend

A static HTML form cannot send email by itself. The browser can collect values and POST them somewhere, but something must receive that request, validate it, and hand it to a mail service. This guide compares the practical options and shows the shortest reliable path.

The four common options

1. A mailto link

The form opens the visitor’s mail client with a prefilled message. It is easy to add and unreliable in production: many visitors have no configured client, the address is exposed to scrapers, and there is no record of whether anything was sent.

2. A serverless function plus an email API

You deploy a function, store an API key, validate the payload, call an email provider, and handle errors. This is a reasonable choice when you already run functions and want full control. The hidden cost is operational: secrets, rate limits, retries, and a failure path that nobody monitors.

3. A hosted form backend

A hosted backend gives you one endpoint built for form posts. It receives the request, stores the fields, sends notifications, and returns a response. You do not deploy code or manage a mail API key. This is usually the shortest path for a static site or a small front-end app.

4. A form builder

A form builder hosts both the form and the backend. It is convenient when you want its editor, but it usually means replacing your markup and accepting the builder’s design system. It is a different trade-off, not a drop-in fix.

Connect an existing form to a hosted endpoint

Start with semantic HTML. Every field that should arrive in the inbox needs a name attribute; the label should be tied to the input with for and id.

contact.html
<form action="https://freelyform.com/f/FORM_ID" method="POST">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required>

  <label for="email">Email</label>
  <input id="email" name="email" type="email"
         autocomplete="email" required>

  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>

  <button type="submit">Send message</button>
</form>

Create the form in the backend, verify the destination inbox, and replace FORM_ID. The form posts normally; the backend returns a success page, a redirect, or JSON depending on the form settings and the request headers.

Why this avoids CORS problems

A native form submission is not a fetch request. The browser navigates to the endpoint, so the same-origin policy does not apply the way it does to JavaScript. If you switch to fetch, add your site origin to the form allowlist and handle the JSON response yourself.

Add inline feedback without a redirect

A redirect to a thank-you page is the most robust fallback. If you want to stay on the page, load the embed script and post with fetch:

Inline status
<script src="https://freelyform.com/embed.js" defer></script>

<form action="https://freelyform.com/f/FORM_ID" method="POST" data-fs-status="#form-status">
  ...
</form>
<p id="form-status" role="status"></p>

Protect the inbox before launch

  • Honeypot field: a hidden input that humans do not fill. Bots that fill every field are marked as spam.
  • Rate limits: cap submissions per form and IP so one script cannot flood the inbox.
  • Captcha when needed: add Turnstile to high-traffic public forms; keep it off low-risk internal forms.
  • Server-side rules: require a message, block link-heavy content, or route by a select field.
  • Origin allowlist: restrict JavaScript submissions to the domains you control.

Test these five cases

  1. A normal submission with every required field filled.
  2. A missing required field, which should return a clear 422 response.
  3. A submission from a disallowed origin, which should be rejected when applicable.
  4. A file upload within the allowlist, then a disallowed file type.
  5. A retry with the same idempotency key, which should not create a duplicate.

What “delivered” should mean

A 200 response only proves the endpoint accepted the request. Delivery is a separate event. A useful backend shows the status of each channel, the provider error when one exists, and a retry control. Without that record, “the form works” is an assumption.

When a custom backend is still the right call

If the form must write to an internal database in real time, enforce complex authorization, or orchestrate multiple services with strict transactional guarantees, a custom backend is appropriate. A hosted form endpoint is a fit when the job is intake, notification, storage, and fan-out to other tools.

Questions from this guide

Can an HTML form send email by itself?

No. HTML can collect values and send them to an endpoint, but it cannot deliver email on its own. A server or a hosted form backend must receive the POST and hand it to a mail provider.

Is a mailto link a reliable form backend?

No. A mailto link opens the visitor’s local mail client, exposes the address to spam, fails when no client is configured, and gives you no delivery record.

Do I need JavaScript to submit a form to a backend?

No. A standard method and action work without JavaScript. JavaScript is only needed if you want to stay on the page and render an inline response.