SoConnective

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 automations collection: name, active, trigger (a domain event), and an actions[] array.
  • Triggers (fired from Payload afterChange hooks): 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 matching trigger + tenant, runs each action in the same req transaction, and writes an automation-runs log row (success/error + detail). Every layer is defensively wrapped — an automation can never break the originating write.
  • Wiring: automateDeal / automateContact / automateTask / automateAppointment are added to each collection's afterChange, alongside the existing timeline hooks. No infinite loops: created tasks are Open (the task.completed trigger 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.

Previous
Phase 2 — Omnichannel Inbox