Standalone lead and outreach capability matrix (Prompt 620)¶
Status: SERIES COMPLETE (620-628)
Date: 2026-08-09
Series: keprix-standalone-lead-outreach (620-628)
Depends on: Archived Visual Agentic CRM (keprix-agentic-crm-lead-gen, docs/architecture/agentic-crm-*)
Classification: REAL | PARTIAL | SIMULATED | MANUAL | MISSING | BLOCKED_OPTIONAL_CREDENTIALS
Honesty rule: UI presence or mocked tests alone never mark REAL. Conformance suite: tests/crm/test_standalone_outreach_conformance.py. Do not mark complete from UI alone.
Reuse (authoritative existing surfaces)¶
| Surface | Keep |
|---|---|
| Packages | src/keprix/crm/, src/keprix/outreach/, src/keprix/discovery/, src/keprix/sheet_preprocess/, Soft Wall, Companies House, viCal, email accounts |
| HTTP | /api/crm/*, /api/outreach/*, /api/crm/sheets/*, /api/crm/discovery/* |
| Stages SoT | CrmStage in crm/models.py (map outreach pipeline statuses; do not invent a third vocabulary) |
| Soft Wall gates | Existing CRM_GATES / outreach approvals; extend payloads, do not fork a second approval system |
| GUI | /crm/* and /outreach/* |
Capability matrix¶
| Capability | Class | Evidence / gap |
|---|---|---|
| Lead discovery (adapter jobs → CRM list) | REAL | discovery/runner.py, /api/crm/discovery/run, /crm/discover |
| Social / portal HTML scraping | SIMULATED | Social scrape refused; property portals stubbed by policy |
| Web directory discovery | PARTIAL | Adapter exists; needs search backend; Soft Wall on fetch |
| Companies House discovery + import | BLOCKED_OPTIONAL_CREDENTIALS | REAL when COMPANIES_HOUSE_API_KEY; else not_configured |
| Listing-page HTML ingestion | SIMULATED | Portals stub; intentional Soft Wall / legal checklist |
| CSV import | REAL | Discovery CSV, sheet upload, outreach lead import |
| TSV import | REAL | Sheet preprocess suffixes |
| XLSX import | REAL | openpyxl path |
| XLS (legacy) | REAL | crm/ingestion/readers.py via xlrd (keprix[analytics]) |
| ODS | REAL | crm/ingestion/readers.py via odfpy (keprix[analytics]) |
| Google Sheet | BLOCKED_OPTIONAL_CREDENTIALS | Routes exist; needs Google credentials |
| Pasted-row import | REAL | ingest_row_array / tools crm_ingest_import rows payload |
| API import | REAL | CRM upsert + discovery + ingest tools |
| Channel-attachment import | REAL | Parser + ingest_channel_attachment; IMAP email_ingest.poll_once implemented, default KEPRIX_SHEET_EMAIL_INGEST=0 (Soft Wall before CRM write) |
| Exact dedup | REAL | email → phone → website+company+locality (crm/ingestion/dedup.py) |
| Fuzzy merge | REAL | Soft Wall merge_identity |
| Sheet Soft Wall enrichment | REAL | /crm/enrich propose/apply |
| Licensed enrichment providers | BLOCKED_OPTIONAL_CREDENTIALS | Connection slots; Soft Wall without live keys |
| Provenance | REAL | crm_field_provenance |
| Contacts / Accounts | REAL | CRUD APIs + GUI |
| Customer / paying conversion | REAL | Soft Wall stage_customer_paying |
| Spreadsheet CRM editable grid | REAL | /crm/leads DataGrid + keyset/bulk/views/export/ingest APIs (CrmLeadsDataGrid, tests/crm/test_leads_grid_api.py) |
| Lists | REAL | CRM + outreach lists |
| Campaigns / sequences | REAL | Outreach store + GUI |
| Scheduling / process-due | REAL | Durable claim-lease scheduler (outreach/scheduler.py, claim_due_enrollments, Soft Wall park); see standalone-lead-outreach-scheduler.md |
| Soft Wall approvals | REAL | CRM gates + outreach send approvals |
| Live email send | REAL | Soft Wall approve → delivery.send_approved_message; SMTP via email_accounts when bound; default KEPRIX_OUTREACH_DRY_RUN=1 (honest dry-run); ESP live BLOCKED_OPTIONAL_CREDENTIALS without keys; see standalone-lead-outreach-delivery.md |
| Provider events (bounce/complaint) | REAL | Normalizer + apply + idempotency (provider_events.py); signed webhooks when verify keys set; fixtures cover SES/SendGrid/Mailgun |
| Reply ingest API | REAL | Manual/API inbound classify |
| Automatic mailbox reply scan | REAL | IMAP + normalize/match/ingest (inbound_mail.py, thread_match.py, scan_replies); cursors + idempotent provider_message_id; see standalone-lead-outreach-mailbox.md |
| Bookings (viCal) | REAL | CRM offer-booking + outreach bookings |
| Nurture | REAL | crm/nurture.py + workflows |
| Suppression | REAL | Suppression wins over enroll/send |
| Attribution | REAL | /crm/attribution |
| Agent tool initiation | REAL | crm / outreach / sheet / discovery tools |
| Channel (Telegram) initiation | REAL | Funnel intents + sheet→list→Soft Wall campaign journey (telegram_funnel, channel_journey); Slack/WhatsApp adapters PARTIAL |
| CLI initiation | REAL | keprix crm-ingest + python -m keprix.crm.ingestion |
| API / frontend initiation | REAL | Documented APIs + full consoles |
| Local single-user persistence | REAL | crm.sqlite + outreach.sqlite |
| Hosted multi-workspace persistence | REAL | SQLite CE + Postgres TEXT schema (durable.py, Alembic 028); workspace scoped queries |
| End-to-end journey (import→send→reply→customer→export) | REAL | tests/crm/test_standalone_outreach_e2e_journey.py + mail-capture SMTP adapter; Mailpit overlay optional |
| Observability (scheduler/provider/mailbox/funnel) | REAL | GET /api/outreach/observability, keprix crm-funnel observability; see standalone-lead-outreach-ops.md |
| Ops docs (CE/Docker/Postgres/backup) | REAL | docs/architecture/standalone-lead-outreach-ops.md, docs/troubleshooting/standalone-lead-outreach.md |
Build order (series)¶
| Prompt | Focus | Depends on |
|---|---|---|
| 620 | This matrix + contract + conformance gaps | Owner approval |
| 621 | Canonical lead schema + spreadsheet ingestion (XLS/ODS/paste honesty) | 620 |
| 622 | Durable storage + tenant isolation (CRM Postgres path) | 621 |
| 623 | Production spreadsheet CRM UI | 622 (parallel with 624 OK) |
| 624 | Durable campaign/sequence scheduler | 622 |
| 625 | Approved email delivery + provider events | 624 |
| 626 | Automatic mailbox replies | 625 |
| 627 | Funnel nurture + channel orchestration | 625+ |
| 628 | E2E observability, release, deploy | all prior; COMPLETED |
Test commands¶
# Series closed: readiness true when e2e + observability present
cd <workspace-root>/keprix
python -m pytest tests/crm/test_standalone_outreach_conformance.py tests/crm/test_standalone_outreach_e2e_journey.py -q
# Existing CRM/outreach regression
python -m pytest tests/crm tests/tools/test_outreach_automation.py tests/discovery -q --tb=line
Dual-store hazard (resolved by mapping)¶
Outreach PIPELINE_STAGES (new, enrolled, contacted, …) differ from CRM CrmStage. Standalone contract SoT is CRM stages; outreach statuses map into them. Do not create a third vocabulary.