How to Send a Website Form to Email (5 Methods With Code)

A web page cannot send email by itself. The form has to post its data to something that can: a form endpoint service, your host's form feature, a serverless function that calls an email API, a Google Apps Script or your own server. Pick by how much control you need, then submit with JavaScript so visitors stay on the page.

The five methods compared
MethodBackend codeCost to startBest for
mailto: link or actionNoneFreeQuick personal sites; unreliable for business
Form endpoint serviceNoneFree tier, then paidStatic sites that need it working today
Netlify FormsNone, on NetlifyFree tier, then paidSites already hosted on Netlify
Serverless function + email APIA small functionFree tiers usually cover small sitesFull control over format and routing
Google Apps ScriptA short scriptFree with a Google accountLow-volume forms that should land in Gmail and a Sheet

Updated

Each method below has complete code. Start with the form and the JavaScript submit handler, which work with every method, then pick a backend.

The form and the JavaScript submit

This front end works with every method. The script sends the form with fetch, shows a status message and keeps the text if sending fails.

Accessible contact form with a honeypot

Labels wrap their inputs, the browser checks required fields and email format, and the status line is announced to screen readers.

<form id="contact" action="https://example.com/your-endpoint" method="POST">
  <label>Name <input name="name" autocomplete="name" required></label>
  <label>Email <input name="email" type="email" autocomplete="email" required></label>
  <label>Message <textarea name="message" rows="4" required></textarea></label>
  <!-- Honeypot: hidden from people, filled in by simple bots -->
  <label class="hp" aria-hidden="true">Leave empty <input name="company" tabindex="-1" autocomplete="off"></label>
  <button type="submit">Send</button>
  <p id="status" role="status"></p>
</form>
<style>
  #contact { display: grid; gap: 10px; max-width: 360px; font: 15px system-ui, sans-serif; }
  #contact label { display: grid; gap: 4px; }
  #contact input, #contact textarea { padding: 8px; border: 1px solid #ccc; border-radius: 8px; font: inherit; }
  #contact button { padding: 10px; border: 0; border-radius: 8px; background: #111827; color: #fff; }
  .hp { position: absolute; left: -9999px; }
</style>

Submit with fetch, stay on the page

Works with any endpoint that accepts a normal form post. If the endpoint wants JSON, send JSON.stringify(Object.fromEntries(data)) with a JSON content type instead.

const form = document.getElementById("contact");
const status = document.getElementById("status");

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const data = new FormData(form);
  if (data.get("company")) return; // honeypot filled: quietly ignore
  const button = form.querySelector("button");
  button.disabled = true;
  status.textContent = "Sending...";
  try {
    const res = await fetch(form.action, {
      method: "POST",
      body: data,
      headers: { Accept: "application/json" },
    });
    if (!res.ok) throw new Error(res.statusText);
    form.reset();
    status.textContent = "Thanks, your message is on its way. We reply within two working days.";
  } catch {
    status.textContent = "Sorry, that did not send. Please try again or email us directly.";
  } finally {
    button.disabled = false;
  }
});

Method 1: mailto

mailto form action

Opens the visitor's email app with the fields in the body. No server needed, but nothing is sent if they have no mail app.

<form action="mailto:[email protected]" method="post" enctype="text/plain">
  <input name="name" placeholder="Name">
  <textarea name="message" placeholder="Message"></textarea>
  <button>Open email to send</button>
</form>

Method 2: a form endpoint service

Services such as Formspree, Basin, Getform and Web3Forms give you a URL to post to and forward each submission to your inbox. Sign up, create a form, paste its URL into the action attribute of the form above and keep the fetch script. Check each service's current free limits and where it stores data.

Method 3: Netlify Forms

Netlify Forms markup

Netlify detects the form at deploy time when it has the data-netlify attribute. Turn on email notifications in the site's form settings. For JavaScript-rendered forms, also keep a plain HTML copy in your built files so Netlify can find it.

<form name="contact" method="POST" data-netlify="true" netlify-honeypot="company">
  <input type="hidden" name="form-name" value="contact">
  <p class="hp"><label>Leave empty <input name="company"></label></p>
  <label>Email <input type="email" name="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <button type="submit">Send</button>
</form>

<script>
  // To submit without leaving the page, post URL-encoded data to "/":
  // fetch("/", { method: "POST",
  //   headers: { "Content-Type": "application/x-www-form-urlencoded" },
  //   body: new URLSearchParams(new FormData(form)).toString() });
</script>

Method 4: a serverless function and an email API

The function receives the form, checks it, and calls an email API with a secret key kept on the server. This example uses Resend's REST API; SendGrid, Postmark and Amazon SES work the same way with their own endpoints.

Serverless function (Node, web standard Request and Response)

Works on platforms that use the web Request and Response objects. Set RESEND_API_KEY in your host's environment variables and verify your sending domain with the email provider first.

export default async function handler(req) {
  if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
  const form = await req.formData();
  const name = String(form.get("name") || "").slice(0, 100);
  const email = String(form.get("email") || "").slice(0, 200);
  const message = String(form.get("message") || "").slice(0, 5000);
  if (form.get("company")) return Response.json({ ok: true }); // honeypot
  if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email) || !message) {
    return Response.json({ ok: false, error: "Missing or invalid fields" }, { status: 400 });
  }

  const res = await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: "Website <[email protected]>",
      to: ["[email protected]"],
      reply_to: email,
      subject: `New message from ${name || email}`,
      text: `From: ${name} <${email}>\n\n${message}`,
    }),
  });
  return Response.json({ ok: res.ok }, { status: res.ok ? 200 : 502 });
}

Method 5: Google Apps Script

A short script, deployed as a web app from a Google account, emails each submission and can log it to a Sheet. Daily email quotas apply, so it suits low-volume forms.

Apps Script web app

In a new Apps Script project, paste this, then Deploy as a web app that runs as you and that anyone can access. Use the web app URL as the form action.

function doPost(e) {
  const p = e.parameter;
  if (p.company) return ContentService.createTextOutput("ok"); // honeypot
  MailApp.sendEmail({
    to: "[email protected]",
    replyTo: p.email,
    subject: "New website message from " + (p.name || p.email),
    body: "From: " + p.name + " <" + p.email + ">\n\n" + p.message,
  });
  // Optional: keep a copy in a Sheet
  // SpreadsheetApp.openById("SHEET_ID").getSheets()[0]
  //   .appendRow([new Date(), p.name, p.email, p.message]);
  return ContentService.createTextOutput(JSON.stringify({ ok: true }))
    .setMimeType(ContentService.MimeType.JSON);
}

Bonus: your own PHP server

Shared hosting often runs PHP. The built-in mail() function works on many hosts, but deliverability is better with an SMTP library such as PHPMailer and a real mailbox on your domain.

PHP handler with basic checks

Rejects non-POST requests, trims inputs, refuses line breaks in the reply address (which would let attackers add headers) and redirects to a thank-you page.

<?php
if ($_SERVER["REQUEST_METHOD"] !== "POST" || !empty($_POST["company"])) { http_response_code(400); exit; }
$name = trim(substr($_POST["name"] ?? "", 0, 100));
$email = trim($_POST["email"] ?? "");
$message = trim(substr($_POST["message"] ?? "", 0, 5000));
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || preg_match("/[\r\n]/", $email) || $message === "") {
  http_response_code(400); exit("Please fill in every field.");
}
$headers = "From: Website <[email protected]>\r\nReply-To: $email";
mail("[email protected]", "New message from $name", "From: $name <$email>\n\n$message", $headers);
header("Location: /thanks.html", true, 303);

Choosing a method

If the site is static and you want it working in ten minutes, use a form endpoint service or your host's built-in forms. If you already have serverless functions, an email API gives you full control over the message and costs little at small volumes. If the business runs on Google Workspace, Apps Script keeps everything inside that account at no extra cost.

Whatever you pick, the front end stays the same: a normal form plus the fetch handler below. Switching backends later means changing one URL.

Privacy

A contact form collects personal data. Say what you do with it next to the submit button, link your privacy policy, and keep only what you need. If you use a third-party service, it processes that data for you, so check where it stores it.

How it works

  1. Build the form with real labels, required fields and the right input types.
  2. Pick a backend from the five methods and set it up.
  3. Point the form's action at that backend, and add the JavaScript handler so submission happens without leaving the page.
  4. Add spam protection: a hidden honeypot field at minimum, and a challenge such as a CAPTCHA if spam gets through.
  5. Send a test from a phone and a desktop, then check the email arrives and is not in spam.

Tips

Frequently asked questions

Can I send email with just HTML?

Only with a mailto: action, which opens the visitor's own email app with the details filled in. It fails for anyone without a mail app set up, which includes many people on shared or work computers, and you cannot style or validate the result. Use one of the other methods for anything important.

Why are my form emails going to spam?

Usually because the message claims to come from the visitor's address, or because your domain lacks SPF, DKIM and DMARC records for the service that sends it. Send from an address on your own domain, set the visitor as reply-to, and add the DNS records your email provider gives you.

What is a honeypot field?

A field hidden from people but visible to simple bots. Humans leave it empty; bots fill it in. Your backend discards any submission where it has a value. It stops a lot of basic spam without bothering real visitors.

Do I need a CAPTCHA?

Not at first. Start with a honeypot and server-side checks such as rejecting messages full of links. Add a challenge only if spam keeps coming, and prefer one that does not ask people to solve puzzles.

Related: How to add a contact form, Business email setup, Contact link generator, Booking form generator, Privacy policy generator.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap