Client Intake Workflow
Audience: Engineers building or debugging the client intake flow; operations staff configuring intake triggers.
What you will accomplish: Understand the 9-step intake workflow — from lead parsing to welcome email send — including gates, idempotency, and failure handling.
Prerequisites: Read Workflow Engine and MCP Surface first.
Estimated time: ~8 minutes.
Turn a lead — an email, a web form, a referral — into a structured matter, CRM contact, and opening task list in under 10 minutes. The AI extracts structured fields, deduplicates against existing contacts, creates records, computes deadlines, and drafts a welcome email — all behind a human approval gate.
Trigger paths
- MCP tool:
intake.run— invoked by the AI agent directly - Webhook:
lead.createdevent from a connected CRM or web form platform
Both converge on the same workflow definition and idempotency key.
Workflow steps
Step 1: parse_lead
Extract structured fields (name, email, phone, case type, incident date) from the lead source using the extraction pipeline. Low-confidence fields become blocking gaps.
document.extract → IntakeFieldsStep 2: dedupe_contact
Search the CRM for existing contacts. Email exact match → A-number match → name similarity. Ambiguous results create a blocking gap for human triage.
CRMConnector.find_contactStep 3: create_contact
If no existing contact found, upsert a new CRM contact. Idempotent on (run_id, step). Skipped if reuse_contact was selected in step 2.
contact.upsert — write (confirm)Step 4: create_matter
Create a new matter in the case management system. Practice area derived from case_type. Privileged flag set on create. Idempotent on (run_id, step).
matter.create — write (confirm)Step 5: compute_deadlines
Apply deadline rule sets based on case type and jurisdiction. Uses business-day calendar, not naive date math. Past-due-on-create is flagged but does not block.
deadline.compute + deadline.scheduleStep 6: open_tasks
Render the opening task checklist from CaseTypeConfig. Tasks are idempotent (uuid5 namespace + run_id + matter_id + title). Completeness invariant enforced.
Task store — write (confirm)Step 7: draft_welcome
Compose a context-aware welcome email using the status_update_email prompt template. Saved as draft only — no send.
email.draft — draft onlyStep 8: GATE:human_review
The run parks in awaiting_approval. An attorney reviews the extracted data, contact/matter creation, and draft email. Blocking gaps prevent approval.
Required role: attorney — TTL: 24hStep 9: send_welcome
Only after gate approval: send the drafted welcome email. Exactly-once delivery via (run_id, send_welcome) idempotency key.
email.send — gated (human)
Idempotency keys
| Step | Idempotency key | Collision behaviour |
|---|---|---|
| Run | sha256(source_channel:provider_event_id)[:16] | Return existing run |
| Contact | (run_id, create_contact) | Return existing contact |
| Matter | (run_id, create_matter) | Return existing matter |
| Task | uuid5(namespace, run_id:matter_id:title) | Return existing task |
| Send | (run_id, send_welcome) | Return existing send receipt |
Gate configuration
gate: human_review
required_role: attorney
channels: [mcp, web, email]
ttl_seconds: 86400 # 24 hours
blocking_conditions:
- ambiguous_dedupe # step 2 returned ambiguous
- extraction_gaps # step 1 had low-confidence required fieldsIf any blocking condition is present, the gate cannot be approved until the condition is resolved (human triage via MCP tool, web UI, or email action).
Failure handling
| Failure | Response | Recovery |
|---|---|---|
ConnectorError(transient) | Retry with backoff (engine) | Automatic, max 5 attempts |
ConnectorError(auth) | Park run; alert operations | Human triage required |
ConnectorError(fatal) | Park run; alert operations | Human triage required |
| Ambiguous dedupe | Blocking gap at gate | Intake coordinator resolves via triage UI |
| Past-due-on-create deadline | Flagged, logged, run continues | Attorney reviews flagged deadlines |
| Engine error on deadline compute | Park run | Retry after fix |
ASSUMPTION (confirm)
The following decisions are implemented with reasonable defaults but require firm sign-off before production:
- I-001 — Exact intake field set per case type
- I-002 — Case type enumeration (
family-based,employment-based,other/uncategorised) - I-003 — Per-type opening task checklists
- I-004 — Write-confirm steps auto-confirmed inside authorised
intake.run - I-005 — Internal "matter opened" notification scope and gating
- I-006 — Ambiguous dedupe — always human disambiguation (no auto-merge)
- I-007 — Dedupe thresholds (email exact=1.0, name+DOB=0.90, A-number=1.0)
See docs/ASSUMPTIONS.md (opens in a new tab) for full tracking.
Example: invoking via MCP
uv run python -c " import asyncio from cam.core.workflows.intake import tool_intake_run from cam.core.workflows.intake.types import LeadPayload result = asyncio.run(tool_intake_run( lead=LeadPayload( channel="web_form", raw={ "email": "jane.doe@example.com", "name": "Jane Doe", "phone": "+1-555-123-4567", "case_type": "family-based", "incident_date": "2024-01-15" } ), idem_key=\"intake:web:2026-06-01:jane-doe\" )) print(result.status) # awaiting_approval "Verification
After gate approval, verify:
matter.get(result.matter_id)returns the created mattercontact.find("jane.doe@example.com")returns the created contactdeadline.list(result.matter_id)shows computed deadlines- Audit log shows
create_contact,create_matter,compute_deadlines,draft_welcome,workflow.approve,send_welcomein sequence - Hash chain is valid (
AuditService.verify())
Next steps
- Status Update Emails — Automated client communication after intake
- Document Generation — Engagement letters and standard forms
- MCP Surface — Full tool catalog
Last updated: 2026-06-01