Two kinds of spam
Bulk spam is sent by programs. They find forms by crawling the web, fill every field with something, and post as many as they can. The messages are often nonsense, links or the same text a thousand times. They arrive in waves.
The other kind is typed by people, often paid per message. It reads like a real enquiry: a web redesign offer, an SEO audit, a guest post, a lead list for sale. The names and company details change, the pitch does not. It arrives a few a day, and it is the kind that wastes the most time, because each one has to be read to be dismissed.
A test for bots does nothing against the second kind, and reading every message yourself does not scale against the first. So the layers split the work.
Layer 1: a honeypot field
Add a field that people never see and bots fill in anyway. Any submission with something in it is a bot. It costs nothing, it needs no third party, and it catches the laziest bots, which are a large share of the volume.
It fails quietly against anything smarter: a bot that renders the page skips hidden fields, and a person never fills it. Build it carefully so password managers and screen readers leave it alone; the explainer on honeypot fields shows how.
Layer 2: rate limits and server rules
Validate on the server, not only in the browser: bots post straight to your handler and never see your client-side checks. Refuse submissions that are missing required fields or are far longer than any real message.
Limit how often one address can submit, and block the addresses that keep coming back. Challenge lets you refuse challenges to addresses and ranges you choose; see IP address blocking. Rate limits stop floods. They do little against spam that arrives from many addresses, a few messages each.
Layer 3: a challenge
A challenge makes the browser prove something before the form goes out. A classic CAPTCHA makes the visitor solve a puzzle, which costs you some of the people who would have written. An invisible challenge does the work in the browser instead.
Challenge is a proof-of-work check. When the visitor presses Send, their browser runs the check and the solution travels with the form. A script that skips it sends no solution and your server refuses it. A bot that runs it pays CPU time on every submission. Challenge does not stop headless browsers outright, and it does nothing against a person typing a pitch. The proof-of-work explainer covers why.
Layer 4: a content classifier
The only way to stop a pitch a person typed is to read it. A content classifier does that for you: your server sends the submission, and gets back a verdict.
Classify takes the whole form, or one message box, and answers spam or clean with a score from 0 to 1. The spam label starts at 0.8, and flags about 1 in 1,000 good messages at 0.8, counted across all the traffic in our reference set. It runs in the EU (Ireland).
Word blocklists are the do-it-yourself version of this layer. They work for a week: the senders change the wording, and the list starts catching real customers who happen to mention the same words.
Layer 5: a review queue
Every filter gets some messages wrong, and a real enquiry thrown away costs more than a spam message read. So do not delete what the filters doubt. Hold it somewhere a person looks once a day.
With Classify, act on the label and use the score for the queue: deliver what is clearly clean, file away what is labelled spam, and hold the band in between for a person. Where that band starts is your choice; the labels and scores page shows the trade-off at each point.
What each layer stops
| Layer | Stops | Lets through |
|---|---|---|
| Honeypot field | Simple bots that fill in every field | Bots that read the page like a browser, and every person |
| Rate limits and IP rules | Floods from one address or range | Spam spread thinly across many addresses |
| Challenge | Scripts that post without running the page, and the cheap bulk | Headless browsers willing to pay, and every person |
| Content classifier | Pitches, scams and junk, whoever typed them | A message it scores wrongly, in either direction |
| Review queue | Losing a real lead to a wrong verdict | Nothing, but it costs a person's time |
Plan for the day a layer is down
Every service you add to a form is a service that can be unreachable. Decide in advance what happens then:
- When Classify cannot answer, it returns a 503 that is not counted. Deliver the message as clean, so an outage never loses a real one.
- When Challenge cannot run its check, the form fires
botpoison-challenge-errorinstead of sending. Use it to show a message or another way to reach you.
A starting order
If you are adding protection to a form today:
- Validate on the server and add a honeypot field. Both are free.
- Add Challenge to take out the bulk without asking anyone to solve anything.
- Add Classify for the pitches that get through, and log the score beside your own judgement for a week or two.
- Turn on a review queue once you know where you want it to start.
Both products are on one plan with a free allowance each, so you can try the whole stack before paying anything. See pricing.