Product
Phase 3 — Native Automation Engine
- Status: ✅ MVP complete (gate PASS)
- Closed: 2026-06-12
- Decision: ADR 0006 (native automation, no n8n)
Goal
A native "when X happens, do Y" engine driven by the domain events the CRM already emits — no external automation tool, full control of the data and the identity graph.
How it works
- Definitions live in the tenant-scoped
automationscollection:name,active,trigger(a domain event), and anactions[]array. - Triggers (fired from Payload
afterChangehooks):deal.created,deal.stage_changed,deal.won,contact.created,task.completed,appointment.scheduled. - Actions (MVP): Create task (title, due-in-N-days, auto-assigned to the record owner, linked to the deal/contact) and Log note (writes a System Note on the timeline).
- Engine (
apps/cms/src/hooks/automations.ts): on an event it finds active automations matchingtrigger+tenant, runs each action in the samereqtransaction, and writes anautomation-runslog row (success/error + detail). Every layer is defensively wrapped — an automation can never break the originating write. - Wiring:
automateDeal/automateContact/automateTask/automateAppointmentare added to each collection'safterChange, alongside the existing timeline hooks. No infinite loops: created tasks areOpen(thetask.completedtrigger only fires on completion); actions never update the triggering record.
Frontend
/dashboard/automations: a rules list (name, "When … → actions", an active toggle, edit/delete) with a friendly trigger→actions builder dialog (add/remove action rows), plus a Recent runs panel reading automation-runs.
Data model
New tenant-scoped collections: automations (with the automations_actions array sub-table) and automation-runs. Migration add_automations (ADR 0007).
Bug found & fixed in the gate
In afterChange, doc.tenant arrives as a populated object, not an id, so the query { tenant: { equals: doc.tenant } } matched nothing and no automation fired. Fixed by normalising: typeof doc.tenant === 'object' ? doc.tenant.id : … (now G-21 mandates this in hooks).
GATE — PASS
End-to-end via the Payload local API in the cms container: created a deal.won → [Create task, Log note] rule, moved a deal to Won, observed tasks 0 → 1 and a run success: task created; note logged, then cleaned up.
Deferred (T-031)
Time-based/scheduled triggers and queued (out-of-transaction) execution; more action types (send message/email, update field, notify); conditions/branches and delays; a visual flow builder.