01 // journal

We deleted the captcha and spam stayed quiet

build-log · 2026-09 · 2 min read · forms · spam · ux

journal/we-deleted-the-captcha
We deleted the captcha and spam stayed quiet

fig.01 · the honeypot, literally · photo by alexander mils on unsplash

Until this week, our contact form asked every visitor to solve a math problem before hitting send. "What is 7 + 4?" It felt responsible. It was also mostly decoration, so we deleted it. Here is why, and what we replaced it with.

The problem

A client-side math captcha protects against exactly one attacker: a bot that fills forms but cannot read the DOM or execute JavaScript. The answer to our question was rendered right next to the input. Any script that could submit the form could also read the sum. Meanwhile every actual human paid a tax: one more field, one more chance to typo, one more reason to close the tab and email a competitor.

The calculus was ugly. Real protection: near zero. Real friction: 100% of users.

The work

We swapped the visible puzzle for a honeypot. The form now carries an extra text field called website, hidden with display: none, tabIndex={-1}, and aria-hidden. Humans never see it, so it is always empty when a real person submits. Bots that blindly fill every input fill it too, and that is the tell.

The server side is the fun part. When website has a value, the API does not reject the submission. It returns a fake success:

if (website) {
  return NextResponse.json(
    { message: 'Message sent successfully!' },
    { status: 200 }
  );
}

No error, no retry trigger, no signal that anything went wrong. The bot moves on thinking it worked. The email never sends.

The same pattern went into the newsletter endpoint while we were at it. Total diff: one hidden input, one early return, and a whole visible field deleted from the UI.

What broke

Nothing yet, and that is the honest part. Honeypots do not stop bots that are smart enough to skip hidden fields, and plenty of modern form spam comes through headless browsers that do exactly that. If volume becomes a problem, the next step is a time trap: a timestamp rendered with the form, rejecting submissions faster than a human could type. We have not needed it. The right lesson is not "honeypot solves spam," it is "match the defense to the actual attacker," and most form spam is still the dumb kind.

The result

The form lost an entire field. Fewer things to fill, fewer things to fail, same deliverable on our end. And the form now tracks real conversions too, since a successful send fires a generate_lead event, which is the first time we can actually measure the thing the form exists to do.

The takeaway

Invisible defenses beat visible puzzles. Before adding friction to any form, ask who actually pays for it. If the answer is "everyone, including people you want," the defense is wrong. Put the cost on the attacker: hide the trap, fake the success, keep the form short.

// next step

We work this way on client projects too.

Tell us what you are making. We reply with a plan, a timeline, and a price.

← the journal