A CRM for course programs and coaching orgs — built by replacing one, then generalizing.
- Role
- Designer & sole developer
- When
- 2025 — present
- Stack
- React
- TypeScript
- PostgreSQL
- Drizzle
- Zod
- WorkOS
Anpa started as the engine underneath The Paradox Process and turned into a product. Replacing Keap meant building, from scratch, what a CRM-plus-marketing-automation platform actually does: contacts, segmentation, automated sequences, and the runtime that drives them. Once it worked for one organization, it was clear the engine wasn't specific to one client. In the codebase's own words, Anpa is "the CRM for course programs and coaching organizations" — and the work since has been deliberately evolving a single-tenant app toward that product.
The engine
The heart of the system is the walker — the runtime that executes automation recipes. A recipe is a sequence of steps an organization builds to move someone through a journey, and the walker advances each enrollment through it one step at a time. (The subject is usually a person, but it's polymorphic — a class session or a replay bundle can run through a recipe too.) The engine supports around fifteen step types: email and SMS sends, fixed and condition-gated waits, yes/no and A/B branching, CRM task and opportunity actions, course-access provisioning, internal notifications, and outbound webhooks.
It doesn't run on an in-process loop. A Vercel cron drains a Postgres-backed task queue every minute; each step run does its work and enqueues the next, so the walker is self-rescheduling, and delays or quiet-hours are just future-dated rows in the queue.
The design decision I care most about: the walker reads the live recipe, not a copy frozen when an enrollment began. If an organization edits a sequence, contacts already mid-flight feel the change — which is how Keap behaves and what these teams expect. The honest, more interesting version is that it's live reads with deliberate carve-outs:
Separating "what runs" (always live) from "what happened" (snapshotted history) is what keeps the engine both flexible and accountable.
Built to run without me
The constraint that shaped everything: the system has to be operable by a non-technical team after I'm gone. That drove real architecture. It's why automation recipes are data an admin edits rather than code only I can change. And it's why there's a page composer — an authoring tool where operators build public landing pages from a library of approved section types, with readiness gates before anything goes live and immutable snapshots of what was published. "Don't make me the single point of failure" turned out to be a more demanding spec than any single feature.
The composer is one workspace of eleven: Today, Contacts, Automations, Tasks, Schedule, Communications, Commerce, Analytics, Operations, Content, and Site — 42 admin screens over 198 internal APIs, with five operator roles and 38 distinct permissions deciding who sees what, behind single sign-on.



Color is handled the same way: an operator picks three brand colors, and the theme engine hands back four complete section tones that are guaranteed readable — any combination that fails gets nudged toward light or dark in 5% steps until it clears WCAG AA. There is no way to publish an inaccessible page, because the bad combinations are never offered. A color change ripples across the whole site.
And the editor itself is engineered like software people live in:
Toward a product
Turning a single-client app into a product is usually pitched as "extract the reusable part into a service." I deliberately didn't, and the decision is written down: keep the one application and harden the boundaries first, keep the data single-tenant until the product boundary is clean, and introduce tenancy only after. Premature extraction would have frozen interfaces I didn't fully understand yet and bought distributed-systems problems I didn't have. Instead the seams get made explicit in place — shared contract modules, an organization/membership/identity model, Paradox seeded as "tenant zero" — so multi-tenancy can grow along those seams later instead of fighting them.
Identity is where that's furthest along. Authentication runs through WorkOS, backed by real identity tables (organizations, operator memberships, auth identities) and provider-agnostic SSO routes, so an organization can eventually bring its own sign-in. I scoped it honestly: it's wired and working but currently locked to a single org, with directory-sync deliberately deferred until a real second customer needs it. The product isn't multi-tenant yet — it's built so that becoming multi-tenant is an increment, not a rewrite.
Anpa is the part of my work that's about systems and judgment rather than screens: a runtime that interprets user-defined logic with the failure modes designed in, a deliberate and documented stance on when not to split a system apart, and an architecture shaped by who has to operate it. It's also a working example of the most common honest path to a product — solve one real problem completely, then notice you've built something general.
