Notify Triggers architecture
Notify Triggers is the control plane and execution coordinator for the Triggers platform. Engineers author flows in Studio, release immutable versions, and let the platform route events through conditions, mappings, rate limits, business services, outputs, and durable waits.
The current platform uses Kafka for source-event ingestion and NATS 2.14 JetStream for internal service coordination, replay, dead-letter delivery, and Durable Timer scheduling. MongoDB stores authoring and operational records. Redis stores short-lived coordination data such as rate-limit state and service heartbeats.
System context
Responsibilities
| Component | Responsibility |
|---|---|
| Studio | Edits the author graph, displays catalog schemas and validation, manages working copies, versions, and releases. |
| Notify Triggers V2 API | Owns flow definitions and release state, validates graphs against active service manifests, and compiles accepted author graphs into immutable runtime graphs. |
| Notify Triggers V2 runtime | Consumes source events, pins each run to a released flow version, persists replay data, evaluates coordinator-owned nodes, and dispatches service or output handoffs. |
| Triggers SDKs | Run service-owned graph segments, apply action input mappings, evaluate service-side conditions and mappings, invoke unchanged business handlers, and return control to the flow. |
| Business services | Publish manifests and heartbeats, expose service sources and actions, and implement domain behavior without depending on Studio or Durable Timer internals. |
| Durable Timer | Owns durable wait occurrences, event snapshots, retry generations, wake scheduling, terminal records, and resume publication. |
| MongoDB | Stores flows, working copies, immutable versions, releases, manifests, source and output catalogs, run views, DLQ inspection records, replay indexes, and the Durable Timer resume outbox. |
| NATS JetStream | Carries internal service traffic and stores replay, DLQ, Durable Timer commands, state, snapshots, schedules, terminal records, and resumes. |
| Kafka | Supplies external source events and remains an available output transport. |
| Redis | Stores distributed rate-limit state and current service heartbeat data. |
Author graph and runtime graph
Studio edits an author-facing graph. A release never executes that working copy directly.
The compiler removes Studio-only metadata and adds deterministic internal
plumbing where execution needs a boundary that authors should not manage. For
example, a visible Durable Timer Time reached edge becomes an internal Notify
resume service boundary in the runtime graph.
Every execution is pinned to the released version that accepted its source event. Editing or releasing a newer working copy does not change an in-flight run or an active Durable Timer occurrence.
Event execution
The coordinator executes source-side conditions, mappings, and rate limits until it reaches a terminal handoff. Once a graph segment enters a business service, its SDK evaluates the nodes and action overrides in that segment. This keeps domain actions unaware of flow transport and control metadata.
Terminal handoffs can target:
- a service over NATS;
- a Durable Timer command over NATS;
- an output over NATS, Kafka, or HTTP;
- the DLQ when the failure is permanent.
Service discovery and release safety
Each SDK service publishes a manifest describing its sources, actions, supported node kinds, input schemas, and NATS targets. Notify stores the manifest and tracks the active version. Studio builds its component catalog from this data.
Release validation checks the complete graph against the active manifests and catalogs. A flow is rejected when a service, action, schema, capability, or transport target cannot be resolved. Manifest drift after release is surfaced as operational drift instead of silently rewriting the immutable release.
Reliability model
The platform is deliberately at least once:
- Kafka source events may be redelivered.
- NATS service handoffs, resumes, replay, and DLQ messages may be redelivered.
- Notify and a business service cannot atomically commit with an external side-effect system.
- Notify and Durable Timer use separate MongoDB and JetStream transaction domains.
Deterministic execution keys, durable replay records, compare-and-set state, resume receipts, fences, and message deduplication suppress known duplicates and stale work. They do not provide global exactly-once side effects. Business actions and external consumers must use the supplied identity or another domain key to be idempotent.
Transient failures are retried by the owning durable consumer. Permanent failures are recorded in the DLQ with enough flow and source identity for inspection and replay.
Durable waits
Durable Timer is a dedicated service, not an in-process sleep inside Notify or a business service. It stores the event received at the Timer, schedules a native NATS 2.14 wake, and publishes a resume back to Notify when due. Notify then continues the pinned released graph.
See:
Runtime data ownership
| Data | Store | Lifecycle |
|---|---|---|
| Working copies, locks, versions, releases | MongoDB | Controlled by Studio and Notify API |
| Active manifests and catalogs | MongoDB, refreshed through NATS | Controlled by services and Notify |
| Source replay payloads and DLQ messages | NATS JetStream | Durable runtime data |
| Replay indexes, run views, DLQ inspection | MongoDB | Operational query model |
| Rate-limit counters and service presence | Redis | Short-lived distributed state |
| Durable Timer active state and snapshots | NATS JetStream | Retained while required for waiting or replay |
| Durable Timer resume receipts | MongoDB | Retained long enough to make resume publication crash-safe |
Deployment and trust boundary
Notify Triggers V2 and Durable Timer are separate workloads built from this repository. They communicate with SDK-enabled business services through the shared internal NATS 2.14 bus.
The current Durable Timer integration does not introduce a separate NATS cluster, per-service Timer credentials, Timer-specific TLS, ACLs, certificates, or signing keys. Access to the internal NATS network is therefore the current trust boundary. Private flow-control context is validated and stripped before business handlers and public Kafka, HTTP, or output-topic boundaries.
Legacy V1 coexistence
This repository still contains the original V1 rule engine under
cmd/triggers. V1 consumes Kafka, evaluates MongoDB-backed flat rules, and
publishes Kafka outputs. Its API, collections, and process model remain for
existing deployments and migration support.
V2 runs from cmd/triggers-v2 and is the architecture described above. It
adds released visual flows, service manifests, SDK-executed segments, NATS
coordination, replay, DLQ, fleet visibility, and Durable Timer. V1 and V2 may
share Kafka and MongoDB infrastructure, but they do not share an execution
model and one must not be used as evidence that the other path is working.
For validation, prove the path that is actually being changed. Producing a Kafka event proves ingestion only; V2 verification also requires evidence that the released flow ran and that its NATS service, output, or Durable Timer handoff completed.