Skip to content
Platforms & CRMLive2026

Monarch Movement

A self-hosted community and onboarding platform that turns a real estate team's recruiting SOP into software members can't skip a step of.

843
passing tests (2 skipped) across 95 files
59
applied Drizzle migrations
10 modules / 46 lessons / 21 PDFs
classroom content shipped
3
public hosts on one Cloudflare Tunnel
Monarch Movement interface

Problem

A real estate team's downline onboarding ran as a manual SOP — three meetings, 30/60/90-day check-ins, a delegation roadmap — tracked ad hoc. It was hosted on Skool, a third-party community tool with no control over data, workflow enforcement, or cost at scale. Nothing stopped a sponsor from skipping a stage, and the group had no owned system to grow a ten-year, multi-thousand-member community on. The group the platform serves is an eXp Realty downline extending six referral levels deep from the core team, and the operator's stated horizon is 10,000 members over ten years. At that scale, a manual SOP tracked in spreadsheets and Skool posts was never going to hold up. Nothing forced a sponsor five referral levels removed from the core team to actually run the same three meetings and three check-ins every new recruit above them ran. Skool also had no way to scope an AI tutor to a specific lesson's content, no way to cap what an AI feature cost per member, and no path to encrypting member PII at rest. That control was out of reach because the platform wasn't theirs.

What was built

Monarch Movement is a self-hosted member platform that encodes the onboarding SOP directly into the product: invitations move through a pipeline with meeting logs and RAG (red/amber/green) health check-ins at fixed intervals, and stage progression can't be skipped. Around that core sit a full community feed (posts, comments, reactions, polls, hashtags, mentions, DMs), a gated classroom with an AI tutor scoped to each lesson, and a searchable member directory with a map. There's also gamification (points, levels, streaks, badges), a calendar with ICS export, and an operator console with analytics, moderation, an audit log, and outbound webhooks. It replaced Skool as the group's member hub and serves three public hosts off one Cloudflare Tunnel into a Mac Mini at home.

The three-meeting/three-check-in cascade is server-enforced, not just UI-suggested. The API rejects logging M2 before M1 is marked complete, and a pipeline stage only advances on an actual check-in being recorded, never on the calendar date alone. A member who lands amber or red at the 30-day check-in automatically triggers a reactivation follow-up job to their assigned leader, instead of waiting for someone to notice. Separate scheduled jobs nudge a leader three days ahead of each check-in milestone and escalate to an admin if a check-in goes seven or more days overdue. Another prompts a new member who hasn't opened their first lesson within five days or hasn't logged in for ten. Two accelerator programs — a cohort-based fast-track and a class-attendance tracker for the group's Global Alliance program — run alongside the main pipeline with their own operator-side enrollment and completion tracking. The platform carries six distinct AI surfaces beyond the per-lesson tutor: a personal daily coach, a community composer assist, a concierge, a roleplay practice-conversation tool, a librarian, and a content studio. Each is routed to one of three Claude model tiers by task cost, instead of defaulting every call to the most expensive model. Every automated message, check-in prompt, and AI response is constrained by an explicit voice guard that blocks retention/production-metric/operator-facing language in favor of growth-and-community framing — enforced as a review blocker on any AI-generated or operator-facing copy, not a style suggestion.

Technical approach

Next.js 16 App Router on React 19 with TypeScript strict throughout. Drizzle ORM runs against Postgres 16 with hand-reviewed migrations only (59 applied). Auth.js v5 handles credentials (argon2id, zxcvbn strength scoring, lockout after 5 failures) plus Google OAuth as secondary, with mandatory TOTP 2FA for operators and per-device session revocation. Background work runs on pg-boss - nine Postgres-backed queues (digest, reminders, backup, retention, onboarding follow-up/behavior/reactivation, wins roundup) - deliberately avoiding a Redis dependency. Realtime notifications use Postgres NOTIFY/LISTEN instead of a separate pub/sub service; browser push follows RFC 8030/8291 with VAPID keys. PII is encrypted at rest with AES-256-GCM, AAD-bound, plus HMAC email hashing for lookups without decrypting. The whole thing backs up encrypted nightly to Backblaze B2 with a 3-pass restore drill already validated. A single edge middleware (proxy.ts) does host-based routing across the three public subdomains through one cloudflared tunnel. A prod incident forced a WAL-surgery recovery (pg_resetwal) after the DB container's restart policy wasn't hardened yet, fixed by making restart:unless-stopped explicit in docker-compose.yml immediately after. Outbound webhooks carry HMAC-signed, timestamp-bound payloads and an SSRF url-guard that blocks loopback/private targets by design. That also makes it the one flagship feature never smoke-tested live in prod, since testing it properly requires an external catcher. Password security stacks three independent checks instead of one: argon2id hashing at OWASP-2024 parameters, a zxcvbn strength score gate that blocks anything under a minimum score at registration, and a HAVE-I-BEEN-PWNED k-anonymity check. That check hashes the password with SHA-1 and sends only the first five hex characters to the public breach-check API, so no plaintext or full hash ever leaves the server. A dual rate-limiting layer backs sign-in. A per-email failure counter locks an account for 15 minutes after five consecutive failures, and a separate per-IP Postgres token bucket (an atomic INSERT … ON CONFLICT DO UPDATE, no Redis) caps sign-in attempts independently of which account is being tried. A startup preflight script hard-fails the app if required secrets like the auth signing key or the PII encryption key are missing, instead of booting in a silently-degraded state. The AI layer's model-tier registry exists because the prior codebase this replaced had hardcoded the most expensive Claude model everywhere by default. Profiling the actual tasks — lesson Q&A, one-line coaching nudges, composer suggestions — found none of them were deep-reasoning problems. So the registry collapses model selection to a single-file, one-line change if a model is ever superseded, and routes routine high-volume calls to the cheapest tier by default.

Creative approach

Craft

The design system is deliberately minimal and dark: obsidian backgrounds, gold-champagne CTAs, parchment body text, with RAG status colors (forest/amber/rust) reused consistently from onboarding check-ins to operator dashboards. lib/tokens.ts is the single source of truth for every color, spacing, and typography value - a PR introducing a raw hex value outside it fails review. Fonts are self-hosted as variable woff2 files specifically to avoid the Google Fonts DNS leak and its GDPR exposure, and to remove a network round trip from the critical path. The typeface pairing is Fraunces for display and headings, using its optical-size axis to tighten letter-spacing at large sizes, against Inter for body and UI text, with JetBrains Mono reserved for code. It's a deliberate serif/sans split that reads as considered, not default-Tailwind.

Motion is CSS-only by rule, with no JavaScript animation library in the stack: interactive state changes run a 150ms ease-out transition, route changes fade over 200ms, and every transition collapses to none under prefers-reduced-motion. Cards never carry drop shadows on the near-black background — shadows are imperceptible at that luminance, so elevation is communicated by border contrast instead. That rule came directly from testing shadows on the actual obsidian palette, instead of assuming a design-system default would translate. The brand mark is a stylized butterfly silhouette rendered as the app icon, favicon, and loading state, paired with a damask background pattern derived from the same motif and rendered as a zero-raster SVG, not an image asset. Accessibility is enforced as a checklist against the token system itself, not left to component-level judgment. Every text/background pairing is tested to WCAG AA contrast ratios, minimum touch targets are 44×44px, and focus rings are a 2px gold-champagne outline with a 2px offset visible against every brand surface. A gated /dev/brand route renders every token, type specimen, and card variant as the standing acceptance check for any visual change before it ships.

Reframe

The core insight is that the SOP is the software, not a feature bolted onto a generic community app. The pipeline enforces stage order so a sponsor literally cannot skip a check-in, which is the actual behavior change the group needed from a downline of agents with wildly inconsistent follow-through. Everything else - community feed, classroom, gamification - exists to keep members inside a system that's already doing the enforcement work a manual process couldn't.

That reframe only matters at the scale the group is actually building for. A three-meeting cascade tracked by hand is survivable when a core team is running it directly on a handful of new agents. It breaks down at six referral levels of remove, where the person responsible for a new agent's onboarding may themselves be several levels removed from the core team and have no direct relationship holding them accountable to the standard. Software that structurally cannot advance a pipeline stage without a completed prerequisite closes that gap without requiring the core team to personally audit every sponsor's behavior. The enforcement travels with the platform, instead of depending on who happens to be doing the onboarding. The gamification and points system was built on the same reframe in miniature: points are earned only from genuine learning and community activity (posting, commenting, completing a lesson), and explicitly never from referral or production metrics. That keeps the incentive layer reinforcing the same growth-not-extraction framing the voice guard enforces in copy, instead of quietly becoming a leaderboard for recruiting volume.

Process and what failed

The build ran as a compressed multi-week sprint (May-August 2026) with heavy in-prod verification, using disposable throwaway accounts that get swept after each smoke walk. That pattern caught real bugs repeatedly before real members hit them. 2FA recovery codes were never actually displayed after enrollment, because a revalidatePath call unmounted the form carrying them (fixed, every pre-fix enrollee had to regenerate). A recurring-event RSVP guard compared against the series' original anchor date instead of the next projected occurrence, silently dropping RSVPs to the recurring 'Movement Monday' meeting for every member. A Resend suppression list quietly blackholed a claim email to the org's root sponsor after an earlier hard bounce, caught only by checking the suppression list before the real send. A credentials CSV was found sitting in Downloads mid-launch and had to be moved to Keychain and deleted.

Outcome

Live at monarchmovement.org since 2026-05-30, with onboarding.monarchmovement.org running the member platform and assets.monarchmovement.org serving lesson PDFs via signed R2 URLs. CI gates every push and PR on typecheck, lint, test, next build, and a CVE scan. The first real (non-seed) member was provisioned and walked through claim-to-dashboard in prod on 2026-08-04; the org's ten eXp accounts are staged for the wider rollout.

The platform shipped Phase 0 (infrastructure scaffold) and Phase 1 (full feature build) together at launch, not as separate milestones. All seven classroom modules, 27 lessons, and 21 supporting PDFs were live from day one, instead of a stub curriculum backfilled later. Production runs as launchd-managed processes on the operator's own Mac Mini, instead of a container orchestrator. A dedicated host-health monitor watches the app and worker processes, and a separate warm-up keepalive job keeps the local AI-adjacent tooling from taking a cold-start hit on the first real request of the day. The nightly backup job runs four times daily rather than once, encrypted to Backblaze B2, with a 3-pass restore drill already validated before go-live — the operator's standing rule that a backup nobody has restored from isn't a backup. Two production defects were caught by that same in-prod, disposable-account verification pattern: a recovery-code display bug and a recurring-event RSVP bug. Left alone, both would have silently affected every member's ability to recover 2FA or RSVP to the standing weekly meeting. Both were closed before real members could hit them.

The 2026-08-17 downline release moved the platform from staged to populated: a seven-level sponsor org tree shipped as "My team" for members, with operator-side placement and scoreboard views behind it, and a fourth intake branch opened for agents already at eXp. On release day all ten of the org's claim links went out through Resend in a single pass.

Since 2026-08-30 the three public marketing pages were rebuilt question-first with FAQ schema so answer engines can cite them, and an llms.txt at the apex permits that citation while keeping member content out of training use. The library gained a reading system: per-book status and progress, a continue-reading shelf, six new badges, a books-finished challenge metric and a reading leaderboard. The classroom now holds 10 modules and 46 lessons, up from 7 and 27 at launch.