Aller au contenu

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/.

POST /api/companies/{companyId}/crm/send

Both 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-approve
  • sent — channel adapter accepted
  • sent_dryrun — dry-run short-circuit (allowlist still applied)
  • blocked_by_allowlist — recipient not on allowlist
  • blocked_by_quota — daily cap reached on crm_credential_buckets
  • failed — channel adapter threw; bucket refunded; tx rolled back
GET /api/companies/{companyId}/crm/outboxes
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.

PATCH /api/companies/{companyId}/crm/outboxes/{outboxId}
DELETE /api/companies/{companyId}/crm/outboxes/{outboxId}

DELETE is soft (deleted_at stamp).

GET /api/companies/{companyId}/crm/approval-rules

Returns rows ordered by evaluation priority. First match wins inside the dispatcher.

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
}
PATCH /api/companies/{companyId}/crm/approval-rules/{ruleId}
DELETE /api/companies/{companyId}/crm/approval-rules/{ruleId}
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.

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).

GET /api/companies/{companyId}/crm/content-pieces

Query parameters:

Param Description
niche Filter by niche slug
channelIntent Filter by intended channel
sourcePath Lookup by file path
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.

PATCH /api/companies/{companyId}/crm/content-pieces/{pieceId}
DELETE /api/companies/{companyId}/crm/content-pieces/{pieceId}

DELETE is soft.

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 /api/admin/post-issue-hooks/reload

Hot-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.