The path of an incoming message
Tenancy
Tenant context is a runtime coordinate, not an option.- Tenant context is mandatory; runtime code fails fast without it
- There is one runtime model, with no deployment mode to branch on
- Context switching is encapsulated by the Tenancy domain
- User data lives in tenant schemas
Flow engine
- A flow is stored as a JSON graph
- The execution loop is deterministic
- Handlers resolve by
(type, version) - Registries are built at boot, with no database lookup on the hot path
sourceHandle, and the engine resolves
the next node from the flow’s edges. See
Handler contract.
Concurrency
Incoming message processing has three protection layers, and they guard different failures:
A lock that cannot be acquired (three attempts, two seconds apart) produces a
busy notice, and the message is dropped — it is reported as
lock_timeout, and there is no backoff queue behind it.
Webhook routing
The URL is/webhook/{channel}/{public_hash}, and public_hash resolves through
Redis (webhook:{hash}). The processed:{id} idempotency key is set by the
webhook controller before the job is dispatched.
Inbound messages are handed to IncomingMessageJob and resolved through the
tenant-aware routing pipeline.