CRM Subsystem
What this is
Section titled “What this is”AImetier’s CRM subsystem turns the platform into a control plane for outbound outreach in addition to coding work. Five channel adapters (Resend email, Cal.com, LinkedIn, WhatsApp via Twilio, Instagram Graph) share a single canonical OutboxDispatcher. The default posture is manual approval on every send. Auto-approve is opt-in per channel via graduated rules in crm_approval_rules.
Phases A–G shipped between 2026-05-23 and 2026-05-26 across 24 PRs (15 features + 7 audit fixes + 2 chore/bugfix). Every phase is independently revertable.
Where to read further
Section titled “Where to read further”This page is the architecture index. Three deeper documents own different concerns:
| Concern | Doc |
|---|---|
| Tick-by-tick narrative, send-flow diagram, full table list, all audit fixes | my_setup/docs/aimetier-explained/crm.html |
| API reference (route shapes, request/response payloads) | docs/api/crm.md |
| Channel guardrails — allowlist, approval rules, atomic quota, brand-voice, content registry, outreach coverage, post-issue hooks | my_setup/docs/aimetier-explained/guardrails.html#channel-guardrails |
| Cron tick walkthrough (CRM sequence runner) | my_setup/docs/aimetier-explained/cron-jobs.html#crm-sequences |
| Scheduler architecture — per-tenant advisory lock, parallelism env var | my_setup/docs/architecture-scheduler.md |
Schema — six new tables + the promoted crm_activities.status column |
my_setup/docs/AIMETIER-PG.md |
Channel credentials — secret://<id>:<v> convention |
my_setup/docs/SECRETS.md |
Audit reports (committed alongside the phases)
Section titled “Audit reports (committed alongside the phases)”These are point-in-time records of decisions and validation, kept for git-history continuity:
docs/crm-audit-2026-05-25.md— honest audit at end of Phase A. Every CRM page in HyperClip turned out to be already DB-backed; the real Phase A defect was a Drizzle Date-binding bug inconnector-sync-worker.ts. Includes decision 0a (allowlist gates dryRun).docs/crm-backfill-report-2026-05-25.md— Phase E backfill: 6,577 rows for the renovation niche, atomic-write run journal + lock per PR #22.docs/crm-e2e-smoke-checklist-2026-05-25.md— Phase G manual UI smoke checklist (8 sections: Approvals, Outboxes, Approval rules preview + bulk-select, channel filters, Library, Forecasts, connector-sync-worker silent logs).docs/crm-issue-compliance-rewrite-log.md— Phase F: 61 active outreach issues received## CRM compliancesection + 30 self-mention comments.docs/crm-phase-g-summary.md— Phase G deliverables (3 e2e + 1 integration + manual checklist +--update-snapshots).
Phase summary
Section titled “Phase summary”| Phase | PR | What shipped |
|---|---|---|
| A | #13 | Connector-sync Date-binding fix + honest audit |
| B | #14 | 5-channel outbound + dispatcher + 3 new tables + @aimetier/crm-channels package + manual-default approval engine + Approvals UI rebuild |
| C | #16 | Sequence cron with per-tenant advisory lock + 4 inbound webhook adapters + auto-pause-on-reply |
| D | #17 | crm_content_pieces + crm_brand_voice_cache + 4 routes + 5 MCP tools + 5 SKILL.md files |
| D-2 | #19 | Post-issue hook system: orchestrator + registry + 3 hooks + transition gates + admin-only bypass header |
| E | #18 | crm_activities.status first-class column + backfill scripts + HyperClip Library page |
| F | #15 | issue-compliance-update.mjs — added CRM compliance section to 61 issues, idempotent |
| G | #20 | 8 e2e + 3 integration tests + manual smoke checklist |
Audit-fix highlights
Section titled “Audit-fix highlights”Seven hardening PRs landed alongside the feature work. The two CRITICAL fixes:
- PR #22 — backfill journal atomic-write + lock. The Phase E backfill journal could be left half-written on mid-run crash, causing restart to re-apply already-processed rows. Fix:
tmp + rename+ per-script lock file. - PR #24 —
dispatch.consumeOrBlockTOCTOU race. Quota used SELECT-then-UPDATE; two concurrent dispatches could both pass the SELECT then UPDATE-decrement past zero. Fix: single atomicUPDATE … SET sent_today = sent_today + 1 WHERE sent_today < daily_cap RETURNING *(returning zero rows = quota exhausted).
The remaining five hardening PRs (#21, #23, #25, #26, #27) cover orphan migration cleanup, content-piece size caps + UNIQUE constraint, hook AbortSignal cancellation + malformed-audit feedback, oracle.degraded propagation + cron tenant parallelism, and a bundled M+L audit fix (bulk-approve role check, activity+bucket atomicity, audit-log file lock, webhook PII redaction, timing-safe sig compare, sourcePath traversal validation, sha256 hex regex, tags cap, bypass-actor capture, YAML hot-reload admin endpoint, plus 6 LOW polish items).
Code paths (quick reference)
Section titled “Code paths (quick reference)”| Concern | Path |
|---|---|
| Dispatcher | server/src/services/crm/outbox/dispatch.ts |
| Channel adapters | packages/crm-channels/src/{resend,cal-com,linkedin,whatsapp,instagram,dry-run}.ts |
| Sequence cron | server/src/services/scheduler/cron-sequences.ts |
| Sequence runner / oracle / sink | server/src/services/crm/{sequence-runner,oracle,sequence-action-sink}.ts |
| Approval engine + rules | server/src/services/crm/{approval-engine,approval-rules}.ts |
| Inbound webhooks | server/src/services/crm/inbox/{email-reply,linkedin,calendly,instagram}.ts |
| Post-issue hooks | server/src/services/post-issue-hooks/{orchestrator,registry}.ts + hooks/ |
| Hook YAML registry | my_setup/post-issue-hooks.yaml |
| Backfill | my_setup/scripts/crm-backfill/{index,state,util,sources}.mjs |
| Skills | skills/{crm-content-audit,crm-content-register,crm-outreach-recorder,brand-voice-self-check,email-sequence-runner,social-poster}/SKILL.md |
Env knobs
Section titled “Env knobs”| Var | Default | Purpose |
|---|---|---|
AIMETIER_CRM_SEQ_TENANT_PARALLELISM |
4 |
Max tenants processed concurrently per cron tick (PR #26) |
AIMETIER_CRM_DRY_RUN |
false |
Force all sends through dry-run adapter (allowlist still applies) |
AIMETIER_CRM_HOOK_TIMEOUT_MS |
10000 |
Per-hook AbortSignal timeout in the orchestrator |
How this fits the rest of AImetier
Section titled “How this fits the rest of AImetier”The CRM subsystem is layered above the existing platform — it doesn’t change agent execution, the heartbeat scheduler, the storage 5-class model, or any of the ADRs (0001 platform-foundations, 0002 agent-workspace-contract, 0003 connector-and-ops). Channel adapters live in their own package (packages/crm-channels/) intentionally to avoid being confused with LLM adapters in packages/adapters/. Drafted outreach copy still flows through storage.putKnowledge (Knowledge class); crm_content_pieces rows reference those blobs without owning their lifecycle.
The post-issue hooks fire on the same PATCH /issues/:id and /issues/:id/request-review endpoints used by every other workflow. Bypass via X-AImetier-Bypass-Hooks: true is admin-only and audited.