You can reduce contact form spam without asking every visitor to solve a CAPTCHA. Start with three controls: a honeypot field to catch some automated submissions, validation on the server, and rate limits to slow repeated requests. Each addresses a different problem. None can decide with certainty whether a well-written message came from a real customer or a spammer.
This guide shows how to add the honeypot to an HTML form, what mailcontact checks behind the endpoint, and how to test the result. If you are still choosing how to receive submissions, start with our guide to contact forms for static websites.
What a honeypot field actually does
A honeypot is a text input that ordinary visitors should leave empty. It sits outside the visible form and the normal keyboard tab order. A bot that indiscriminately fills inputs may fill it too; the receiving server uses that value as a reason to discard the submission.
The hidden field alone does nothing. Your form backend must recognize it and check it before storing a message or sending a notification. With mailcontact, the supported field name is company_website. A non-empty value after trimming whitespace causes the endpoint to return its usual success response without saving a message. That discarded submission does not consume your monthly message allowance.
Add a honeypot to your HTML contact form
For the recommended integration, load the hosted MailContact script: it injects the check during initialization and handles sending and automatic verification. The manual example below is for native forms and direct API integrations.
Paste this inside the form that already contains your visitor's email and message fields. Keep the class specific to your contact form so an unrelated style does not accidentally reveal the input.
<!-- Add inside your existing contact form. -->
<div class="contact-meta" aria-hidden="true">
<label for="contact-website">Website</label>
<input id="contact-website" type="text" name="company_website"
tabindex="-1" autocomplete="off" />
</div>
<style>
.contact-meta {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
</style>The CSS moves the input away from the visible page. tabindex="-1" removes it from normal keyboard tab navigation, and aria-hidden hides the wrapper from assistive technology. autocomplete="off" asks the browser not to fill it. Do not mark this input as required: a legitimate submission needs to leave it empty.
Treat those attributes as part of the implementation, then test them. Browser autofill, password managers and your site's CSS can interact with form fields in unexpected ways. If the trap fills during a normal visit, the visitor can see a success message even though the submission was discarded. That is a false positive to fix before publishing, not evidence that your spam protection is working well.
Include the honeypot when submitting with JavaScript
new FormData(form) includes the named input automatically. If you instead construct a JSON body by hand, include company_website with the other fields. Omitting it returns a FORM_CHECK_MISSING error. The hosted script adds the field automatically; custom SDK forms can call MailContact.prepare(form) once before interaction. Our complete fetch contact form uses FormData and includes this field.
A check only in browser JavaScript is easy to bypass by sending a request directly to the endpoint. Validation belongs on the server as well as in the interface; MDN's form validation guide explains why browser validation is useful feedback but cannot enforce security.
Combine the trap with validation and rate limits
A honeypot catches a pattern of automated behaviour. It does not limit how often someone can call your endpoint or how much data they can send. mailcontact applies additional checks to every form request:
- Required fields: a syntactically valid email address and a non-empty message. A valid email format does not prove the sender owns that address.
- Field limits: up to 200 characters for the name, 300 for the subject, 320 for the email and 5,000 for the message.
- Request size: bodies above 64 KB are rejected.
- Request frequency: 20 form requests per minute per IP, plus a limit of 60 accepted messages per minute per inbox.
Rate limits are separate from plan quotas. Free allows 50 accepted form submissions per monthly quota period, while Pro allows 1,000 incoming messages across the account. A rejected invalid request or a discarded honeypot submission does not use that monthly quota. A spam message that passes the checks and is accepted does count. See the spam protection reference for the current rules.
Test both the visitor path and the bot trap
- Submit normally: leave the trap empty and send a message using your real form. Confirm it arrives in the inbox.
- Use the keyboard: tab through the page and verify the hidden input never becomes a normal tab stop.
- Try autofill: let your browser fill the visible contact fields, then inspect the honeypot value. It should still be empty.
- Fill the trap deliberately: in a controlled test, send a non-empty
company_websitevalue. The endpoint should return success, with no new inbox message or notification. - Remove the check: sending must fail with
FORM_CHECK_MISSINGwhile preserving the draft. - Test a rejected request: an empty message should return a validation error, and your page should preserve what the visitor typed.
Test after changing form builders, CSS or submission scripts too. A redesign can remove the honeypot from the posted payload even while it still exists in the page. Checking only the visible layout will not catch that regression.
When spam still gets through
A bot can leave the honeypot empty, use valid-looking fields and send slowly enough to stay under rate limits. A human spammer can do the same. MailContact also combines scanner scores with local content checks and project-scoped campaign detection. Suspicious messages can require automatic proof of work; strong spam is held in Spam for 30 days without notifications or quota usage. The hosted script handles verification and preserves failed drafts. Proof of work increases automation cost without proving a visitor is human. Review Spam for false positives and restore legitimate messages.
Keep a few examples of unwanted messages and note which project receives them. Contact us with that context if the existing controls do not fit your traffic. And if the problem is bots scraping a public email address, that is a separate issue: the guide to keeping your personal email off your website covers the contact choices available.