Skip to content

Spam protection that stores, flags and never deletes

Defence in depth, none of it destructive. Spam is a label you can see and flip, not a bin you cannot open.

In short: Postbag's spam protection layers a honeypot field (_gotcha by default), a per-form, per-IP token-bucket rate limit, an origin allowlist, optional Cloudflare Turnstile verification and a heuristic score. Every outcome is stored with a status (received, spam or quarantined) and routes exclude spam and quarantined submissions unless told otherwise.

Honeypot

Every form has a honeypot field, _gotcha unless you rename it in settings.honeypot_field. Add it to your form hidden from humans; anything that fills it is stored as spam and routed nowhere. The bot sees the same 200 as a real visitor.

<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-10000px" aria-hidden="true">

Rate limit and origin allowlist

settings.rate_limit sets per-IP requests per minute with a burst for each form. Overflow is stored as quarantined with reason rate_limited and a 429 is returned with a retry hint, so the data is still there if it was real.

settings.allowed_origins restricts which sites may post. A post from elsewhere is stored as quarantined with reason origin_rejected. The same list drives CORS for fetch-based submissions. Behind Cloudflare, the real client IP and country are taken from the connection headers.

Cloudflare Turnstile

Set settings.turnstile { enabled, secret } and include the cf-turnstile-response field in your form. Postbag verifies the token server-side with a short timeout; a failed or missing token quarantines the submission with reason turnstile_failed. The check fails open into quarantine rather than dropping anything if Cloudflare is unreachable.

Scores, not verdicts

Submissions carry spam: { score, reasons[] }. Today the reasons are the honeypot and cheap heuristics; user actions ("spam" / "not spam") feed future scoring. Routes can include spam by setting quality.exclude_spam to false, useful for an audit webhook that should see everything.

Questions, answered

Will spammers know they were caught?
No. Spam and quarantined submissions receive the same response as accepted ones.
Can I recover a real message that was marked spam?
Yes. Spam is a status on a stored row. Change it in the dashboard or through the API and retry the deliveries.
Do I have to use a CAPTCHA?
No. The honeypot and rate limit are on by default; Turnstile is optional per form.

Your first form is three minutes away.

Sign up, get a submit URL, point a form at it. The first submission lands in your inbox and your email. Everything else appears when you need it.