May 31, 2024
4 mins read
Contact forms are a standard feature on most websites that are often abused by spammers. There are several known methods of reducing the number of unwanted messages received from a contact form including CAPTCHAs, third-party spam filtering services or backend solutions relying on black-lists and spam detectors. CAPTCHA is usually the worst choice as it totally destroys the user experience. The other options could be viable if you run a website with a large audience. For small resources like this blog they are an overkill.
In my experience the majority of spam messages is not the actual spam, but random content generated by spambots crawling the internet and parsing contact form endpoints for future spamming campaigns. There are a few simple methods to stop these bots.
Crawling the internet is a resource-intensive and expensive activity, so the bots take many short-cuts to optimize the process. Instead of running a full web-browser engine, they simply parse the HTML code of your contact page searching for the <form>
elements. Here are a few simple techniques you can use to stop the bots.
The first obvious technique is to hide the form by adding it dynamically in Javascript after the page has been loaded. It often suffices to leave out the action
field on the form.
This works together with the first trick. Wait for browser events like onfocus
and onclick
to be generated by each input field in the contact form. Populate the form action
field and enable the submit
button only after all required inputs have been filled.
You can start a timer when the page loads and wait for a few seconds before modifying the form.
Here is an example taken from the source code of this blog. The form created using Semantic UI looks like this:
<form class="ui form" id="contact_form">
<div class="field">
<label>Name</label>
<input type="text" id="name_input" name="sender" placeholder="Your Name" />
</div>
<div class="field">
<label>Email</label>
<input
type="email"
id="email_input"
name="email"
placeholder="Your Email"
/>
</div>
<div class="field">
<label>Text</label>
<textarea
id="message_input"
form="contact_form"
name="message"
minlength="10"
maxlength="1024"
spellcheck="true"
required
></textarea>
</div>
<button class="ui secondary button disabled" id="submit_button" type="submit">
Submit
</button>
</form>
The Javacript code for updating the form:
var count = { name: 0, email: 0, message: 0 };
var submit_updated = false;
const send_url = "<your form processing endpoint>";
function update_count(param) {
switch (param) {
case "name":
count.name += 1;
break;
case "email":
count.email += 1;
break;
case "message":
count.message += 1;
break;
default:
console.error("wrong key");
}
if (!submit_updated) {
if (count.name > 0 && count.email > 0 && count.message > 0) {
let button = document.getElementById("submit_button");
let form = document.getElementById("contact_form");
if (button && form) {
button.className = "ui secondary button";
form.action = send_url;
form.method = "post";
submit_updated = true;
}
}
}
}
window.onload = function () {
let name_element = document.getElementById("name_input");
name_element.onclick = () => {
update_count("name");
};
name_element.onfocus = () => {
update_count("name");
};
let email_element = document.getElementById("email_input");
email_element.onclick = () => {
update_count("email");
};
email_element.onfocus = () => {
update_count("email");
};
let message_element = document.getElementById("message_input");
message_element.onclick = () => {
update_count("message");
};
message_element.onfocus = () => {
update_count("message");
};
};
I have been using this approach for several months on three websites and I have not received a single spam message.
Some suggest punishing the spammers by providing a randomly generated endpoint in the action
field belonging to a domain owned by your most-hated internet giant. The idea is that waiting for a response from a non-existing endpoint will slow down the bot and might eventually result in black-listing the offender. I recommend against it. The bot does not need to wait for the response as the request are most likely asynchronous, and the spammers are smart enough to hide their true IP. Also, the method can backfire if the requests appear to come from your host.