After identifying the conversation and the automation flow associated with the channel, the Flow Service takes over control of the entire automation execution.
In the previous articles we saw how a webhook is received, validated, and forwarded for processing in a secure way. At this point, the platform has already identified the message's origin, validated its authenticity, located the corresponding channel, and created (or retrieved) the conversation associated with the user.
It's from this stage onward that the Flow Service comes into play, responsible for executing the automation configured for that channel.
In this article, we'll focus exclusively on how this service works, understanding which actions it performs and how it performs them.
The execution flow happens as follows:
Message
(flow.execute.queue queue)
│
▼
Flow Service
│
▼
Loads Flow (JSON) from Redis Cache (base structure -> react-flow)
│
▼
Flow linked to the Channel found?
│ │
No Yes
│ ▼
│ Fetches INSTANCE
│ │
│ ▼
│ First interaction?
│ │ │
│ Yes No
│ │ │
│ ▼ ▼
│ First Node Last executed
│ Node
│ └──────┬───────┘
│ ▼
│ Executes Module
│ │
│ ▼
│ Updates INSTANCE
│ │
│ ▼
│ Records STEP
│ │
│ ▼
│ Analyzes return
│ │
│ ┌───────────┼────────────┐
│ │ │ │
│ ▼ ▼ ▼
│ Stay at Send Next
│ Node Response Node
│ │ │ │
│ │ ▼ ▼
│ │ Publishes Publishes
│ │ message flow.execute
│ │ to channel again
│ │
▼ ▼
End End
Each message received by the service contains all the information needed to locate the corresponding conversation:
This data is enough for the Flow Service to figure out exactly which point of the automation that conversation is currently at.
Each channel has an associated automation flow. This flow is stored as JSON, following a structure similar to React Flow, containing:
Since this flow rarely changes, it stays cached (REDIS), reducing database queries.
After locating the flow, the service checks whether that conversation has already started an automation. This information is obtained through the INSTANCE, responsible for storing the current execution state. The INSTANCE contains, for example:
Otherwise, execution continues exactly from the point where it left off.
While the INSTANCE represents the current state, the STEPS table logs the entire execution history. Each pass through a node generates a new record containing information such as:
This history makes auditing, debugging, and reprocessing easier.
The Flow Service doesn't know the implementation details of each node type. It simply locates the corresponding module and executes its standard interface. Each module is fully independent, following the principles of low coupling and high cohesion. Depending on the configuration received in the node's data field, the module may:
Regardless of the module type, its return always follows the same logical structure. It can indicate that:
This standardization makes the execution engine extremely simple and decoupled from the modules.
After executing the module, the Flow Service analyzes the return. There are two possible behaviors.
Some modules represent waiting states, such as waiting for a user response. In that case, the INSTANCE is updated and execution ends at that point.
When a new message arrives, processing will resume from that exact same node.
Message
↓
Node "Ask for Name"
↓
Waiting for response
↓
INSTANCE saved
↓
End
When the module indicates continuation, the Flow Service checks the flow's Edges to figure out which will be the next node. If the module itself specified a specific destination (for example, in a condition or Switch), that path takes priority.
After determining the next node, the INSTANCE is updated and a new message is published to the queue to continue execution.
Current Node
↓
Updates INSTANCE
↓
Finds next Edge
↓
Publishes again
↓
Flow Service continues execution
This model turns each automation step into a small unit of work, allowing processing to be distributed across multiple instances of the service without losing the context of each conversation.
Since each node has different behavior, a good approach is to use a modular architecture based on the Strategy pattern, where the Flow Service acts only as an orchestrator. All business rules are encapsulated within the modules responsible for each node type.
This organization reduces coupling, makes it easier to create new nodes, and avoids changes to the main engine whenever a new feature is added.
src/
├── application/
│ ├── consumers/
│ │ └── flow.consumer.ts
│ │
│ ├── services/
│ │ ├── flow.service.ts
│ │ ├── instance.service.ts
│ │ ├── step.service.ts
│ │ └── flow-cache.service.ts
│ │
│ └── dto/
│
├── domain/
│ ├── entities/
│ │ ├── flow.ts
│ │ ├── instance.ts
│ │ └── step.ts
│ │
│ ├── interfaces/
│ │ ├── node-module.ts
│ │ └── node-result.ts
│ │
│ └── repositories/
│
├── infrastructure/
│ ├── rabbitmq/
│ ├── redis/
│ ├── database/
│ └── api/
│
├── modules/
│ ├── if/
│ ├── switch/
│ ├── delay/
│ ├── http/
│ ├── database/
│ ├── send-message/
│ ├── update-attendance/
│ ├── calculate/
│ ├── webhook/
│ ├── ai/
│ └── ...
│
├── shared/
│ ├── logger/
│ ├── errors/
│ └── utils/
│
└── main.ts