Guide

Contact form for a static site, no backend needed

A static site has no server to receive a form POST, which is why so many static sites ship with a dead contact form or a bare mailto: link. The fix is one attribute: point the form's action at an endpoint that stores and emails the submission for you.

Step 1: Create your endpoint

Sign up, create a form, and copy your endpoint. Add and verify the email address you want submissions delivered to. That is the whole backend, and you never host it.

Step 2: A complete working page

Here is an entire contact page, ready to save as contact.html. Replace YOUR_FORM_ID and add your own styling:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact</title>
</head>
<body>
  <h1>Contact</h1>

  <form action="https://forms.scrolltheory.media/f/YOUR_FORM_ID" method="POST">
    <label>Name
      <input type="text" name="name" required>
    </label>

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

    <label>Message
      <textarea name="message" rows="5" required></textarea>
    </label>

    <!-- hidden spam honeypot: leave empty -->
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
           style="position:absolute;left:-9999px" aria-hidden="true">

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

Upload it wherever your site lives: GitHub Pages, Cloudflare Pages, an S3 bucket, shared hosting, anything that serves HTML. There is nothing to install and no dependency to update.

Step 3: Send a test

Open the page, fill it in, and submit. The visitor sees a clean thank-you page, the submission shows up in your dashboard, and a notification lands in your inbox with reply-to set to the sender. Add a hidden _next field if you want the visitor sent to your own thank-you page instead.

Optional: stay on the page

The form above works with zero JavaScript. If you would rather show a success message without leaving the page, add this below the form:

<script>
document.querySelector("form").addEventListener("submit", async (e) => {
  e.preventDefault();
  const form = e.currentTarget;
  const res = await fetch(form.action, {
    method: "POST",
    headers: { "Accept": "application/json" },
    body: new FormData(form)
  });
  const data = await res.json();
  form.outerHTML = data.ok
    ? "<p>Thanks, your message was sent.</p>"
    : "<p>Something went wrong. Please try again.</p>";
});
</script>

What about spam?

The hidden honeypot field quarantines most bots on its own. Flagged messages are held in a separate view instead of deleted, so a wrongly flagged real message is one click from recovery, and spam never counts against your monthly quota.

Get your endpoint

Create a form on the free plan and you get an endpoint to drop into any of the examples above. Every submission is stored and spam is quarantined rather than deleted, so a false positive never costs you a real lead.

Start free   Read the docs

More guides