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.text to email → 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:

FieldTypeDescription
conditionstringA Liquid expression that should evaluate to true or false
nextStatestringThe 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:

VariableWhat it gives you
dataResults from connector operations
paramsParameters from the API request or postback data
messageThe user's most recent message or action
inputValues captured from input assignments
userInformation 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:

  1. Run operations to check inventory
  2. Use a conditional transition to send an error message if out of stock
  3. 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.