Features
CRM — The Revenue Engine
The CRM module is the commercial core of OpsDash. It manages the full sales lifecycle: capturing leads, converting them into contacts and deals, managing the pipeline, and logging every interaction. Because CRM data and project data share the same database, OpsDash can calculate revenue metrics no standalone CRM can.
Contacts
Contacts is the central record for every person the agency works with: prospects, clients, contractors, and stakeholders. A contact in OpsDash can be linked to a deal, a company, a project, and an activity — and all of those links are queryable from one place.
- Free: 250 contacts, basic CRUD, table view
- Sales: 10,000 contacts + bulk operations, saved views, CSV import, owner assignment
Key features:
- List page (
/crm/contacts): Sortable, server-side paginated table. Bulk actions on Sales+. - Add Contact: Name, email, phone, company, job title, lead source, tags, owner, custom fields.
- Contact detail: Profile sidebar, quick actions (call, email, log activity, create task), linked deals, activities timeline, audit log.
- Saved views (Sales): Save any filter combination as a named view (view names capped at 80 characters; no cap on the number of views).
- CSV import (Sales): Bulk-create contacts from a CSV upload.
- Tags: Max 30 per contact, enforced by DB trigger.
| Item | Detail |
|---|---|
| Routes | /crm/contacts, /crm/contacts/[id] |
| Server actions | getContactsPaginated, createContact, updateContact, deleteContact, importContactsFromCsv, bulkUpdateContacts |
| Feature flags | crm:contacts (usage-capped), crm:csv-import (Sales), crm:contact-fields (Free), crm:contact-filters (Sales) |
| Audit actions | contact.created, contact.updated, contact.deleted, contact.merged |
Companies
Companies are organizations that contacts belong to and deals are associated with. Supports enrichment fields (renewal dates, upsell opportunity) and a flexible metadata JSONB column for org-specific data.
- Free: 50 companies, basic CRUD
- Sales: 2,000 companies + enrichment, metadata filters
Company enrichment (Clearbit): An optional one-click fill of industry and company size from Clearbit via the enrichCompany server action — deterministic firmographic lookup, not AI. The Enrich company control appears only when CLEARBIT_API_KEY is configured and the company has a name or domain; it is hidden otherwise so users are never prompted for a no-op.
Leads
Leads represent potential opportunities not yet qualified into contacts and deals. The purpose-built lead capture system lets any external tool (Zapier, Make, n8n, web forms) create a lead via API without manual data entry.
- Sales: Leads unlock at the Sales plate (
crm:leads) — CRUD, conversion, lead scoring, list filters / saved views, lead capture API, workflow triggers on conversion. Cap 5,000 leads (Sales), scaling to 50,000 (Agency).
Lead conversion: “Convert to Contact” creates a Contact and Deal atomically from the lead data (convertLeadToContactAndDeal). The lead is marked converted; the new contact and deal are linked back. Fires any lead_converted workflows.
Lead Capture API: External tools POST to /api/lead-capture with an API key. See API endpoints.
Deals
Deals are the commercial opportunities in the pipeline. The pipeline is a custom kanban board where org admins define the stages. Deal values feed directly into the Dashboard revenue KPIs and profitability calculations.
- Sales: Deals unlock at the Sales plate (
crm:deals) — pipeline board, custom stages, quotes, MRR/ARR, CSV import, at-risk detection. Cap 2,000 deals (Sales), scaling to 20,000 (Agency).
Key features:
- Kanban board with drag-and-drop stage changes (viewer role: read-only)
- Deal detail: Value, close date, probability, linked contact, company, project
- Deal quotes: Create, edit, and PDF-export quotes linked to deals
- At-risk detection: Automatically flags deals with no activity for over 7 days
- Workflow integration: Moving a deal to a stage fires
deal_stage_changeworkflows
| Item | Detail |
|---|---|
| Routes | /crm/deals, /crm/deals/[id] |
| Server actions | getDealsPaginated, createDeal, updateDeal, deleteDeal, updateDealStage, getDealIdsAtRisk, getQuotesForDeal, createQuote |
| Feature flags | crm:deals (usage-capped, Sales), crm:quotes (Sales), crm:csv-import (Sales) |
| Workflow trigger | deal_stage_change — fired on updateDealStage |
Activities
Activities are the CRM interaction log. Every phone call, email, meeting, and task completion is recorded as an activity linked to a contact, deal, or both.
- Activity types: call, email, meeting, note, task
- Timeline, scheduling, outcomes, reminders, and the org-wide activity feed (Sales): activities gain grouped/editable timeline view, scheduled calls with 15-minute reminders, and a paginated org feed (
crm:activity-timeline,crm:activity-scheduling,crm:activity-feed) - Activity feed shows chronological history per contact and per deal
Data Health
The Data Health page (/crm/data-cleanup — URL kept; the sidebar label and page title are “Data Health”) gives a live 0–100 data-health score, eight completeness/validity checks you can fix in bulk, and exact + fuzzy duplicate detection you can merge any time. Sales plate (crm:data-cleanup), hidden on Free. Implementation: data-cleanup-content.tsx, use-data-cleanup-content.ts, and siblings under crm/data-cleanup/ (see route README.md).
- Health score (0–100): weighted from the checks + duplicate flags. ≥85 Healthy, ≥60 Fair, else Needs attention. Updates live as issues are fixed.
- Health checks: contacts missing email, invalid email format, missing name, missing company; won-deals missing reason; lost-deals missing reason; open-deals missing close date; stale deals (open, no activity 30 days).
- Bulk-fix: won/lost-reason checks offer “Review & fix” → set the same reason on all flagged deals in one action (
bulkSetDealReason). - Duplicate detection: contacts (exact email + fuzzy name), deals (exact contact + title + fuzzy title), and companies (normalised name). Fuzzy groups show a confidence % (Sørensen–Dice) and require a two-step confirm before merging.
- Merge: soft-archives the merged record and reassigns its related rows (deals/activities/projects, or contacts/deals/projects for a company) to the kept one.
| Item | Detail |
|---|---|
| Route | /crm/data-cleanup (UI label: Data Health) |
| Server actions | runDataHealthChecks, findDuplicate{Contacts,Deals,Companies}, merge{Contacts,Deals,Companies}, bulkSetDealReason |
| Feature flag | crm:data-cleanup (Sales plate, hidden on Free) |
| Audit actions | contact.merged, deal.merged, company.merged, deal.bulk_reason_set |