flow.tier equals VIP
Product guide
Message Flows are ReplyBox automations for building WhatsApp journeys on a canvas. Phase 7C makes condition branching authorable in the editor while keeping the 7B runtime, backend validator, and persistence contract unchanged.
flow.tier equals VIP
Condition editor
contact.tags or flow.tier
Operatorequals, contains, is_set
ValueRequired unless the operator is presence-based.
A condition sends nothing and mutates nothing. It evaluates one field, stores the selected branch, and traversal follows that stored result.
What shipped
Types and serialization preserve branched graphs across load, save, reload, and save.
validateGraph and canConnect mirror backend resolve_graph.
The canvas renders true and false handles, branch labels, and invalid-state badges.
Deletes, inserts, undo/redo, and tree layout preserve branch labels without guessing.
Authors set field, operator, and value with a hybrid picker and branch guidance.
Backend round-trip and save-to-runtime tests prove no runtime or migration change is needed.
Core contract
The frontend blocks bad authoring early, but save validation still belongs to resolve_graph. When frontend and backend disagree, the backend wins.
source_handle stores true, false, or an empty linear handle.
Condition config{ field, operator, value } is persisted on the condition node.
Runtime resultresult_json stores the selected branch once per run.
No schema changeThe existing edge column carries all branch identity.
Validation rules
Every condition must have exactly one true and one false outgoing edge.
Non-condition nodes can have zero or one outgoing edge.
There must be one start node with in-degree 0, and every node must be reachable from it.
Loops are rejected. FLOW_RUN_MAX_STEPS is only runtime defense in depth.
Branches are currently a tree. Every node must have in-degree less than or equal to 1.
A condition needs a field and, for comparison operators, a non-empty value.
Authoring behavior
Adding a condition creates a real condition node. ReplyBox does not create placeholder branch stubs, does not silently reroute successors, and does not invent branch labels.
tail -> condition and leaves both branches for the author.
Middle insertIf the selected node already has an outgoing edge, the condition is dropped unconnected.
Delete behaviorOnly pure linear chains can heal. Branch-connected nodes and condition nodes never auto-heal.
LayoutTree layout places true left of false and centers parents over their children.
Persistence and runtime
The runtime evaluates a condition exactly once, persists { "branch": "true"|"false", "matched": bool }, and traversal consumes that stored branch. It never re-evaluates a previous condition step.
_replace_graph -> _to_detail
Round tripgraphSignature must stay stable.
E2E guardSave -> runtime proves 7C authoring works with 7B execution.
Current limits
Send ordering
awaiting_send gate keeps nodes in builder orderEnqueuing a connector send job is not the same as the message arriving on WhatsApp. The connector runs several send uploads in parallel and media uploads finish in upload-completion order, roughly by file size, so naive enqueue order can deliver messages out of the flow's builder order.
Message steps enter an explicit awaiting_send status after their send job is handed to the connector. The run is parked until the connector's terminal status webhook (sent, delivered, read, or failed) confirms the send, so only one send per run is in flight and WhatsApp receives the nodes in builder order.
Cancelling a run parked in awaiting_send does not recall or unsend the already in-flight message. Cancel stops the run from enqueueing subsequent nodes, and the gate's compare-and-set prevents a late status webhook from advancing the run after it commits cancelled.
A lost or late terminal webhook would otherwise freeze the run. A periodic sweep (FLOW_RUN_ACK_SWEEP_SECONDS) checks for stalled awaiting_send steps and releases those whose outbound rows are already settled or otherwise stranded. The timeout window (FLOW_RUN_SEND_ACK_TIMEOUT_SECONDS) is deliberately generous to avoid mid-album timeouts that would reorder attachments.
Transports with album uploads (Baileys / WhatsApp web) ship one node's attachments as a single album job, so the handler loops items in one worker slot and intra-node order is preserved. Cloud API has no single-album job and uses per-message paths; the gate is what preserves inter-node ordering across that difference.
Operational notes
Aborting an awaiting_send step only affects the run: the outbound send is independent and still reports status. If a run looks frozen and a lost “sent” webhook is suspected, check the ack-sweep logs and the stuck-send diagnostics. The system recovers automatically once the sweep or a resumed webhook releases the gate.
Saved flows in IQ
Staff can search eligible Message Flows from the IQ reply composer and preview a current-source, exact match of a saved flow before approving it to send.
It creates no outbound rows, jobs, or messages. Preview preserves the flow's original item order (text and media) and binds a server-verified flow_reference that includes organization and channel scope, semantic revision, ordered nodes, draft segment ids, and approved media hashes.
Approving a preview attaches the flow_reference to the IQ v4 approval, and the server re-checks flow eligibility, source bytes, and revision before authorizing the send. The composer locks the selected saved sequence while previewed and offers an explicit switch back to manual drafting.
Unsupported flow features (delays, branches, variables, extra-media nodes) remain ignored, and sequences above eight transport items are not allowed — that limit is a transport implementation limit. Existing v2/v3/v4 approvals without a flow_reference keep the legacy wire format.
The table message_flow_channels (Alembic migration 0149_message_flow_channels) tags a flow as belonging to a channel. A flow is eligible for a channel when it is tagged there, or when it has actually run on a conversation of that channel (derived from message_flow_runs). Migration 0149 is strictly additive with no backfill, and the staff-facing tagging UI is a follow-up, so the table is the server-side escape hatch until that control ships.