> ## Documentation Index
> Fetch the complete documentation index at: https://docs.textyess.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Steps

> Every node between the trigger and the end is a step. Steps come in two flavours — actions that do something in the world, and control nodes that decide where the flow goes next.

A **step** is a single node on the canvas. Drop one between the trigger and an end node, connect the arrows, and it becomes part of the flow.

<Frame>
  <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-palette.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=a76ce75467a4ee99801afe889d3cf1d0" alt="The step palette showing every available step type you can drag onto the canvas" width="299" height="740" data-path="images/flows/step-palette.jpeg" />
</Frame>

Steps fall into two categories:

<CardGroup cols={2}>
  <Card title="Action steps" icon="bolt">
    They have side effects — send a message, tag a contact, call a webhook. If they fail, they retry. If retries are exhausted, they take an optional error path.
  </Card>

  <Card title="Control steps" icon="diagram-project">
    They shape the flow — delay, branch, wait for a reply, look up history. No side effects, just routing.
  </Card>
</CardGroup>

## At a glance

| Step                             | Type    | What it does                                                         |
| -------------------------------- | ------- | -------------------------------------------------------------------- |
| **Send Message**                 | Action  | Send an approved WhatsApp template message                           |
| **Send Dynamic Message (AI)**    | Action  | Generate the message body with AI on a pre-approved dynamic template |
| **Time Delay**                   | Control | Pause for a duration (minutes, hours, days)                          |
| **Conditional Split**            | Control | Branch True / False on a condition                                   |
| **Wait for Reply**               | Control | Suspend until the contact texts back (or times out)                  |
| **Add Tag**                      | Action  | Tag a contact or order                                               |
| **Add Note to Contact**          | Action  | Append a note to the contact profile                                 |
| **Add Note to Order on Shopify** | Action  | Append a note to a Shopify order                                     |
| **Send Webhook**                 | Action  | POST/GET/PUT/DELETE/PATCH to an external URL                         |
| **Remove from Blacklist**        | Action  | Un-blacklist the contact (e.g. after they reply YES)                 |
| **No-Op (End)**                  | Control | Terminal node — the flow ends here                                   |

## Action steps

<AccordionGroup>
  <Accordion title="Send Message" icon="comment-dots">
    Sends a WhatsApp message using a template. This is the workhorse of the platform.

    <Frame>
      <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-send-message-config.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=2bd698da754ac88d44864b00622931bc" alt="The Send Message step configuration panel" width="1412" height="846" data-path="images/flows/step-send-message-config.jpeg" />
    </Frame>

    **What you configure:**

    * **Template** — pick a previously-approved template or create a new one inline (it will go through Meta approval at publish time).
    * **Template category** — `UTILITY` or `MARKETING`. This drives both Meta's review and the frequency cap (see [Frequency cap](/flows/frequency-cap)). New send steps default to **UTILITY** in the UI.
    * **Language** — the template language (must match the contact's country).
    * **Variables** — values for any `{{1}}`, `{{2}}` placeholders in your template. Each can be a literal, a memory reference (e.g. `order.customer.firstName`), or a computed expression.
    * **Buttons** *(optional)* — quick reply buttons. When configured, the step **automatically waits for the contact's reply** and branches by button label. Add a timeout and a timeout branch as you would for a wait.
    * **Media header** *(optional)* — image, video, or document. Drop a URL or pick a stored asset.
    * **Discount** *(optional)* — let TextYess reserve a unique discount code from a campaign you've set up.
    * **Stop button** *(optional)* — append a localised opt-out button so contacts can leave with a tap.

    **Two shapes:**

    * **Shape 1 — no buttons.** Sends and moves to the next step immediately. Pair with the **Wait for Reply** control step if you want to wait for a text answer.
    * **Shape 2 — with quick-reply buttons.** Sends, waits for the contact, and branches by button. Each button is an output handle on the canvas, plus a `timeout` branch. Do not pair with Wait for Reply — Shape 2 already includes the wait.

    <Frame>
      <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-send-message-buttons.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=beb32dec7ebdd0d98efae54251551da6" alt="A Send Message step with quick-reply buttons configured, with one branch per button visible on the canvas" width="1412" height="846" data-path="images/flows/step-send-message-buttons.jpeg" />
    </Frame>

    **Memory written:**

    | Field                     | When                                                                                                                            |
    | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
    | `<stepId>.messageId`      | After a successful send                                                                                                         |
    | `<stepId>.reply.type`     | After a button reply: `"button"`. After a text reply: `"text"`. After timeout: `"timeout"`. If skipped by the cap: `"skipped"`. |
    | `<stepId>.reply.buttonId` | After a button reply                                                                                                            |
    | `<stepId>.reply.text`     | After a text or button reply                                                                                                    |
    | `<stepId>.reply.reason`   | When skipped — e.g. `"frequency_cap"`, `"hard_ceiling"`                                                                         |
  </Accordion>

  <Accordion title="Send Dynamic Message (AI)" icon="wand-magic-sparkles">
    Generates the message body at runtime using the **brain0** AI engine, then sends it on a pre-approved **dynamic template**.

    Use this when you want personalised copy on every send instead of the same template body for everyone — and you don't want to manage one Meta-approved template per variation.

    <Frame>
      <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-send-dynamic-message.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=b35de27d56def49b2f0f9a466c5ba6b2" alt="The Send Dynamic Message (AI) step configuration with prompt and custom rules" width="1412" height="846" data-path="images/flows/step-send-dynamic-message.jpeg" />
    </Frame>

    **What you configure:**

    * **Template category** — same two options as Send Message (`UTILITY` or `MARKETING`); defaults to **UTILITY**.
    * **Prompt** — natural-language instructions for the AI (e.g. "Greet the customer by name and recommend our newest collection in a warm, conversational tone").
    * **Custom rules** *(optional)* — extra constraints (max length, must include a specific phrase, etc.).
    * **Language** — the template language.
    * **Discount + copy-code button** *(optional)* — same as Send Message. The copy-code source can be a reserved discount code or a static value.

    <Note>
      The dynamic template itself is submitted to Meta for approval the first time you publish a flow that uses one. The category you pick is the **submission intent**; Meta's final decision is what gates the frequency cap.
    </Note>

    **Memory written:**

    | Field                    | When                                                                         |
    | ------------------------ | ---------------------------------------------------------------------------- |
    | `<stepId>.messageId`     | After a successful send                                                      |
    | `<stepId>.generatedText` | The AI-generated body — useful for logging and "what did the AI say?" audits |
  </Accordion>

  <Accordion title="Add Tag" icon="tag">
    Adds a tag to a contact or to an order. Tags drive segmentation in TextYess and (for Shopify orders) sync back to your store.

    **What you configure:**

    * **Entity** — `contact` (default) or `order`.
    * **Entity ID** — leave empty to tag the current execution's contact / triggering order. Override only for advanced use cases.
    * **Tag** — the tag string (e.g. `vip`, `responded-to-welcome`).
  </Accordion>

  <Accordion title="Add Note to Order on Shopify" icon="note-sticky">
    Appends a free-text note to the triggering Shopify order. Useful for flagging orders your support team should look at ("waited 24h for reply, no response").

    **What you configure:**

    * **Note** — the text to append. Supports variables.
    * **Order ID** *(optional)* — defaults to the triggering order.

    <Warning>
      Requires an active Shopify connection. Currently a Shopify-only action.
    </Warning>
  </Accordion>

  <Accordion title="Add Note to Contact" icon="user-pen">
    Appends a free-text note to the contact's profile in TextYess. Visible in the conversation panel.

    **What you configure:**

    * **Note** — the text to append. Supports variables.
  </Accordion>

  <Accordion title="Send Webhook" icon="webhook">
    Makes an outbound HTTP request to an external URL. Use to notify your CRM, Slack, Zapier, internal tooling, or any system that can accept HTTP.

    **What you configure:**

    * **URL** — the destination. Can be built from variables (e.g. `concat("https://crm.example.com/leads/", contact.id)`).
    * **Method** — `POST` (default), `GET`, `PUT`, `PATCH`, `DELETE`.
    * **Headers** *(optional)* — JSON object of headers.
    * **Body** *(optional)* — the request body. Usually JSON; supports variables.

    **Memory written:**

    | Field                 | When                        |
    | --------------------- | --------------------------- |
    | `<stepId>.statusCode` | After the request completes |

    **Errors and retries:** Send Webhook retries on 5xx and network errors with exponential backoff. Non-success responses (4xx) take the error path if you've connected one.
  </Accordion>

  <Accordion title="Remove from Blacklist" icon="check-double">
    Removes the current contact from the marketing blacklist so they can receive future marketing messages again. Use after a contact explicitly opts back in.

    **What you configure:** Nothing — it operates on the current contact.

    <Warning>
      Only use this when the contact has clearly consented (e.g. they typed `START` in response to your opt-in flow). Re-engaging blacklisted contacts without consent puts your WhatsApp account at risk.
    </Warning>
  </Accordion>
</AccordionGroup>

## Control steps

<AccordionGroup>
  <Accordion title="Time Delay" icon="clock">
    Pauses the flow for a fixed duration, then moves on. Durable — survives restarts.

    <Frame>
      <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-time-delay.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=011031375e38a1db33bc2d5b50e68b29" alt="The Time Delay step configuration with duration and unit fields" width="1412" height="846" data-path="images/flows/step-time-delay.jpeg" />
    </Frame>

    **What you configure:**

    * **Duration** — a number.
    * **Unit** — minutes, hours, days.

    **Common values:**

    | Goal                                                      | Duration   |
    | --------------------------------------------------------- | ---------- |
    | Allow a contact a few minutes to check out before nudging | 30 minutes |
    | Wait until tomorrow before a follow-up                    | 24 hours   |
    | Request a review a week after fulfilment                  | 7 days     |

    <Tip>
      In **test mode**, delays are automatically capped to a few seconds so you don't wait 24 hours to validate your flow.
    </Tip>
  </Accordion>

  <Accordion title="Conditional Split" icon="code-branch">
    Branches the flow into a True path and a False path based on a condition.

    Conditions can be a **preset** (e.g. "has placed an order in the last 30 days") or a **custom expression** (e.g. `order.total > 100 and contact.tags contains "vip"`).

    Two output handles: `true` and `false`. Either can be unconnected — the unconnected side ends the flow.

    See [Conditions](/flows/conditions) for the full grammar.

    <Frame>
      <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-conditional-split.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=90a0c46ff4d190f1bc5ebedc1d550430" alt="The Conditional Split step with the preset picker open" width="1412" height="846" data-path="images/flows/step-conditional-split.jpeg" />
    </Frame>
  </Accordion>

  <Accordion title="Wait for Reply" icon="comment-dots">
    Suspends the flow until the contact sends a **free-text reply**, then branches `replied` or `timeout`.

    <Frame>
      <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-wait-for-reply.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=5396fb0cc8712e3f31c8008e8d1207f8" alt="The Wait for Reply step configuration showing the timeout and the two branches" width="1461" height="846" data-path="images/flows/step-wait-for-reply.jpeg" />
    </Frame>

    **What you configure:**

    * **Timeout** — e.g. `24 hours`. If the contact doesn't reply by then, the flow follows the `timeout` branch.

    **Memory written:**

    | Field                 | When                                         |
    | --------------------- | -------------------------------------------- |
    | `<stepId>.reply.type` | `"text"` if they replied, `"timeout"` if not |
    | `<stepId>.reply.text` | The text they sent (when `type === "text"`)  |

    <Warning>
      Use **Wait for Reply** only after a Send Message **without buttons** (Shape 1). If your Send Message already has quick-reply buttons (Shape 2), the wait is built in — adding a separate Wait for Reply will be rejected by the flow editor.
    </Warning>
  </Accordion>

  <Accordion title="No-Op (End)" icon="circle-stop">
    Terminal — the flow ends here. You rarely need to drop one yourself; the editor inserts a No-Op automatically wherever a branch is left unconnected, so you can leave the "False" handle of a Conditional Split dangling and the flow will simply end on that path.
  </Accordion>
</AccordionGroup>

## Error handling: the failure path

Every **action step** has an optional **failure** output (a red handle in the canvas). When the action exhausts its retries, the flow takes that path instead of failing the whole execution.

```
[Send Webhook]──success──→ [Send Message: "Synced ✅"]
       │
       └──failure────────→ [Add Note to Contact: "Sync failed, manual review needed"]
```

If you leave the failure handle unconnected, an unrecoverable error **halts the execution** with status `failed`.

<Frame>
  <img src="https://mintcdn.com/textyess/5tjaRU7jMjYHGKQZ/images/flows/step-failure-path.jpeg?fit=max&auto=format&n=5tjaRU7jMjYHGKQZ&q=85&s=82107a212a23ef4fe47e27a8873c9962" alt="A canvas zoom showing an action step with its failure handle connected to a follow-up step" width="391" height="198" data-path="images/flows/step-failure-path.jpeg" />
</Frame>

<Tip>
  For critical flows, always connect the failure handle to at least an **Add Note to Contact** so you have a record of what went wrong.
</Tip>

## Variables: how steps share data

Each step writes its result to a shared **memory** object under its own step id. Later steps can read from it.

Example: a Send Message step with id `welcome_msg_1` whose contact tapped a quick-reply button writes `{ messageId: "wamid.xxx", reply: { type: "button", buttonId: "yes", text: "Yes please" } }`. A downstream Send Message template can reference the reply text as:

| Where                         | What to type                          |
| ----------------------------- | ------------------------------------- |
| Variable picker (recommended) | Select `Welcome Message → Reply text` |
| Custom expression             | `welcome_msg_1.reply.text`            |
| Template variable `{{1}}`     | `welcome_msg_1.reply.text`            |

The variable picker in the editor lists every readable field from the trigger and every previous step, so you almost never type these paths by hand.

Next: [Conditions](/flows/conditions) — how to express branching logic without writing code.
