I Tried Every Spam Protection Method. Then I Built Something That Actually Works.
From reCAPTCHA to honeypots to IP blocking, I've fought spam with every weapon available. Most of them failed. Here's what I built instead.
My contact form went live on a Tuesday morning. By Tuesday afternoon, I had 23 spam submissions about cryptocurrency investments and “business opportunities.” Welcome to the internet.
reCAPTCHA: Necessary But Not Enough
Everyone’s first instinct is Google reCAPTCHA. I added it that same Tuesday:
<form>
<div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Submit</button>
</form>
The spam stopped! Success! Except now my conversion rate dropped by 30%. Turns out people really hate solving puzzles just to contact you. “Click all the traffic lights” might work for Google, but it’s a terrible user experience for a simple contact form.
Then I started getting complaints. A user emailed me directly (ironically) to say she couldn’t submit the form because reCAPTCHA wasn’t working with her screen reader. Another user on mobile gave up after failing the CAPTCHA three times. Oh, and I had to manage API keys, handle server-side verification, deal with Google’s tracking concerns. It was a hassle.
But here’s the thing: reCAPTCHA is still necessary. It’s one of the best spam protection tools available. The problem is that after two weeks, spam started trickling in again. Modern bots are getting really good at solving reCAPTCHA. You need reCAPTCHA AND other layers of protection working together.
Honeypot Fields: Good Until They’re Not
Next attempt: honeypot fields. The concept is clever. Add an invisible field to your form. Humans won’t see it or fill it out. Bots will:
<input type="text" name="website" style="display: none;">
If that field gets filled out, reject the submission. Simple! This actually worked for a few weeks. Spam dropped to almost zero. No user friction. I thought I’d won.
Then the spam came back. Smarter bots started detecting honeypot patterns. They look for CSS display properties, check for fields with suspicious names like “website” or “url”, and skip them. So I got more creative with hiding. Used absolute positioning off-screen. Gave fields normal-sounding names. Made the honeypot look like a regular field in the HTML. It became a cat-and-mouse game. I’d find a new hiding technique. It would work for a couple weeks. Spammers would adapt. Rinse and repeat.
Rate Limiting: The Redis Requirement
Fine, I thought. I’ll rate limit by IP address. Can’t spam if you can only submit 5 times per hour:
const submissionCount = await redis.incr(`form:${ipAddress}`);
if (submissionCount > 5) {
return { error: 'Too many submissions' };
}
This required adding Redis to my infrastructure. Now I had another service to run, monitor, and keep alive. And it didn’t even work that well. Bots started using proxy rotation. Every request came from a different IP. Rate limiting became useless.
Worse, legitimate users behind corporate VPNs or shared networks started getting blocked. A whole office building might share one IP address. If one person submitted the form, nobody else could. I got an angry email from someone who tried to contact me three times over two days and kept getting blocked. She wasn’t a bot. She wasn’t spamming. She was just persistent, and all three attempts came from her company’s VPN.
IP Blocking: The Maintenance Nightmare
Okay, how about blocking known spam IPs? There are databases of bad actors. Just check incoming requests against the list:
const spamIPs = await loadSpamDatabase();
if (spamIPs.includes(userIP)) {
return { error: 'Blocked' };
}
Sounds reasonable. Except those databases are huge. Checking against millions of IPs on every request is slow. And they change constantly. IPs that were spam sources yesterday are legitimate today. Cloud providers rotate IPs. What was an AWS server running a bot is now someone’s prod application.
I tried maintaining my own blocklist. That lasted about a week before it became a full-time job. Check submissions, identify spam patterns, add IPs to the list, remove false positives, repeat forever. Plus, blocking legitimate users feels terrible. Someone emails you: “Why can’t I submit your contact form?” And you have to explain that their IP is on a spam list somewhere. Great first impression.
What Actually Stops Spam
After months of fighting spam with every tool I could find, I realized something: single solutions don’t work. Spam is a multi-faceted problem. Bots are sophisticated. They rotate IPs, solve CAPTCHAs, detect honeypots, and bypass rate limits. Fighting them requires multiple layers working together.
But building multiple layers yourself is insane. I was spending 20% of my development time on spam protection. That’s not an exaggeration. Twenty percent of my work hours went to fighting bots instead of building features. This is exactly why I built StaticForm.
StaticForm doesn’t rely on one technique. It uses reCAPTCHA (or Turnstile, or hCaptcha if you prefer) as the foundation, but combines it with honeypots, IP analysis, content filtering, email validation, behavioral analysis, and pattern recognition. Multiple systems checking each submission from different angles. The key difference: you still add reCAPTCHA to your form, but StaticForm handles all the server-side verification for you. No managing API keys on your backend. No combining multiple spam protection systems yourself. StaticForm does all of that automatically.
More importantly, those systems are constantly updated. When spammers find a new technique, I adapt the filters. You don’t have to do anything. The protection just gets better over time. I’ve been running StaticForm for months now across multiple sites. Spam rate went from about 70% to under 5%. And that 5% is mostly really sophisticated attempts that at least look like real messages.
The Real Cost
Here’s what I was doing before StaticForm: checking submissions daily to filter spam manually (15 minutes per day), updating spam protection code monthly when bots adapted (2-3 hours per month), investigating why legitimate users got blocked (variable, but frustrating), maintaining Redis, updating IP lists, testing new protection methods. That’s roughly 10 hours per month on spam protection. For a simple contact form.
Now with StaticForm, I check the dashboard once a week. Takes two minutes. Occasionally I’ll mark something as spam or not spam to help the filters learn. That’s it. The time I save is valuable. But honestly? The mental space is more valuable. I don’t think about spam anymore. It’s not my problem. It just works.
Get 10 free credits to test your form at app.staticform.app. Pay as you go, or buy a plan to save money.