Free plan250 submissions a month, spam filtering included, no card.See pricing →
Guide · No backend

Build a contact form without a backend

A “backend” for a contact form means three unglamorous jobs: receive the POST, keep spam out, and deliver the message reliably. You can own all three yourself, or hand them to an endpoint and keep only the part you care about: the form itself.

3 min readAny host or frameworkLast reviewed

On this page

Your options, compared

Four ways to receive a contact form. They differ in how much of the backend you end up owning.

ApproachServer codeSpam protectionDeliveryUpkeep
Your own API route and email SDKYou write itYou build itYour email providerKeys, limits, patches
A serverless functionOne functionYou build itYour email providerFunction and key
Your host’s form featureNoneThe host’s own filterThe host’s dashboardTied to one host
Form endpointNoneBuilt inEmail, inbox and integrationsNone

The minimal integration

A fetch-based submit handler for when you want an inline success message instead of a full-page redirect. Plain HTML (no JS) works too: just set the form action to your endpoint.

JavaScript
const form = document.querySelector('#contact-form');

form.addEventListener('submit', async (event) => {
  event.preventDefault();

  const button = form.querySelector('button[type="submit"]');
  button.disabled = true;

  try {
    const response = await fetch('https://mtform.co/f/your-form-key', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(Object.fromEntries(new FormData(form))),
    });

    const result = await response.json();

    if (!response.ok) {
      throw new Error(result.error?.message ?? 'Unable to send your message.');
    }

    form.reset();
    // Replace with your own success UI.
    console.log('Sent', result.submission_id);
  } catch (error) {
    // Values are preserved because we never reset on failure.
    console.error(error.message);
  } finally {
    button.disabled = false;
  }
});

Swap in your own endpoint. On failure the handler shows the returned error.message and keeps what the visitor typed.

Step by step

  1. 1

    Pick your front end

    Keep whatever you already have: plain HTML, React, Vue, Astro, or an AI-generated page. The endpoint doesn’t care what rendered the form.

  2. 2

    Create an endpoint

    Create a form in MakeTheForm to get a public endpoint URL and an inbox. Nothing to deploy or host.

  3. 3

    Wire it up

    Either set the form’s action to the endpoint (no JS) or POST to it with fetch for an inline success message. Add the hidden honeypot field for free spam protection.

  4. 4

    Verify and go live

    Verify your recipient once and submit a test. It lands in your inbox and by email. With delivery you can actually see.

Why an email API key in the browser doesn’t work

Calling an email provider straight from the page looks like a backend-free shortcut. It puts your secret key in JavaScript that every visitor downloads, so anyone can copy it and send mail as you.

Email API called from the page · don’t ship this
// Anyone who opens devtools now has your key.
await fetch("https://api.email-provider.example/send", {
  method: "POST",
  headers: { Authorization: "Bearer sk_live_…" },
  body: JSON.stringify({ to: "you@example.com", text: message }),
});

Common pitfalls

Reaching for a serverless function too early

A one-off Lambda or edge function to catch a form is a deploy target, a cold start, and a spam problem you now own. For a contact form, an endpoint is less to build and less to keep alive.

Forgetting spam protection

A raw POST target gets found by bots within days. Use the honeypot field and, if needed, turn on Turnstile: both belong on every plan, not just paid ones.

Troubleshooting

SymptomWhat’s happeningFix
CORS errorYour fetch sent a non-simple request or a disallowed header.POST JSON with Content-Type: application/json; the endpoint handles the CORS preflight for browser origins.
429You hit the burst rate limit while testing repeatedly.Wait for the window in Retry-After. Rate-limited requests never count against your submission quota.

Frequently asked questions

Can you make a contact form without a backend?

Yes. Point the form at a hosted form endpoint that receives, filters, stores, and emails the submission. You write only front-end code; the endpoint is the backend, managed for you.

What’s the difference between a form endpoint and building my own backend?

A form endpoint gives you receipt, spam filtering, storage, email delivery, and retry visibility out of the box. Building your own means writing and hosting a server, wiring a mail provider, and handling spam and deliverability yourself.

Is a form endpoint the same as a serverless contact form?

It does the same job without the function. A serverless contact form is code you write and deploy to a function platform, plus an email provider and your own spam handling. A form endpoint is that function already built and run for you, so there is nothing to deploy.

Is a form endpoint secure to use in client-side code?

Yes. The public form key is a non-secret address meant for the browser. Your destination email is never exposed in page source, and abuse is handled by rate limits, origin rules, and spam filtering.

Keep going

Ship this form for real.

Create an endpoint, paste it in and watch the first submission land in your inbox.

Create free endpoint