CRM
Ce contenu n’est pas encore disponible dans votre langue.
The CRM subsystem (Phases A–G, shipped 2026-05-23 → 2026-05-26) adds outbound outreach across email, calendar, LinkedIn, WhatsApp, and Instagram. All routes are company-scoped except the post-issue-hooks reload (instance-admin global). Auth follows the standard board-token / agent-key conventions documented in Authentication.
For architecture see docs/architecture/crm.mdx. For the deep-dive narrative see the crm.html page in my_setup/docs/aimetier-explained/.
Send (canonical)
Section titled “Send (canonical)”POST /api/companies/{companyId}/crm/sendBoth manual sends and the sequence cron call into the same dispatcher (server/src/services/crm/outbox/dispatch.ts). The dispatcher orders allowlist → approval rules → atomic-quota check → channel adapter. Returns 200 with the resulting crm_activities row id and status.
Body:
{ "channel": "email" | "cal-com" | "linkedin" | "whatsapp" | "instagram" | "dry-run", "outboxId": "{outboxUuid}", "to": "recipient@example.com", "payload": { /* channel-specific shape */ }, "contentPieceId": "{contentPieceUuid}", "clientDedupKey": "sha256(agentId+contactRef+kind+body)"}Response statuses come back as crm_activities.status:
draft_pending_approval— manual approval default; approve via Approvals UI or/crm/approvals/bulk-approvesent— channel adapter acceptedsent_dryrun— dry-run short-circuit (allowlist still applied)blocked_by_allowlist— recipient not on allowlistblocked_by_quota— daily cap reached oncrm_credential_bucketsfailed— channel adapter threw; bucket refunded; tx rolled back
Outboxes
Section titled “Outboxes”GET /api/companies/{companyId}/crm/outboxesCreate
Section titled “Create”POST /api/companies/{companyId}/crm/outboxes{ "channel": "email", "niche": "renovation" | null, "secretRef": "secret://{secretId}:latest", "config": { "fromAddress": "ops@example.com" }}secretRef follows the secret://<id>:<v> convention — see Secrets.
Update / delete
Section titled “Update / delete”PATCH /api/companies/{companyId}/crm/outboxes/{outboxId}DELETE /api/companies/{companyId}/crm/outboxes/{outboxId}DELETE is soft (deleted_at stamp).
Approval rules
Section titled “Approval rules”List (ordered)
Section titled “List (ordered)”GET /api/companies/{companyId}/crm/approval-rulesReturns rows ordered by evaluation priority. First match wins inside the dispatcher.
Create
Section titled “Create”POST /api/companies/{companyId}/crm/approval-rules{ "predicate": { "channel": "email", "niche": "renovation", "tags": ["nurture"], "timeOfDay": { "start": "09:00", "end": "17:00", "tz": "Europe/Paris" } }, "action": "auto_approve" | "queue_for_manual", "priority": 100}Update / delete
Section titled “Update / delete”PATCH /api/companies/{companyId}/crm/approval-rules/{ruleId}DELETE /api/companies/{companyId}/crm/approval-rules/{ruleId}Preview (sandbox-evaluate without saving)
Section titled “Preview (sandbox-evaluate without saving)”POST /api/companies/{companyId}/crm/approval-rules/{ruleId}/preview{ "channel": "email", "niche": "renovation", "tags": ["nurture"] }Returns { "matches": true, "action": "auto_approve" } without persisting. Used by the Settings UI’s ApprovalRulesSection.
Approvals — bulk-approve
Section titled “Approvals — bulk-approve”POST /api/companies/{companyId}/crm/approvals/bulk-approve{ "approvalIds": ["{id1}", "{id2}", ...] }Approves N rows in a single transaction. Requires instance_admin role (PR #27 audit fix — non-admins get 403, not silent no-op). Each approval flips its crm_activities row from draft_pending_approval to sent (or schedules dispatch via the dispatcher).
Content pieces
Section titled “Content pieces”GET /api/companies/{companyId}/crm/content-piecesQuery parameters:
| Param | Description |
|---|---|
niche |
Filter by niche slug |
channelIntent |
Filter by intended channel |
sourcePath |
Lookup by file path |
Register
Section titled “Register”POST /api/companies/{companyId}/crm/content-pieces{ "title": "Q3 nurture sequence — first touch", "summary": "...", "sourcePath": "knowledge/marketing/nurture/first-touch.md", "sourceSha256": "{64-char hex}", "tags": ["nurture", "renovation"], "channelIntent": "email", "niche": "renovation"}Size caps (PR #23): frontmatter ≤ 64 KB; title ≤ 512 B; summary ≤ 4 KB; tags max 50 entries × 64 B each. UNIQUE on (company_id, source_path). sourcePath is validated against .. traversal (PR #27). sourceSha256 enforces 64-char hex regex.
Update / delete
Section titled “Update / delete”PATCH /api/companies/{companyId}/crm/content-pieces/{pieceId}DELETE /api/companies/{companyId}/crm/content-pieces/{pieceId}DELETE is soft.
Brand-voice check
Section titled “Brand-voice check”POST /api/companies/{companyId}/crm/brand-voice/check{ "contentSha256": "{64-char hex}", "draft": "..."}Reads crm_brand_voice_cache first (keyed by contentSha256); cache hit short-circuits without LLM call. Cache miss runs the check and writes back. Used by both the brand-voice-self-check agent skill and the brand-voice-coverage post-issue hook.
Post-issue hooks reload (admin-only)
Section titled “Post-issue hooks reload (admin-only)”POST /api/admin/post-issue-hooks/reloadHot-reloads my_setup/post-issue-hooks.yaml without server restart. Requires instance_admin role. Returns the parsed registry summary on success. PR #27 added this so operators can iterate on hook config without restart-induced agent-loop disruption.