Catching (probably all) email SPAM with a really tasty honeypot
We recently launched one of our projects for a customer of ours where onboarding of new users is to be done manually. So there was a requirement that there should be a very simple sign-up form on the landing page where interested users can enter the email and they will then be contacted by the customers support team.
By the way, you should really check the project out. It’s a really novel way for taking notes, organising your knowledge, your ideas and most importantly visualising the often really complex relations between all those arguments. While at the same time keeping it simple with the option to dive deeper if necessary. Anyway, it’s called Logo Dynamic Cards and you can take a closer look at ld-cards.com.
So after implementing the simplest approach: A simple <form>
with an ยด` tag
we of course ran into problems with bots signing up on mass. Some of those bots even used
actually legit email addresses that had been overtaken and then clicked on the obligatory
confirmation email we send after signup. We didn’t expect that happening :thinking_face:.
So after some searching we stumbled upon the suggestion to use a honeypot to filter out SPAM, and the version we came up with is working perfectly so far, catching 100% of all SPAM 🙏.
The version we came up with looks like this: On the landing page there is a single <form>
with
all the inputs that looks like this:
<form>
<div class="form-group normal">
<label>Email</label>
<input class="form-control" name="email" type="text">
<label>Name</label>
<input class="form-control" name="name" type="text">
</div>
<button type="submit">Submit</button>
</form>
Now when the form is submitted the server will turn around the values for name
and email
, treating the
email
as the honeypot value, and using the name
value as the email, i.e. something like this in Elixir code:
def form_submit(conn, params) do
email = params["name"]
honeypot = params["email"]
email = if email == nil or email == "", do: "stepped_into@honey.pot", else: email
MyApp.Leads.new_lead(email, honeypot)
# send same answer for SPAM and actual users to not give any hints to the bots
render(conn, "we_will_contact_you_soon.html")
end
And we must of course make sure that actual users only fill out the name
field and leaves the
email
field blank, something that we can achieve easily using some old fashioned css. However
note that we did not simply put a class on the elements we want to hide but instead use some
pseudo class selectors so that a bot will need to actually understand the css at a deeper level
then simply checking if some class is applied directly to an element. The css currently looks like
this, but we could obviously make it even more complicated:
.normal.form-group label:nth-child(3),
.normal.form-group input:nth-child(2) {
display: none;
}
With this setup every bot so far has filled out the email
field, and while some are smart enough to not fill
out the name
field we still know that these are bots because the email
field is the honeypot 😄.
Obviously if you actually need the name
of the user (we don’t for this app), you’ll have to modify this a bit.
If we need to collect the name in the future we plan to simply collect that name only after the user has
confirmed the email, so that we can still keep this setup for our honeypot.