modules
Clients module
Track recurring-retainer customers for service and professional-services businesses. MRR, ARR, churn — all in one place.
Updated 2026-04-19
Who this is for
The Clients module only shows up for service-mode orgs (consultancies, agencies, professional services). Retail and wholesale orgs don't see it — they use Sales + Contacts instead.
A Client in Opterflow is a commercial relationship, not a person. The person is a Contact; the Client is the agreement — "Acme Corp pays us GHS 5,000/month for social media management."
When to use Clients vs. Contacts
| Situation | Use |
|---|---|
| Retainer / recurring agreement | Client |
| One-time buyer / walk-in | Contact with a Sale |
| Lead you're still pitching | Contact in a Pipeline deal |
| Client whose retainer ended | Client with status=churned |
Every Client is backed by a Contact (its "primary contact" — the person you talk to most).
What's on /clients
- Three stat cards up top:
- Active Clients count
- MRR — monthly recurring revenue (pesewas-accurate, quarterly/annual retainers normalised)
- ARR — MRR × 12
- Client table sorted by MRR descending. Columns: name, status, start date, monthly retainer, total billed, last touch, owner, actions.
- Filter bar: status chips (active / paused / churned), owner dropdown, search.
Adding a client
Click Add Client. The modal asks for:
- Client name — usually the company name
- Primary contact — searchable dropdown of existing Contacts. Or "Create new contact" inline.
- Start date — when the retainer begins
- Retainer amount — in your org's currency
- Frequency — monthly / quarterly / annual
- Owner — which staff member is the account lead (optional)
- Industry (optional)
- Notes (optional)
Submit creates the Client row + first RetainerAgreement + primary ClientContact join row, all in one transaction.
How MRR is calculated
Source of truth: RetainerAgreement, not invoices.
- Only active clients contribute (paused and churned = 0)
- Only the currently-effective agreement counts (
effectiveFrom ≤ today < effectiveTo) - Frequency normalisation:
- monthly →
amount - quarterly →
amount / 3 - annual →
amount / 12
- monthly →
Stored denormalised on Client.currentMrr so sorting by MRR stays fast. Recomputed every time a retainer changes.
No pro-ration in v1. A client added mid-month contributes full MRR from day 1. Simpler, disappears next month anyway.
One-off project work isn't counted — that flows through Invoices with no retainer link. Financials module (v1.2) will surface "Project Revenue" vs "Recurring Revenue" as separate lines.
Editing a client
Click any row → opens the edit modal. You can change:
- Name, status, start date, owner, industry, notes
- Retainer amount or frequency — v1 replaces the current agreement in place. Rate change history is v1.1. Until then, editing a retainer is destructive — the UI warns.
Churning a client
Two ways:
- "Mark churned" (default) — status → churned, churnedAt = now, captures a reason (lost / budget / scope_ended / other). MRR drops to 0. Client stays visible (filter is "All" or "Churned").
- "Delete permanently" — admin only, requires type-to-confirm. Removes the row entirely. Usually you want "Mark churned" instead — you lose less data.
Reactivating a churned client
Click a churned row → Reactivate. Status flips to active, churnedAt + churnReason cleared, MRR recomputed from the existing RetainerAgreement.
Integration points
- Campaigns: client's primary contact shows up in campaign audience filters (
client.status = 'churned' AND churnedAt ≥ now - 90dis a good reactivation audience — coming in v1.1). - Pipeline: v1.1 adds a "Promote to Client" action on won deals.
- Financials: future MRR chart reads directly from RetainerAgreement history.
Permissions
| Role | Access |
|---|---|
| Super admin | Not applicable — super admin is platform-level |
| Admin | Full CRUD |
| Manager | Full CRUD |
| Staff | View only |
MRR/ARR stat cards respect financial visibility — staff without revenue permission see "Active Clients" count but the MRR/ARR cards show locked.
Common mistakes
- Using Clients for one-time buyers. If they're not on a retainer, they're a Contact with a Sale.
- Not setting frequency correctly. A client paying "GHS 30,000 annually" with frequency=monthly shows MRR=30,000 (wrong!). Set frequency=annual so MRR normalises to 2,500.
- Editing retainer without warning. v1 overwrites history. Write down the previous rate somewhere before changing, or wait for v1.1's proper rate-change flow.