🎯 What We're Building
One owned platform — the Super System — where all of Love at First Fight's data lives once and every screen, email, and app reads from it. Below is the system we're building, not the stack we're leaving.
One platform
Owned modules
Surfaces
Tool spend cut
The vision
A custom platform built in-house: one clean database, logic in code (not fragile webhooks), every capability we rely on owned and extensible. It delivers courses, runs the CRM, takes payments, sends email, and automates the business — in each customer's local time zone, in real time, and AI-native so Claude can query and help run it.
✨ What the new system does
The capabilities that make it more than a like-for-like replacement.
🎓 Course delivery + gamification
Modules, lessons & pricing in one place. Duolingo-style progress, badges, cohort visibility, and couple/partner progress → higher completion, retention, upsells.
👥 Real CRM (People)
Leads, students, partners — payments, notes, tags, progress, watch-%. Per-user "3D" column views; sensitive data permission-gated. Replaces AC-as-CRM + the 39k-row sheet.
🛒 Own cart & checkout
One product, many pricing options (no more 18+8 duplicates). Stripe/PayPal direct — Apple/Google Pay, Klarna. Refunds from inside the system.
🌍 Time-zone-aware
Courses open and emails send in the customer's local time — retiring the Phoenix-time workaround.
📈 Trusted tracking
End-to-end attribution under one domain — replacing distrusted ThriveCart reports and ClickMagick.
⚙️ Automation, owned
A per-minute scheduler ("what needs to happen now?") replaces AC "waiting" flows. Marketing editable, back-end logic locked.
🔌 Integrations
QuickBooks/Wave accounting sync; Missive & WebinarJam connected via API where we keep them.
⚡ Real-time + 🤖 AI-native
Live notifications & CRM updates via sockets. APIs expose data so Claude can analyse, report, and assist ops.
🛡️ Secure by design
Secrets in encrypted env files, token auth, monthly key rotation, error alerting (Bear), YubiKey for staff under consideration.
Shape of the system
Full detail under Architecture. A future Flutter app reuses the exact same API — no rebuild.
Where the plan stands
🗂️ Current Systems reference / context
Background only — what we're migrating away from. Kept for context; the build itself is described under Vision, Modules and Architecture.
Site #1 — Marketing / Funnel laff_website
| Users | 6 (staff only) |
|---|---|
| Role | Quizzes, opt-ins, scarcity, reviews, blog |
| Content | 3,190 posts · 718 reports · 517 reviews · 7 quizzes |
| Key plugins | Thrive Quiz Builder, Leads, Ultimatum, Visual Editor, Yoast |
Site #2 — Courses / Membership laff_data ← engine
| Users | 41,113 customers |
|---|---|
| Role | LMS, member access, orders, automation |
| Growth | 2023: 2.4k · 2024: 18.9k · 2025: 14.9k · 2026: 4.9k |
| Key plugins | Thrive Apprentice, Uncanny + Thrive Automator, ActiveCampaign, 2× custom dispute plugins |
ThriveCart — external checkout (where the money moves)
⚠️ Data reality (drives the whole design)
- LOGS DWARF DATA wp_thrive_reporting_logs = 501 MB / 2.1M rows — 16× bigger than the orders table (31 MB)
- SMALL CORE Entire functional dataset ≈ 536 MB → fits in RAM
- GOTCHA Course titles are NOT in post_title (all say "Course overview post")
- GOTCHA Money is integer cents stored as strings
- GOTCHA Two automation engines run at once — audit both
🏗️ Architecture
Five front-end surfaces, one API — public (Nuxt), member, admin & ops (Vue SPAs), plus a future Flutter app — all speaking to the same Laravel API.
Front ends & clients
Data & services
Hosted on AWS · CloudFront in front of www / app / admin
Capability replacement
| Today (rented) | Super System (owned) |
|---|---|
| Thrive Apprentice | → Courses / LMS module |
| ThriveCart | → Commerce module + PSP |
| Thrive Quiz Builder | → Funnels module |
| Leads / Ultimatum | → Funnels module |
| Uncanny + Thrive Automator | → Automation engine |
| ActiveCampaign | → Messaging module |
| WordPress users/roles | → Identity & access |
Data store strategy
| Workload | Home |
|---|---|
| Functional users, orders, courses, payments | Postgres (main) sacred |
| Audit / business events access, activity, order events | Postgres — separate database (same server; split later) |
| Behavioral analytics navigation, A/B, funnels | In-house pipeline → PG → ClickHouse later |
| App / debug logs | Files + error tracker (NOT SQL) |
🔒 Rule: analytics writes are never synchronous in a request — fire-and-forget to a Redis Stream, drained in batches.
🧭 Decisions Log
Every architectural decision made so far, with status. Full reasoning lives in the research markdown files.
| Decision | Choice | Status |
|---|---|---|
| Backend framework | Laravel (PHP 8.3+) | OPEN |
| Front-end shape | Decoupled JSON API + 2 Vue SPAs (for future Flutter) | OPEN |
| Public site | Nuxt (Vue SSR) for SEO | OPEN |
| Deployment topology | 5 domains: www / app / admin / ops / api (ops = Vue SPA for staff procedures) | OPEN |
| API auth | Laravel Sanctum tokens | OPEN |
| Database engine | PostgreSQL | OPEN |
| DB topology | Functional + audit = 2 databases on 1 server (split-aware) | OPEN |
| Queues / workers | Horizon + Redis, separate pools | OPEN |
| Analytics capture | Redis Stream, async batch-drain | OPEN |
| Analytics: build vs buy | Build in-house (rejected PostHog) | OPEN |
| Hosting | AWS (CloudFront / Beanstalk / EC2 / S3) | OPEN |
| DB backup / recovery | EC2 + WAL-G PITR → S3 + local RAID + international | OPEN |
| Video hosting | BunnyStream (closes blocker) | OPEN |
| Amazon SES + per-minute scheduler (replaces AC) | OPEN | |
| Catalog model | One product → many pricing options | OPEN |
| Logic split | Editable marketing vs hard-coded back-end | OPEN |
| Payments | Stripe (primary) + PayPal · drop ThriveCart | OPEN |
| Phasing | Trust-Again pilot vs back-end-first | OPEN |
| Vue admin component library | PrimeVue / Vuetify / shadcn-vue? | OPEN |
| Repo shape | Monorepo vs 4 repos? | OPEN |
| API tier | Beanstalk vs ECS Fargate / App Runner? | OPEN |
⚡ Capacity & Bottleneck
Where the ceiling is, and how many users/day we can serve before it strains.
Today's real load
Start tier capacity
Headroom at start
The bottleneck
Bottleneck order (what breaks first)
Postgres stays the bottleneck — it has the lowest ceiling in the stack either way. PgBouncer doesn't make it faster; it removes the connection limit, leaving CPU as the only Postgres wall (so the bar moves right, not away). CPU scales with instance size + read replicas; connections don't — that's what PgBouncer is for.
Key fact: the functional dataset (~536 MB) fits in RAM, so the DB is memory-served, not disk-bound — the wall is CPU/connections, never disk. Writes are trivial (~88 orders/day, peak 293).
📈 EC2 scaling tiers — "if we make Postgres bigger, what can we handle?"
Because the whole DB fits in RAM, scaling = adding vCPUs. Each step up roughly doubles capacity. Pick a starting tier; scale on measured signals.
| Tier | Postgres instance | vCPU / RAM | ~Member sessions / day | ~$/mo* |
|---|---|---|---|---|
| ★ Today | t3.small | 2 / 2 GB | ~20k–30k | ~$15 |
| Start | t3.medium (rec.) | 2 / 4 GB | ~40k–60k | ~$30 |
| Steady | m7g.large (Graviton) | 2 / 8 GB | ~150k–200k | ~$60–75 |
| Growth | m7g.xlarge | 4 / 16 GB | ~350k–450k | ~$120–150 |
| Heavy | m7g.2xlarge | 8 / 32 GB | ~700k–900k | ~$240–300 |
| Big | r7g.4xlarge | 16 / 128 GB | ~1.5M–1.9M | ~$700–850 |
You're at the ★ row today (~5–6 q/s peak = ~1–2% of the smallest box). Start one notch up (t3.medium) for build/launch headroom, not because traffic needs it. * On-demand ballpark; Reserved cuts ~30–50%. A read replica ~doubles reads more cheaply than a tier jump + doubles as failover.
🟢 Recommended start (sized to reality)
| Postgres | t3.medium (2 DBs: laff + laff_audit) |
|---|---|
| Redis | t3.small |
≈ ~$45/mo total → ~40k–60k sessions/day (~15× current). Audit is a 2nd database on the same box — split to its own server later, on a trigger.
⚡ Redis tiers (sized for RAM, not CPU)
| Instance | RAM | When |
|---|---|---|
| t3.small | 2 GB | ★ now / start |
| t3.medium | 4 GB | growth |
| m7g.large | 8 GB | heavy |
| + split | — | on eviction |
🧭 Which lever, by signal
| DB CPU high | → bigger tier / read replica (scales ✅) |
|---|---|
| DB connections maxed | → PgBouncer (sizing up won't help ❌) |
| Redis evicting | → bigger / split Redis |
| API CPU high | → Beanstalk autoscale |
CPU & connections are different limits: CPU grows with instance size; connections don't — that's PgBouncer's job.
Scaling levers (in order)
- 1 · Cache more / longer in Redis (↓ DB load, free)
- 2 · Add a read replica (↑ CPU + spreads connections)
- 3 · Autoscale the API tier (↑ API CPU)
- 4 · PgBouncer (fixes connection ceiling)
- 5 · Bigger DB instance (↑ CPU only)
Redis split ladder (by measured traffic)
| Stage | Events/day | Setup |
|---|---|---|
| NOW | < 50k | 1 Redis + separate pools |
| 1 | 50k–1M | separate logical DB |
| 2 | 1M–10M | separate Redis instance |
| 3 | > 10M | + ClickHouse / Kafka |
Projected ~50k/day → firmly Stage 0. First split ~20× away.
🧱 Feature Modules
Each capability we bring in-house becomes a Laravel domain module. Sorted by build priority.
💡 Click any module to open its detail page — design questions, data model and open decisions.
| # | Module | Replaces | Priority | Notes (from Bruce's KB) |
|---|---|---|---|---|
| 1 | Identity & access › | WP users / roles | P0 | pivot-table access (grant/revoke = 1 row); permission-gated screens |
| 2 | CRM (People) › | ActiveCampaign + class-list Sheet | P0 | Leads/Students/Partner; per-user "3D" column views; notes; double-payment detection |
| 3 | Catalog & pricing › | ~18 + ~8 duplicate products | P0 | One product, many pricing options ($697/$277/$397/$197/+lifetime) |
| 4 | Courses / LMS › | Thrive Apprentice | P0 | video on BunnyStream ✓; time-zone-aware drips; coaching-replay auto-access |
| 5 | Customer Dashboard › | Thrive Apprentice member area | P0 | The member-facing app (app.) — login, my courses + switching, lesson player, quizzes/answers/results, progress & couple progress |
| 6 | Commerce + Cart › | ThriveCart | P0 | Stripe/PayPal direct (Apple/Google Pay, Klarna); in-system refunds; time-zone signup |
| 7 | Disputes / chargebacks › | laff-dispute-evidence-v2 | P1 | "First win" — already built, folds in |
| 8 | Automation + Scheduler › | Uncanny/Thrive Automator + AC waits | P1 | per-minute cron "what needs to happen now?"; editable marketing vs locked logic |
| 9 | Messaging › | ActiveCampaign email | P1 | Amazon SES; local-time sends; editable sequences |
| 10 | Gamification › | — (new) | P1 | Duolingo-style progress/badges; cohort + couple/partner progress → retention/upsell |
| 11 | Revenue & Attribution › | ThriveCart reports + ClickMagick | P1 | trusted sales screen; end-to-end attribution under one domain |
| 12 | Funnel builder › | GoHighLevel wishlist | P2 | team-editable funnels + checkout per pricing option |
| 13 | Integrations › | manual entry | P2 | QuickBooks/Wave accounting sync; Missive, WebinarJam via API |
| 14 | Analytics pipeline › | (scattered) | P2 | in-house, async Redis Stream; A/B tests; navigation tracking |
Cross-cutting requirements (touch many modules)
Identity & Access
Who can log in, what they are, and what each person is allowed to see and do — for ~41k customers and the staff team. Replaces WordPress users/roles and the scattered permission logic.
What it does
One identity table for everyone — customers and staff — with authentication (Sanctum tokens), roles, and fine-grained permissions. Course/product access is a pivot relationship (user ↔ product), so granting or revoking access is a single row change. Every other module asks this one "who is this and what may they do?"
🧭 Key design questions
orders.view, students.edit), then layer field-level visibility for the sensitive columns the CRM needs. Laravel does this cleanly with a policy/gate layer + a permission table; libraries like spatie/laravel-permission give roles+permissions out of the box. Avoid hard-coding per-user rules — drive it from data.🗂️ Data model sketch
🔗 Depends on / feeds
CRM (People)
Replaces ActiveCampaign-as-CRM and the 39k-row class-list Google Sheet. Leads, students, partners — all in one screen.
🧭 Key design questions
🔗 Depends on
Catalog & Pricing
The single biggest data-model change: collapse the ~18 Thrive + ~8 ThriveCart duplicate products into one product with many pricing options.
🧭 Key design questions
tva_bundles. Are bundles a pricing option, or a product made of other products? Decide the shape.🗂️ Data model sketch
🔗 Depends on
Courses / LMS
Course delivery: modules → lessons → resources, drip scheduling, certificates — replacing Thrive Apprentice. Video on BunnyStream.
🧭 Key design questions
post_title ("Course overview post"). Need the real title source + the course→module→lesson→resource tree. (Open data blocker.)🔗 Depends on
Commerce + Cart
Own cart & checkout, Stripe + PayPal direct, order ledger, refunds — replacing ThriveCart. The highest-risk migration (the $7.9M).
🧭 Key design questions
🔗 Depends on
Disputes / Chargebacks
The dispute-evidence tool Berner already built (laff-dispute-evidence-v2) folds in here. A standalone, high-value piece.
🧭 To work through
Automation + Scheduler
A per-minute scheduler ("what needs to happen now?") replacing AC "waiting" flows + Uncanny/Thrive Automator. Hard split: marketing editable, back-end logic locked.
🧭 To work through
Messaging
Transactional & marketing email via Amazon SES, sent in the customer's local time. Replaces ActiveCampaign email.
🧭 To work through
Gamification
Duolingo-style progress, badges, cohort visibility, couple/partner progress → completion, retention, upsells. A revenue feature.
🧭 To work through
Revenue & Attribution
A trusted sales/revenue screen + end-to-end attribution under one domain. Replaces distrusted ThriveCart reports and ClickMagick.
🧭 To work through
Funnel Builder
Team-editable funnels + checkout per pricing option — the GoHighLevel-style capability John wants, without the limits.
🧭 To work through
Integrations
Connect the tools we keep: QuickBooks/Wave (accounting sync), Missive (support), WebinarJam/EverWebinar — via API.
🧭 To work through
Analytics Pipeline
In-house behavioral analytics: navigation, A/B tests, funnels — captured async via a Redis Stream so it never slows the system.
🧭 To work through
Customer Dashboard
The logged-in student experience — where a buyer lands after login to consume what they paid for. This is the Customer Vue 3 SPA at app.loveatfirstfight.com. Unlike the other modules it's not a new back-end domain — it's the front-end surface that composes Courses, Quizzes, Gamification, Identity and Commerce into one place. Replaces the Thrive Apprentice member area. Full spec: customer-dashboard.md.
What the student gets
🧭 Key design questions
🗂️ Data it reads / writes (mostly composed from other modules)
🔗 Depends on / feeds
🛣️ Build Roadmap
Phased so we always have something working and never risk the money. WordPress runs until the very end.
Phase 0 — Research & decisions
Build this research base · answer open questions · lock decisions · get a production DB dump · read the dispute plugin.
Phase 1 — Foundation (read-only mirror)
Laravel JSON API + Postgres + Sanctum. Import users/orders/courses read-only. Minimal admin SPA that views data. Prove the model holds 41k users / 165k orders.
Phase 2 — Member experience (courses)
Customer SPA: login, dashboard, "my courses", lesson player, progress. Wire video storage. Port drip + certificates. Runs in parallel with WP.
Phase 3 — Commerce cutover
Integrate Stripe (+PayPal). Dual-run with ThriveCart, reconcile daily, flip "buy" buttons only after clean reconciliation. Keep rollback.
Phase 4 — Funnels & automation
Rebuild quizzes, opt-ins, scarcity. Rebuild both automation engines. Migrate ActiveCampaign triggers.
Phase 5 — Decommission WordPress
Final reconciliation. Freeze and archive both WP sites. URL redirects. Done.
⏰ Backstop deadline
Be live before ActiveCampaign renews 4 June 2027 (~$5,581/yr) so we don't pay another AC year. Soft, not hard — AC partial-refunds unused time. The hope is to be well ahead of it.
🧭 Open phasing question (decide with Linka)
Linka's idea: pilot with the simpler Trust Again program to prove the model.
Bruce's lean: build course-delivery / back-end (CRM + delivery) first, leave the marketing site,
tackle tracking later. Both agree: stabilise current stack first, build in parallel, retire one tool per phase.
💰 Commercials & Savings
The money case for the build: tool spend retired, the renewal deadline, and the wins that dwarf the subscriptions.
Tool spend targeted
Biggest single line
Be-live-by date
Savings targets (rough, annual)
| Tool | ~$/yr | Fate |
|---|---|---|
| ActiveCampaign | ~$5,581 | Replaced — SES + built-in CRM |
| ClickMagick | ~$3,500 | Largely absorbed — own tracking |
| AWS (was video backups) | ~$2,800 → ~$0 | Already moved to BunnyStream ✓ |
| Hookdeck | ~$470 | Obsolete — traffic internal |
| Pabbly | ~$240–360 | Obsolete |
| Uncanny Automator / Thrive licences | licences | Obsolete |
The wins bigger than tool cost
❓ What We Still Need
The open questions blocking real progress. The two starred items block the Courses module — the biggest unknowns.
🚧 Blockers (data)
- ★ BLOCKER Real course-title source (not in post_title)
- Video storage → BunnyStream (confirmed)
- Production DB dump (is local .test current?)
- tva_orders.status code meanings (1/4/0/2)
- Course→module→lesson hierarchy + drip rules source
- What is laff_data_courses2 — stale clone?
- Map ~18+8 duplicate products → one product + pricing options
🧩 Product / scope
- PHASING Trust-Again pilot vs back-end-first (decide w/ Linka)
- Subscriptions/recurring, or one-time only?
- Replace ActiveCampaign (SES + built-in CRM)
- Single currency (USD) confirmed?
- Password manager: RoboForm / 1Password / Bitwarden / Proton / custom?
- Domain/branding for the new system
⚙️ Technical / infra
- Vue admin component library choice
- Monorepo vs 4 repos
- Beanstalk vs ECS Fargate / App Runner
- PgBouncer from start or later
- WAL-G vs pgBackRest + restore drill cadence
- AWS region + per-tier cost estimate
✅ Recently decided
- Front-end shape (API + Vue SPAs + Nuxt)
- 4-domain topology
- PostgreSQL · functional + audit as 2 DBs, 1 server
- Horizon + Redis + analytics Stream
- AWS hosting + EC2 WAL-G PITR backups
- Capacity model (DB is the bottleneck)