Dev Stories

Why We Built Our Own Anti-Spam Plugin (When So Many Exist)

· 16 min read

It started with a phone call on a Monday morning. The office had barely opened.

“We got 600 spam messages over the weekend. Through the contact form. Again.”

The client was losing trust in the form entirely. Real leads were being buried under a pile of garbage submissions about crypto, SEO services, and whatever else the bots were selling that week.

This is the dev story behind CF7 Ultimate Honeypot — why it exists, what we tried before we wrote it, and the technical decisions that shaped it. If you have ever stared at a spam-flooded inbox and wondered why none of the “solutions” actually solve the problem, this post is for you.


The Problem We Faced

We build and maintain WordPress sites. Dozens of them. Almost every one runs Contact Form 7 because it is lightweight, flexible, and does exactly what a contact form should do without trying to be an entire marketing platform.

But CF7 ships with no built-in spam protection. That is a deliberate design choice — Takayuki Miyoshi keeps the plugin lean and lets the ecosystem handle everything else. We respect that. But it means every CF7 installation is an open door until you bolt something onto it.

The spam was not the old kind

We are not talking about the “Buy Viagra” messages from 2012. The bots hitting our clients’ forms in 2024 and 2025 were running headless browsers. They executed JavaScript. They rendered CSS. They filled out forms like a human would, with realistic timing between keystrokes. Some of them submitted messages that read like genuine business inquiries — because they were generated by large language models.

Content filters could not catch them. The messages were grammatically correct and contextually relevant. They mentioned the client’s industry. They asked about pricing. The only tell was volume: the same “person” was apparently interested in 40 different businesses on the same Tuesday afternoon.

The damage was not just annoyance

Spam in a contact form is not like spam in your personal email. When a business owner opens their inbox and sees 200 junk messages mixed in with 5 real leads, they do not carefully sort through them. They start ignoring the inbox. We watched clients miss legitimate inquiries because the signal-to-noise ratio had collapsed.

One client told us they had stopped checking their contact form submissions entirely. They were relying on phone calls and walk-ins. Their website’s contact page — the thing we built and they paid for — was functionally dead.

That was the moment this stopped being a minor annoyance and became a problem we needed to fix properly.


What We Tried

We did not jump straight to writing code. We are not the kind of developers who build from scratch when a good solution already exists. We spent months testing everything the WordPress ecosystem had to offer. Here is an honest account of what we found.

The Japan Quiz

The client who made that Monday morning phone call was a Japanese company. Almost all of their legitimate inquiries came from within Japan. Meanwhile, 99% of the spam was from overseas senders — foreign-language messages, foreign IP addresses, foreign everything.

So we tried something simple: we added a quiz question to the form that only someone living in Japan could answer. Something like “What color is the roof of a Japanese post box?” or a question about a common convenience store chain. No CAPTCHA, no image grid — just a text field with a question that required local cultural knowledge.

The results were dramatic. Spam dropped to nearly zero overnight. For that specific client, the problem was solved.

But we could not celebrate for long. The approach had two serious flaws.

First, it ruined the form’s design. A random trivia question stuck in the middle of a professional contact form looked out of place. The client’s marketing team pushed back. They had spent months refining the page’s UX, and now there was a quiz question about post boxes breaking the flow. Anti-spam measures should not make a form look worse.

Second, we knew this had an expiration date. The quiz worked because bots in 2024 were not smart enough to google the answer. But with LLMs getting better by the month, how long before a bot could answer “What is the name of the bullet train in Japan?” as easily as a human? We gave it a year, maybe two, before AI-powered bots would blow right through cultural knowledge questions. It was a clever hack, not a durable solution.

We needed something that was invisible to users, did not compromise design, and would not become obsolete the moment bots got smarter. That realization pushed us to look at the broader plugin ecosystem.

Akismet

Akismet is the default answer to spam on WordPress. It is made by Automattic, it comes pre-installed, and it has a massive spam database. We used it for years.

The problems started when the bots got smarter. Akismet works by analyzing content — it looks at the text of the submission and checks it against known spam patterns. That works brilliantly against “Buy cheap watches” messages. It does not work when an LLM generates a unique, contextually appropriate message for every submission.

We also ran into GDPR issues. Akismet sends form submission data to external servers in the United States for analysis. For our EU-based clients, that created a compliance headache. We had to add consent notices, update privacy policies, and explain to non-technical business owners why their visitors’ messages were being routed through a third-party API. Some clients just said no.

And then there is the cost. Akismet is free for personal sites, but commercial use requires a paid plan. Fair enough. But paying a monthly fee for a spam filter that was letting half the spam through anyway felt wrong.

Google reCAPTCHA

We tried reCAPTCHA v2 first. The “I’m not a robot” checkbox. It worked, technically. Spam dropped. But so did conversions. We measured a 22% drop in form completions on one client’s site within the first month. Mobile was worse — users on phones were rage-quitting the image grid challenges.

We switched to reCAPTCHA v3, the invisible version. Better UX, since there is no visible challenge. But reCAPTCHA v3 introduced its own set of headaches:

  • Performance hit. Google’s JavaScript payload added 200-400ms to page load times. For sites where we had spent weeks optimizing Core Web Vitals, this was a step backward.
  • False positives. Users on VPNs, Tor, or privacy-focused browsers consistently scored low and got blocked. We were punishing exactly the kind of privacy-conscious users our clients wanted to reach.
  • The GDPR problem again. reCAPTCHA sends behavioral telemetry to Google. Several European Data Protection Authorities have flagged this as problematic. We were back to the same compliance conversation.
  • It still let spam through. Bot operators learned to “warm up” browser sessions to build a high trust score before hitting the form. The arms race was not one we could win on Google’s behalf.

Basic Honeypot Plugins

We found a handful of honeypot plugins for CF7 on the WordPress repository. The concept is sound: add a hidden field that humans cannot see but bots fill in. If the field has data on submission, reject it silently.

We installed several. They worked — for about two weeks.

The problem was that these plugins used static, predictable implementations. The hidden field had the same name on every page load. The CSS hiding method was always display: none or visibility: hidden. A bot that encountered the form once could learn the pattern and skip the honeypot field forever.

And that is exactly what happened. Gen-2 bots running Puppeteer check computed styles before filling fields. A display: none input is trivially detectable. The basic honeypot went from “catching 90% of spam” to “catching 30%” within a couple of months as bot scripts updated.

Combining Multiple Plugins

At one point we were running Akismet, reCAPTCHA v3, a honeypot plugin, and a rate-limiting plugin simultaneously on a single site. Four plugins. Four potential points of failure. Four things to keep updated and configured.

The spam dropped significantly. But the maintenance burden was absurd. Plugins conflicted with each other. reCAPTCHA interfered with page caching. Akismet’s API occasionally timed out and held up form submissions. The rate limiter blocked a legitimate user who submitted a form twice because they were not sure the first one went through.

We were solving a spam problem by creating a reliability problem. That is not engineering. That is duct tape.


What We Built

We sat down and asked ourselves a simple question: what would the ideal anti-spam solution actually look like?

We wrote out a list of requirements. Not features. Requirements.

  1. No visible UI. The user should never know protection exists. No checkboxes, no puzzles, no badges.
  2. No external dependencies. No API calls to Google, Automattic, or anyone else. Everything runs on the server the site is already hosted on.
  3. GDPR-compliant by default. No cookies. No third-party data transfers. No consent banners needed for the spam filter.
  4. Lightweight. No 300KB JavaScript payloads. No measurable impact on page load time or Core Web Vitals.
  5. Resistant to bot evolution. Not a static defense that breaks when bots update their scripts. Something that changes shape on its own.
  6. Works with page caching. WordPress nonces break on cached pages. Our solution cannot depend on them.
  7. Zero configuration. Install, activate, done. No API keys, no threshold tuning, no per-form setup.

That list became the design spec for CF7 Ultimate Honeypot.

The Core Idea: Polymorphic Defense

The biggest failure of existing honeypot plugins was predictability. Same field name, same hiding method, same position in the DOM. A bot that figures out the pattern once can skip it forever.

We needed the form to look different every time it loads. Not different to the user — the user sees nothing. Different to the bot parsing the HTML.

Polymorphic honeypots rotate field names on every page load. The hidden field is not called honeypot or hp_field. It is called something generated dynamically, something that looks like a legitimate form field to a bot scanning the DOM. The CSS hiding technique rotates too — sometimes it is off-screen positioning, sometimes it is a zero-height container with overflow hidden, sometimes it is a combination of techniques. A bot cannot build a static rule to skip our honeypot because the honeypot is never the same twice.

Layered Validation

A single detection method is never enough. Bots that slip past the honeypot need to hit another wall. We built multiple layers that work independently:

Layer 1 — Polymorphic honeypot. Catches the majority of automated submissions. Field names, hiding methods, and DOM positions change on every load.

Layer 2 — Behavioral analysis. Lightweight JavaScript measures basic interaction signals: time from page load to submission, mouse movement presence, and keystroke patterns. A form submitted in under two seconds with zero interaction events is not human. This layer catches headless browser bots that are sophisticated enough to skip honeypot fields.

Layer 3 — Server-side token validation. We generate an HMAC-signed token with a timestamp when the form renders. The token is validated on submission. This prevents direct POST attacks that bypass the frontend entirely. We specifically chose stateless HMAC tokens instead of WordPress nonces because nonces break when the page is served from cache. Our tokens work with any caching setup — full-page cache, CDN, static HTML cache, whatever.

Layer 4 — Silent rejection. When we catch a bot, we do not return an error. We return a fake success response. The bot thinks its submission went through. It moves on. If we returned a 403 or a validation error, the bot operator would know their script was detected and could adjust. Silent rejection removes the feedback loop that drives bot evolution.

What We Deliberately Left Out

Design is as much about what you remove as what you include. We made conscious decisions to leave out:

  • Cloud-based checking. No sending submission data to external servers. Ever. This is non-negotiable for GDPR compliance and for the principle that a spam filter should not create a privacy problem.
  • Content analysis. We do not inspect the text of the message. With LLM-generated spam, content analysis is unreliable and adds processing overhead. We focus on behavioral signals, not linguistic ones.
  • User-facing settings panels. There is no admin page with 30 toggle switches. The plugin works out of the box with sensible defaults. If you need to adjust something, there are developer-friendly filters and hooks. But the default state is “just works.”
  • reCAPTCHA integration. We are not wrapping someone else’s solution. This is a clean-room implementation of a different approach.

The Technical Choices

A few implementation details that developers might care about:

PHP only for server-side logic. No custom database tables. No cron jobs. No background processes. The plugin hooks into Contact Form 7’s existing submission pipeline via wpcf7_before_send_mail and wpcf7_spam filters. It validates the submission, makes a pass/fail decision, and gets out of the way. The footprint is minimal.

Vanilla JavaScript on the client side. No jQuery dependency (even though CF7 itself loads jQuery). No frameworks. The behavioral analysis script is a few kilobytes, loaded asynchronously, and does not block rendering.

Accessibility-first honeypot design. Hidden fields use aria-hidden="true" and tabindex="-1" so screen readers skip them entirely. We tested with NVDA, JAWS, and VoiceOver to make sure the honeypot does not create confusion for assistive technology users. Accessibility is not an afterthought. It is a constraint we designed around from day one.


What We Learned Along the Way

Building CF7 Ultimate Honeypot taught us a few things that go beyond the plugin itself.

Simple beats clever

Our first prototype was over-engineered. It had machine learning scoring, canvas fingerprinting, and a local SQLite database for tracking bot signatures. It worked well in testing. It was a nightmare to maintain and debug.

We stripped it back. The final version uses straightforward techniques — honeypots, timing analysis, cryptographic tokens — combined in a way that is greater than the sum of its parts. Every component is easy to understand, easy to test, and easy to debug when something goes wrong.

The best security tools are the ones that are boring on the inside. Boring code is reliable code.

The spam ecosystem is a business

Bots are not random chaos. They are products. Someone builds them, sells them, and provides customer support. Understanding this changes how you think about defense.

Bot operators optimize for cost per successful submission. If your form is slightly harder to spam than the next site, many bots will skip you and move on to easier targets. You do not need to be unbreakable. You need to be more expensive to attack than the alternative.

Every layer of defense we add raises the cost. Polymorphic fields mean the bot operator cannot write a static script. Behavioral analysis means they need to simulate human-like interaction. Token validation means they cannot skip the frontend. Silent rejection means they cannot debug their failures.

None of these layers is impenetrable alone. Together, they make your form a bad investment for the bot operator.

Privacy and security are the same conversation

We kept running into a pattern: the most popular anti-spam tools required sending user data to third-party servers. Akismet sends the message content. reCAPTCHA sends behavioral telemetry. Other services send IP addresses and device fingerprints.

For developers working with EU clients, this creates a conflict. You want strong spam protection, but every external API call is a potential GDPR liability. We decided early on that CF7 Ultimate Honeypot would process everything locally. No data leaves the server. No consent banner required for the spam filter. No DPA to sign with a third-party vendor.

This is not just a compliance decision. It is a performance decision. No external API calls means no added latency. It is a reliability decision. No external service means no outage when someone else’s infrastructure goes down. And it is a trust decision. Site owners can tell their visitors, truthfully, that their form data stays on the server.


Where We Are Now

CF7 Ultimate Honeypot is live on the WordPress plugin repository. It runs on sites that get a handful of submissions per day and sites that get thousands. The spam catch rate has held steady as bots have evolved, because the polymorphic approach does not depend on knowing what today’s bots look like — it depends on bots behaving differently from humans, which is a much more durable assumption.

We still maintain client sites. We still deal with spam. But the Monday morning phone calls about 600 spam messages have stopped. The contact forms work. The leads come through. The bots get a polite “Thank you for your message” and nothing else.

If you are a developer tired of duct-taping four plugins together to protect a contact form, or a site owner who has given up on ever getting a clean inbox, we built this for you. Not because the world needed another WordPress plugin, but because we needed a tool that did not exist and figured others probably needed it too.


Key Takeaways

  1. Existing solutions have real trade-offs. Akismet sends data externally. reCAPTCHA hurts performance and conversions. Basic honeypots are too predictable. There is no shame in acknowledging that and building something different.

  2. Polymorphic defense beats static defense. Any protection that looks the same on every page load will eventually be reverse-engineered. Changing the form’s internal structure on every request forces bots to solve a new problem each time.

  3. Layer your defenses. No single technique stops every bot. Honeypots, behavioral analysis, cryptographic tokens, and silent rejection work together to make your form expensive to attack.

  4. Privacy is a design constraint, not a feature checkbox. Processing everything locally is simpler, faster, more reliable, and keeps you on the right side of GDPR without extra paperwork.

  5. Ship boring code. The best security tools are the ones that are easy to understand, easy to test, and easy to fix at 2 AM. Save the cleverness for the architecture. Keep the implementation straightforward.

We built CF7 Ultimate Honeypot because we were frustrated. We are sharing this dev story because we think the frustration is universal — and the solution does not have to be complicated.


CF7 Ultimate Honeypot is free and open source on the WordPress Plugin Repository. If you want to understand the technical details behind the layered defense approach, read our deep dive on Invisible Security: Protecting Forms Without Annoying Users.

All Columns