Message routing
When building message flows, each message needs a way to determine which message the agent sends next. This is routing, and it is how the conversation moves forward.
There are three ways to determine which message gets sent next, and they can be combined within a single message.
Suggested replies
In RCS, suggested replies are tappable chips that appear below a message. Each suggested reply can carry data that tells the agent which Message to send next.
RCS has two types of suggestions: replies and actions. Suggested replies tell the agent which message to send next. Suggested actions trigger device behavior (open a URL, dial a phone number, share location), and do not send a new message.
Suggested replies can appear on text messages, rich cards, and carousel cards. When the user taps one, the agent sends the message specified in that reply's data.
Example:
welcome
- Template: "What would you like to do?" with suggested replies [Check Order] and [Browse Menu]
- User taps "Check Order" → agent sends check-order
- User taps "Browse Menu" → agent sends show-menu
This is the primary way to build flows in the UI.
Input routing
When a message collects a response from the user, it can store that response and send a configured next message automatically.
The user's most recent response is always available as message.text (what they typed or the label of the reply they tapped) via template variables. Input assignments let you extract and store values for use in the next message. For example, assigning the user's text to email makes it available as {{ input.email }}.
Example:
ask-email
- Template: "What's your email address?"
- Input: Assign
message.texttoemail→ send confirm-email
confirm-email
- Template: "Got it! We'll send updates to
{{ input.email }}."
For details on input assignments and validation, see Assigning Inputs.
Conditional transitions
Conditional transitions let your agent evaluate data and automatically send a different message without waiting for the user to respond. This is how you build branching logic: your agent processes data behind the scenes, decides which path to take, and the user only sees the resulting message.
Note: Conditional transitions are currently only available via the States API. UI support is coming soon.
How it works
Each message can have a transitions array. After the message's operations run, each transition's condition is evaluated in order. The first condition that evaluates to true wins, then the agent sends that message. If no transition matches, the current message renders its template normally.
Operations run → Evaluate transitions → Match found? → Send next Message (repeat)
→ No match? → Render current template, send message
Adding transitions
Add a transitions array to any message configuration. Each transition has two fields:
| Field | Type | Description |
|---|---|---|
condition | string | A Liquid expression that should evaluate to true or false |
nextState | string | The Message ID to send when the condition is true |
Here's a complete example showing operations, transitions, and a template together:
{
"operations": [
{
"connectorId": "inventory-api",
"operation": "checkStock",
"contextKey": "stock"
}
],
"transitions": [
{ "condition": "{{ data.stock.quantity == 0 }}", "nextState": "out-of-stock" },
{ "condition": "{{ data.stock.quantity < 5 }}", "nextState": "low-stock-warning" }
],
"template": {
"template": "{\"contentMessage\":{\"text\":\"We have {{ data.stock.quantity }} in stock!\"}}"
}
}In this example:
- If the inventory API returns
quantity: 0, the agent sends the out-of-stock Message - If it returns
quantity: 3, the agent sends low-stock-warning - If it returns
quantity: 50, no transition matches, so the current message sends "We have 50 in stock!"
Writing conditions
Conditions use the same Liquid template syntax as your message templates. The condition should render to the string true to match.
The simplest approach is an inline comparison:
{{ data.status == "approved" }}This renders to true or false directly. You have access to all the same variables as your templates:
| Variable | What it gives you |
|---|---|
data | Results from connector operations |
params | Parameters from the API request or postback data |
message | The user's most recent message or action |
input | Values captured from input assignments |
user | Information about the end user |
Comparison operators
{{ data.score > 80 }}
{{ data.tier == "premium" }}
{{ data.items.size > 0 }}
{{ message.text == "yes" }}Combining conditions
Use and / or to combine multiple checks:
{{ data.score > 80 and data.tier == "premium" }}
{{ data.status == "active" or data.status == "trial" }}Catch-all fallback
Use true as the condition for an always-take transition. This is useful as the last entry in a transitions array, as a default route:
{
"transitions": [
{ "condition": "{{ data.status == \"vip\" }}", "nextState": "vip-flow" },
{ "condition": "true", "nextState": "default-flow" }
]
}First match wins
Transitions are evaluated top to bottom. The first condition that renders to true is taken, and the rest are skipped. Order your transitions from most specific to least specific:
{
"transitions": [
{ "condition": "{{ data.age < 13 }}", "nextState": "child-plan" },
{ "condition": "{{ data.age < 18 }}", "nextState": "teen-plan" },
{ "condition": "true", "nextState": "adult-plan" }
]
}If age is 10, the first condition matches and the agent sends child-plan. It never evaluates the other conditions.
Chaining
Transitions can chain through multiple messages. Each intermediate message runs its own operations to fetch or transform data, and the results accumulate across the chain, so later messages can use data produced by earlier ones. The user only receives the message from the final message in the chain.
check-eligibility → (eligible?) → calculate-discount → (big spender?) → vip-offer
→ (not eligible?) → standard-offer
Limits
Transitions have a maximum chain depth of 10 hops. If your agent transitions through more than 10 messages without sending a template, it stops and returns an error. This prevents infinite loops from misconfigured transitions (like Message A sending Message B, which sends back to Message A).
If you're hitting this limit, your flow likely needs to be simplified.
Combining methods
A single message can use multiple routing methods. For example, a message might:
- Run operations to check inventory
- Use a conditional transition to send an error message if out of stock
- Render a template with suggested replies that let the user choose what happens next
The evaluation order matters: operations run first, then transitions evaluate, then the template renders with suggested replies. If a transition fires, the template is never sent, and the agent sends the transition's target Message instead.
Transitions are fully optional. Messages without a transitions array work exactly as before. Operations run, then the template renders and sends. You can add transitions to individual messages without affecting the rest of your agent.
Updated 1 day ago