How to Make a Contact Form That Sends Emails
An HTML contact form needs two parts: the form itself, built with <form>, <input>, <textarea> and a submit button, and a form handler that receives the data and emails it to you. HTML alone can’t send email. The easiest handler is a form service: you point the form’s action at its address.
Last updated: October 4, 2026
Quick answer
How a contact form works
When a visitor presses Send, the browser packages every field that has a name and sends it to the address in the form’s action attribute. Something at that address has to receive the data and send the email. That’s why a form copied into a plain HTML page “does nothing”: there’s no handler behind it.
You have four options for the handler:
- A form service (Formspree, Web3Forms, Getform and similar): no server code, a free tier for small sites. Best for static HTML sites.
- Your host’s built-in forms: Netlify, for example, detects forms with a
data-netlify="true"attribute. - A WordPress plugin (Contact Form 7, WPForms, Fluent Forms) if your site runs on WordPress.
- Your own server script in PHP, Node.js or Python, if you run your own server.
Step 1: Build the form fields
A contact form usually needs a name, an email address and a message. Keep it short: every extra field means fewer people finish it.
- Add
<form method="POST">. Always use POST for messages, so they aren’t put in the URL. - Give every field a
<label>, anidand aname. Thenamebecomes the field’s label in the email you receive. - Use
type="email"for the email field, andrequiredon fields that must be filled in. - Add
autocompleteattributes (name,email) so browsers can fill them in.
The example has the full form with styling and built-in validation. Press Send with empty fields to see the browser’s messages. (In this preview, submitting just shows a note; it isn’t connected to a handler.)
Step 2: Connect a form service (easiest)
Form services give you a unique address to put in your form’s action. They receive each submission, filter spam, and email it to you. The steps are similar for all of them; with Formspree, for example:
- Create a free account and a new form, entering the email address where messages should go.
- Copy the endpoint it gives you, which looks like
https://formspree.io/f/abcdwxyz. - Paste it into your form:
<form action="https://formspree.io/f/abcdwxyz" method="POST">. - Publish the page and send yourself a test message. The first submission usually asks you to confirm your email address.
Web3Forms works slightly differently: the action is always https://api.web3forms.com/submit, and you add a hidden field with your key: <input type="hidden" name="access_key" value="YOUR_KEY">. Whichever you choose, check the free plan’s monthly limit and its privacy policy, since visitors’ messages pass through the service.
Other options: WordPress plugins and server scripts
- WordPress: don’t paste a raw HTML form into a page. Install a form plugin such as Contact Form 7 or WPForms, build the form there, and place it with its shortcode or block. The plugin handles sending, spam filtering and storage. For reliable delivery, also set up an SMTP plugin so emails are sent through a real mail account.
- Your own server: point
actionat a script such as/contact.php. The script must check every field again (never trust the browser), protect against spam, and send the email, ideally through an SMTP service rather than PHP’s basicmail()function, which often lands in spam folders. - Avoid
action="mailto:…". It doesn’t send anything itself; it tries to open the visitor’s email app, fails for anyone without one set up, and exposes your address to spam bots.
Step 3: Protect the form from spam
Any public form attracts spam bots within days. Layer a few defences:
- A honeypot field: an extra input that’s hidden from people but visible to bots in the code. Real visitors leave it empty; bots fill it in, and the handler discards those messages. Formspree, for example, uses a field named
_gotcha. - The service’s own spam filter, which most form services and plugins turn on by default.
- A CAPTCHA (such as Cloudflare Turnstile, hCaptcha or reCAPTCHA) if spam still gets through. Prefer invisible or low-friction ones; puzzles annoy real visitors.
Step 4: Show a thank-you message
By default, submitting a form leaves your site for the service’s own “thanks” page. Two better options:
- Redirect to your own page: most services let you set a “redirect URL” (in their dashboard or with a hidden field) to a page like
/thank-you/. - Stay on the page: send the form with JavaScript’s
fetch(), then replace the form with a message. Most services accept this when you send the headerAccept: application/json.
The example below shows the stay-on-the-page pattern. In this preview it fakes the network request (a placeholder address can’t receive anything), but the structure is what you’d use on a real site.
Which form handler should I use?
| Handler | Setup | Cost | Best for |
|---|---|---|---|
| Form service | Minutes; change the action | Free tier, then monthly plans | Static HTML sites |
| Host’s built-in forms | An attribute on the form | Usually free up to a limit | Sites already on that host |
| WordPress plugin | Install and build the form in the plugin | Free plugins available | WordPress sites |
| Own server script | Write and secure the code yourself | Your hosting | Developers with a server |
mailto: action | None | Free | Not recommended |
Common contact form mistakes
- No handler. A form without a working
actionsilently goes nowhere. Always send yourself a test message after publishing. - Missing
nameattributes. Fields without a name aren’t sent, so the email arrives empty. - Using GET. Messages end up in the URL and browser history. Use
method="POST". - No spam protection. Add a honeypot or turn on the service’s filter from day one.
- No confirmation. Visitors who see nothing happen will submit again, or give up. Show a clear thank-you message.
- Emails landing in spam. Check your spam folder when testing, and use a proper sending setup (the service’s, or SMTP for your own script or WordPress).
- Forgetting privacy. Mention in your privacy policy that the form collects names and emails, and who processes them.