ADR-019: Commercial Motions Architecture
Status: Draft Owner: @bilal Date: 2026-03-09
Context
Envo Energy is evolving beyond a single self-serve SaaS product. Three distinct customer segments have emerged, each requiring different sales models, pricing structures, onboarding journeys, and infrastructure capabilities. We need a coherent architecture that supports all three without fragmenting the codebase.
Decision
Introduce three commercial motions — Growth, Acquisition, and Partner — served by a two-app monorepo:
| Motion | Sales Model | Pricing | Target Customer |
|---|---|---|---|
| Growth | Self-serve | Fixed tiers (Basic/Premium) | SME landlords, small HMO operators |
| Acquisition | Agency/outbound sales | Per-enquiry, custom pricing | Mid-market landlords, letting agents |
| Partner | Enterprise sales | Custom contracts | Large enterprises, housing associations |
Two-App Structure
| App | Domain | Purpose | DB Access |
|---|---|---|---|
envo-dashboard | app.ehq.tech + custom domains | Tenant-facing SaaS (all motions) | Prisma + Pothos GraphQL |
envo-admin | admin.envo-energy.com | Internal Envo ops — provisioning, pricing, invoicing | Direct Supabase client |
Key Architectural Choices
envo-dashboardremains the monolith — all tenant-facing features for all motions live here. No decomposition.envo-adminis a separate Next.js app — internal-only, behind CF Access, uses direct Supabase client (no Pothos overhead).- Close CRM handles the Acquisition sales pipeline externally — no in-house CRM.
commercial_motioncolumn onorganisations— determines which features, billing, and onboarding flow apply.- Feature gating via
canAccess(org, feature)— Growth customers gated by plan tier; Acquisition/Partner gated by custom config. - Custom domains via Cloudflare for SaaS — Acquisition/Partner customers get branded domains resolved by middleware.
Options Considered
| Option | Pros | Cons |
|---|---|---|
| Single app, route groups for admin | Simpler deployment | Admin pollutes tenant-facing auth, GraphQL layer unnecessary for internal queries |
| Two-app monorepo (chosen) | Clean separation, right tool for each job | Two deployments to manage |
| Microservices per motion | Maximum isolation | Premature complexity, shared DB makes this pointless |
Consequences
Easier:
- Each app can evolve independently (admin moves fast without affecting tenants)
- Feature gating is centralised and testable
- Sales pipeline stays in purpose-built tooling (Close CRM)
Harder:
- Shared types/utils may need extracting to
envo-commononce duplication grows - Two deployment pipelines to maintain
- Custom domain SSL provisioning adds operational complexity
Related
- ADR-020 Enquiry Billing Model — per-enquiry pricing for Acquisition motion
- ADR-014 White-Label & BYOAK — custom domains and API key isolation
docs/05-Specs/Commercial Tiers Scoping.md— full scoping documentdocs/05-Specs/Enquiry Billing Model.md— detailed billing model analysis