Inspiration
Every moderator knows the feeling. You open the mod queue and see the same post removed for the fifth time today — no flair, no required info, wrong format. You remove it, leave the same comment you've left a hundred times, and wait for the user to resubmit. They usually don't. The post dies, the user leaves frustrated, and you've wasted ten minutes of your day on a problem that should solve itself.
We moderated communities on Reddit and lived this loop daily. AutoModerator can remove posts, but it cannot teach. It cannot tell a user exactly what to fix, and it absolutely cannot restore a post the moment a user corrects it. That gap — between removal and reinstatement — is where good content dies and good users give up.
FlairEnforcer exists to close that gap.
What it does
FlairEnforcer is a formatting enforcement tool that gives users a second chance — automatically.
When a user submits a post that violates the subreddit's formatting rules (missing flair, missing required keywords, body too short, wrong title format), FlairEnforcer:
- Temporarily removes the post — not permanently, just holds it
- Posts a sticky checklist comment — telling the user exactly what to fix, item by item
- Sends the author a modmail — with the same checklist and a direct link to edit their post
- Watches for edits — the moment the user updates their post to comply, FlairEnforcer automatically approves it, deletes the checklist comment, and sends a confirmation
Zero mod intervention. The post goes from held to live the moment it meets the rules.
Mods configure everything through a visual dashboard — a pinned custom post in their subreddit. They can:
- Build rules per flair (or globally) with a drag-and-drop editor
- Set required keywords, minimum body length, and title format requirements
- Test regex patterns with a live preview before saving
- Monitor the held queue with force-restore and permanent-remove controls
- Track 7-day stats: posts held, auto-restored percentage, average fix time
If a held post isn't fixed within 24 hours, a one-off scheduler job fires exactly at expiry, sends the user a final modmail, and cleans up the queue.
How we built it
FlairEnforcer is built entirely on Reddit's Developer Platform (Devvit) using the Classic SDK.
Backend: TypeScript with @devvit/public-api. The core is two triggers:
onPostSubmit— evaluates every new post against the ruleset stored in Redis, removes and holds failing posts, schedules a precise 24-hour expiry job viacontext.scheduler.runJob({ runAt: new Date(expiresAt) })onPostUpdate— fires on every edit, re-evaluates the current post content, and auto-approves if it now passes
The evaluation engine (evaluate.ts) is a pure function with zero SDK
dependencies — fully unit-testable with Vitest.
Storage: Devvit Redis, scoped per installation. We use a Sorted Set
(held_index) for the held post queue — scored by expiry timestamp — so
concurrent submissions from multiple users never corrupt each other. Stats
use Redis Hashes with hIncrBy for atomic increments, and a 30-day TTL
prevents zombie key accumulation.
Dashboard: A Vite-bundled React app served as a WebView custom post.
The frontend communicates with the backend via window.parent.postMessage,
and the backend responds through useWebView's postMessage hook. No
external APIs, no external databases — everything stays inside Devvit's
sandbox.
Challenges we ran into
The concurrency trap. Our first instinct was to store the held post
queue as a JSON array in Redis. On high-traffic subreddits, two simultaneous
submissions would both read the array, both append their post ID, and one
write would silently overwrite the other — leaving posts trapped in limbo
forever. Switching to Redis Sorted Sets with zAdd made concurrent writes
atomic and eliminated the problem entirely.
The expiry scheduler design. Our original design used an hourly cron
job that looped through all held posts checking for expiries. On a large
subreddit with hundreds of held posts, this would hit Devvit's execution
time limit and crash mid-loop, leaving posts in a corrupted state. We
replaced it with one-off runAt jobs — each held post schedules its own
precise expiry job at creation time. No loops, no timeouts, no corruption.
The onPostUpdate race condition. On very fast edits immediately after
submission, onPostUpdate would occasionally fire before onPostSubmit
had finished writing the HeldPost to Redis. Our first instinct was a
retry loop with a 1-second delay. We realized this burns serverless compute
time unnecessarily — the correct fix is a graceful exit. The user's next
edit fires a fresh onPostUpdate, and by then the data is always present.
The WebView iframe constraints. Reddit's custom post iframe has an
opaque container, which means CSS backdrop-filter (glassmorphism) renders
as a plain gray box. We caught this during testing and replaced all blur
effects with flat dark cards — cleaner, faster, and consistent across
Reddit clients.
Accomplishments that we're proud of
The auto-restore loop is the thing we're most proud of. onPostUpdate
firing to automatically approve a corrected post — with the sticky comment
deleted and a confirmation modmail sent — is something AutoModerator
fundamentally cannot do. It requires event-driven triggers that only exist
inside Devvit. We built the one mod tool that is architecturally impossible
to replicate outside this platform.
We're also proud of the zero-corruption data layer. The combination of atomic Sorted Set operations, one-off scheduler jobs, and stats TTLs means FlairEnforcer will not accumulate data debt or corrupt its own queue under any traffic pattern.
What we learned
We learned to think in Devvit's constraints rather than around them. Every time we reached for an external database or a polling loop, we asked: can Redis Sorted Sets solve this atomically? Almost always, the answer was yes, and the solution was simpler than what we'd planned.
We also learned that the evaluation engine being a pure function — no SDK dependencies, fully unit-testable — is not just a clean code preference. It's what let us iterate on the rule logic quickly without uploading to Devvit after every change. Test fast locally, upload slow to production.
What's next for FlairEnforcer
- Multi-condition rules — AND/OR logic between multiple rule groups, not just per-flair matching
- Mod analytics export — downloadable CSV of held/restored stats for subreddit health reports
- User strike tracking — users who repeatedly submit non-compliant posts get flagged in the dashboard with a warning count
- Flair-based auto-removal messages — different sticky comment templates per flair, so r/personalfinance gets a different checklist than r/legaladvice
- Appeal flow — a button in the sticky comment that lets users request manual mod review if they believe the hold was incorrect
Built With
- api
- css
- developer
- devvit
- platform
- react
- redis
- tailwind
- typescript
- vitest
Log in or sign up for Devpost to join the conversation.