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"> <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.