Build Bettermode
KINDAreplaces $49/mosaves $588/yrback to the verdict
A community platform where the interesting engineering is making spaces, permissions and feeds behave predictably, and the governing design is that every moderation action is notifiable, explicable and reversible from the first version.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and the digest worker.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why Nested spaces, permissions and moderation records with real constraints.
Get it Docker or your package manager. open ↗
Verify
psql connects and a recursive space query returns correctly - decidefree to read; advice worth having if the community is commercial
Why They depend on where your members are and how you are established, and the notice and statement mechanisms are built in phase two rather than added later.
Get it Read the hosting provider obligations for the jurisdictions your members are in and write down what each requires of you. open ↗
Verify
A written note names the jurisdictions and the specific obligations - decidefree
Why A statement of reasons has to reference something, and rules written after the first contested removal are rules written to justify it.
Get it Write the rules, the categories a notice can use, and what each restriction level means.
Verify
Written rules exist and every restriction level has a stated meaning - have readyfree, and it requires people's patience
Why Permission edge cases, notification tolerance and moderation load are all social rather than technical, and none of them appear with five test accounts.
Get it A genuine group willing to use it, including at least two moderators who are not you.
Verify
Thirty members have used it for a fortnight with at least one real moderation decision
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- spaces(id, parent_id, name, visibility, join_policy, archived_at) - members(id, user_id, space_id, role, joined_at, suspended_until, banned_at) - posts(id, space_id, author_id, title, body, created_at, edited_at, state) · state is visible, restricted or removed - notices(id, target_kind, target_id, reporter_id, category, body, received_at, decided_at) · the report mechanism - decisions(notice_id, outcome, reason, decided_by, at, statement_sent_at) · what was done and why - restrictions(id, target_kind, target_id, kind, reason, applied_by, at, lifted_at) · reversible by design - appeals(decision_id, body, submitted_at, outcome, at) - A restriction is a record with a lifting path rather than a deletion, because a removal you cannot reverse is a removal you cannot correct.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/community | Members' posts and moderation records, including reports about identified people. |
NOTICE_FORM_PUBLIC | required | true | The notice mechanism must be available to anyone, not only members. |
REQUIRE_DECISION_REASON | required | always | Present as a constant. A removal you cannot explain is one you cannot defend. |
NOTIFICATION_MODE | required | digest | Per-reply email is how a community loses its notifications to spam filters. |
APPEAL_ROUTE_AWAY | required | true | Appeals go to someone other than the original decider where possible. |
MODERATION_RETENTION_YEARS | required | 3 | Decisions and statements outlive the content they concerned. |
The build, in order
Spaces and permissions that hold
Who can see and do what is decided in the query, not the interface.
Interface filtering means the data was already sent.
Files
src/permissions.tstests/permission-leak.test.tsNested spaces confuse everyone if the rule is implicit.
Files
docs/permissions.mdA revoked role that persists until logout is not revoked.
Files
src/roles.ts
done when · tick each as it passeswatch out- Filtering content by permission in the interface while the API returns everything means a private space's posts are one network inspection away from anyone, and in a community that is a breach of the trust that made it private. Filter in the query and assert absence across list, single-item and error responses.
Anyone can report, and it goes somewhere
A notice mechanism exists, is easy to find and produces a tracked record.
The obligation is not limited to members.
Files
src/notices.tsAn inbox has no state and produces no evidence of what was done.
Files
src/notices.tsThe reporter should know it arrived.
Files
src/notify.ts
done when · tick each as it passeswatch out- A report button that sends an email to a shared inbox produces no record, no state and no way to show what was done, which fails the obligation and also fails the community. Make a notice a database record with a lifecycle from the first version.
Decisions that explain themselves
Every restriction produces a statement of reasons to the affected member.
Moderators forget within a week and members ask later.
Files
src/decisions.tsNot as a moderator's optional courtesy.
Files
src/statements.tsA removal you cannot reverse is one you cannot correct.
Files
src/restrictions.ts
done when · tick each as it passeswatch out- Removing a post without recording why makes the decision impossible to explain when the member asks, and moderators genuinely forget within a week. Require a reason at decision time, send it, and keep the record, so a contested removal is a conversation about a stated reason rather than about memory.
Appeals, because moderators are wrong sometimes
A member can contest a decision and get an answer.
Including the ones that seem obvious.
Files
src/appeals.tsWhere possible. Otherwise it is a formality members see through.
Files
src/appeals.tsA high rate means moderation is going wrong, which is worth knowing.
Files
src/moderation-report.ts
done when · tick each as it passeswatch out- An appeal reviewed by the same moderator who made the decision is a formality, and members work that out quickly and stop appealing, which removes your only signal that moderation is going wrong. Route to a different reviewer where the community is large enough, and report the success rate either way.
Feeds and notifications people will tolerateproduct builder
Members see what matters without being emailed constantly.
Same rule as phase one. A feed is just another read path.
Files
src/feed.tsPer-reply email is how a community loses its own notifications to spam filters.
Files
src/digests.tsYears of history is not a day's notifications.
Files
src/membership.ts
done when · tick each as it passeswatch out- Notifying on every reply in every space a member has joined produces dozens of emails a day, and the response is not to adjust settings but to filter the whole domain to spam, which removes even the notifications that mattered. Batch by default and make per-space control easy to find.
That is the whole plan for Bettermode. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Legal advice on which obligations apply.
- Automated content classification.
- Trending and recommendation.
- Monetisation and paid memberships.
- embeds, APIs, customization, analytics, moderation, and enterprise integrations
- network effects
- native mobile apps
- video and voice infrastructure
- enterprise moderation and discovery
- Moderator workload reporting, since burnout is what actually ends volunteer moderation
- Transparency reporting on notices and outcomes, which builds trust and is increasingly expected
- Bulk moderation for coordinated abuse, once single decisions are properly recorded
Need the files? The project pack on the verdict page hands your agent the whole brief · more community.