I finally did something about the contact form on this site, and honestly Im annoyed it took me this long. Some background first because I know at least three of you actually read the archives: techpads contact form has been running the exact same bit of PHP since around 2013. I know this because the file still has a comment at the top that says "TODO: sanitize this properly" and a timestamp from when I was clearly using a much older version of PHP than anything currently supported. It worked, technically. It just also worked for every spam bot on the internet.
Last week I actually opened the mail folder that catches form submissions instead of just glancing at the subject lines and deleting on sight, and there were 4,812 messages sitting in there since January. Four thousand eight hundred and twelve. Maybe nine of them were real. One was someone asking if I still had a Raspberry Pi 3 review from 2016 (I do, its still up, its embarrassing). The rest were crypto nonsense, fake SEO audits, and one very persistent bot that kept submitting the same message about discount printer ink about four times a day, every day, for months. I have no idea why printer ink specifically. Its oddly specific enough that Im almost curious who it is.
So I spent a Sunday afternoon fixing it, and since this is the kind of thing techpad has always done, heres what actually worked.
Honeypot fields still work, mostly
The classic trick is a form field thats invisible to humans (hidden with CSS, not type="hidden", because bots check for that) but visible to any script filling out every field it can find. If that field has anything in it on submit, you silently drop the message and pretend it succeeded. I added one called website_url because bots love a field that sounds official. This alone cut the volume by maybe 60%. Not bad for ten minutes of work I should have done in 2014.
Rate limiting by IP, not just by form
The printer ink bot was hitting the endpoint directly, skipping the form entirely, so client-side tricks werent going to touch it. I added a dead simple rate limit: three submissions per IP per hour, tracked in a tiny SQLite table I already had lying around for something unrelated. Anything past that gets a 429 and nothing else. This is the part that actually killed the printer ink guy, and I felt a small, petty satisfaction watching those logs go quiet.
Timing checks
If a form gets submitted less than two seconds after the page loads, its not a person. Nobody reads a contact form and fills in three fields in under two seconds, not even me, and Im famously bad at slowing down for anything. I added a hidden timestamp field set by JavaScript on page load and check the gap server-side.
None of this is clever. Its all stuff that was already documented a decade ago on a hundred other blogs, which is sort of the point I keep coming back to lately: half the "solved problems" in web dev never actually got fixed on most of the actual websites running out there, mine included. We just collectively agreed to complain about spam instead of spending the Sunday afternoon.
The other half of the fix was just admitting I didnt want to keep hosting my own mail sending logic at all. Rolling your own SMTP handling for a single contact form is exactly the kind of thing that feels like it should be five lines of code and is actually forty, plus a spf record you forgot about, plus wondering why Gmail is quietly dropping your messages into spam. I ended up moving the actual send-the-email part over to Tricknowtech's email API, which was less setup than I expected, and now I dont think about deliverability at all, which is honestly the whole goal of a contact form in the first place.
If your own contact form hasnt been touched since some earlier, more optimistic era of the internet, go look at whats actually landing in that inbox. Youll probably be surprised, and not in a good way. Mine had a bot in there congratulating me on winning a prize I never entered, sent every single day at 3:47am like clockwork. I sort of miss it now that its gone. Sort of.