Inspiration
Every mod team has that one username. You remove their post on Tuesday, see them again on Friday, and someone asks in mod chat: “Didn’t we already deal with this person?”
Usually the answer is yes — but it lives in someone’s memory, an old modmail thread, or a ban note nobody checked. Punishments end up inconsistent: one mod warns, another goes straight to a permaban, and the user never gets a clear pattern of consequences.
We built Bot Verdict because moderation shouldn’t depend on who’s on shift or who remembers the history. Small subs feel this pain just as much as big ones; there just isn’t room for a full internal tracking system. We wanted something that runs quietly in the background and only steps in when a line you define gets crossed.
What it does
Bot Verdict watches content removals in your subreddit. When a mod removes a post or comment, the app adds one strike for that user. Hit your thresholds and it acts automatically:
- 1st removal → warning message to the user
- 3rd → mute
- 5th → temp ban (7 days by default)
- 8th → permanent ban
All of those numbers are configurable.
It also:
- Decays old violations if someone stays clean (so a mistake from months ago doesn’t guarantee a ban forever)
- Notifies mods via modmail when it takes action
- Adds mod notes so the team can see what happened
- Lets you view a user’s record or override / wipe it from the ⋯ menu on any post or comment
You don’t open a dashboard or change how you moderate. You keep removing content. Bot Verdict handles the ladder.
How we built it
It’s a Devvit background app — no custom post UI, no extra pages for mods to learn. We listen for ModAction events (removelink, removecomment, spam removals) and ignore everything else so manual bans don’t double-count.
Redis stores each user’s violation count, status, and history per subreddit. On each event we apply decay first, bump the count, then check thresholds and call Reddit’s APIs for warnings, mutes, and bans.
Settings live in Mod Tools → Installed Apps, so each community sets its own ladder. Menu items hook into Reddit’s post/comment menu for “View Record” and “Override.”
Stack: TypeScript, Hono for the server routes, Vite for builds. We kept the codebase small on purpose — one clear path from “mod removed something” to “action taken or logged.”
Challenges we ran into
ModAction payloads are thinner than the docs suggest. We assumed we’d get removal reasons, moderator notes, or rule IDs on the trigger. The event only reliably gives action, targetUser, targetPost / targetComment, and subreddit context. Rule tracking meant scraping [R1] tags from title, selftext, and comment body — and accepting a lot of “Untagged” when mods only tag the removal-reason field.
Duplicate events in a short window. Reddit can fire multiple ModActions for what feels like one removal. Without handling that, violation counts jump twice. We added an in-memory dedup map keyed by username with a 30-second cooldown. That works for a single instance, but it’s not shared across cold starts or multiple workers — we had to be explicit that dedup is best-effort, not a distributed lock.
Internal state vs Reddit state diverges. VerdictBot stores tier and violations in Redis; Reddit stores real mutes and bans. A temp ban can expire on Reddit while our record still says tempbanned. Decay lowers counts in Redis but doesn’t call unbanUser or unmuteUser — by design, but it complicates escalation: the next removal might try to ban someone who’s already banned and hit API errors we have to catch without rolling back the violation increment.
Escalation order and idempotency. Thresholds aren’t independent — you have to evaluate perm → temp → mute → warn in one pass and skip tiers the user already passed (record.tier !== 'permabanned', etc.). Getting that wrong either double-punishes or never escalates. Warning only fires when tier === 'none', so decay-driven tier downgrades created edge cases we had to test manually.
Lazy decay math. There’s no cron on Devvit for “reduce violations every 30 days.” Decay runs on the next violation, using lastViolation, lastDecayCheck, and floored day periods. Off-by-one on Math.floor(daysSinceLastDecayCheck / decay_days) and tier recalculation after decay (recalculateTier) were easy to get wrong; we unit-tested that path because it’s easy to break silently.
Settings from the portal don’t arrive validated. Mods can save mute_threshold: 2 and warn_threshold: 5. We added normalizeSettings() to enforce strict ordering (warn < mute < temp < perm) at load time so runtime logic doesn’t assume sane input.
Async trigger handler vs Reddit’s timeout expectations. on-mod-action has to return 200 quickly. Heavy work runs in void handleViolation(...).catch(...) so the trigger doesn’t block. That means errors surface in logs, not to the mod who removed the post — we had to log aggressively and never throw past the handler boundary.
Moderator detection costs an extra API round trip. Skipping mods means getUserByUsername + getModPermissionsForSubreddit per tracked removal. Fail-open on error (treat as non-mod) vs fail-closed was a product call; we chose fail-open so a Reddit API blip doesn’t skip enforcement for everyone.
TypeScript strictness on the server bundle. exactOptionalPropertyTypes, trigger request types from @devvit/web/shared, and keeping test files out of tsc --build while still running Vitest took config tuning — small stuff, but it blocked deploy until lint and project references were aligned.
Accomplishments that we're proud of
- Shipped a full escalation ladder (warn → mute → temp → perm) that actually runs without mod input after setup
- Violation decay — rare in mod tools, and it matters for long-lived communities
- Override menu — mods always have the last word; we cared about trust as much as automation
- Zero workflow change — install, set numbers, go back to removing content
- Got it through upload, publish, and public listing review on the Devvit platform
- Kept the project readable: tests for decay/settings/rule parsing, plain-language README for real mods
What we learned
Background apps fit moderation better than we expected. Judges and users care about “does this install and work without training?” more than flashy UI.
The Reddit API shapes your product whether you like it or not. You design around what triggers actually send, not what would be ideal.
Mod trust is everything. Automated bans without a visible record and an easy override would’ve been a non-starter. Notifications and mod notes aren’t extras — they’re how teams stay comfortable leaving the tool on.
What's next for Bot Verdict
- Smarter rule tagging if Reddit ever exposes removal reasons to apps
- Optional “cooldown” between escalations so two removals in one minute don’t jump straight to a ban
- Clearer post-decay behavior — maybe prompts for mods when internal status and Reddit ban state don’t match
- Per-rule thresholds (stricter on spam, looser on civility)
- More playtest feedback from real subs on defaults — are 1/3/5/8 the right out-of-the-box numbers?
Long term we’d love subs to treat repeat enforcement as something you configure once, not something you re-argue in mod chat every week.
Built With
- devvit
Log in or sign up for Devpost to join the conversation.