Skip to main content

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

ComponentResponsibility
StudioEdits the author graph, displays catalog schemas and validation, manages working copies, versions, and releases.
Notify Triggers V2 APIOwns flow definitions and release state, validates graphs against active service manifests, and compiles accepted author graphs into immutable runtime graphs.
Notify Triggers V2 runtimeConsumes source events, pins each run to a released flow version, persists replay data, evaluates coordinator-owned nodes, and dispatches service or output handoffs.
Triggers SDKsRun 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 servicesPublish manifests and heartbeats, expose service sources and actions, and implement domain behavior without depending on Studio or Durable Timer internals.
Durable TimerOwns durable wait occurrences, event snapshots, retry generations, wake scheduling, terminal records, and resume publication.
MongoDBStores 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 JetStreamCarries internal service traffic and stores replay, DLQ, Durable Timer commands, state, snapshots, schedules, terminal records, and resumes.
KafkaSupplies external source events and remains an available output transport.
RedisStores 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

DataStoreLifecycle
Working copies, locks, versions, releasesMongoDBControlled by Studio and Notify API
Active manifests and catalogsMongoDB, refreshed through NATSControlled by services and Notify
Source replay payloads and DLQ messagesNATS JetStreamDurable runtime data
Replay indexes, run views, DLQ inspectionMongoDBOperational query model
Rate-limit counters and service presenceRedisShort-lived distributed state
Durable Timer active state and snapshotsNATS JetStreamRetained while required for waiting or replay
Durable Timer resume receiptsMongoDBRetained 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.