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

# Five flow recipes: welcome series, reminders, support triage, surveys, order updates

> Copy-ready flow definitions for the five automations every customer asks for — welcome series, appointment reminders, support triage, survey collection, and order updates — each with a working JSON definition, the trigger to use, and the behavior to expect.

# Five flow recipes

Five working flow definitions you can paste into the Flows API today: a welcome series, an appointment reminder, support triage, survey collection, and order updates. Each recipe covers the trigger to pick, the full `definition` JSON, and what a run does.

The [Flows overview](/flows/overview) covers trigger types, node semantics, and edge handles; this page assumes those concepts and goes straight to definitions. [Build your first automation flow](/guides/build-first-flow) walks one flow design end to end — this page gives you the other five.

## 1. Choose the trigger

Every flow starts from exactly one trigger, declared top-level as `trigger_type`.

| Trigger                      | Use it for                                                                                          | API shape                                                                                                                                                                                    |
| ---------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Business Event** (`event`) | React to a platform event — a new contact, an inbound message, a completed call                     | `"trigger_type": "event"` with `definition.trigger.event` naming the event (`contact.created`, an inbound-message event, `call.completed`, …)                                                |
| **Schedule** (`schedule`)    | Batch work on a cadence — reminders, digests, re-checks                                             | `"trigger_type": "schedule"` plus top-level `cron_expr` (5-field cron) and optional `cron_tz` (IANA timezone, defaults to UTC). A missing or unparseable `cron_expr` is rejected with a 422. |
| **Webhook** (`webhook`)      | An external system (your storefront, booking app, helpdesk) starts the flow                         | `"trigger_type": "webhook"`; the caller POSTs free-form JSON to the flow's webhook URL and every posted key lands in the run context as a flat variable                                      |
| **Manual** (`manual`)        | Start runs from your own backend via the Start Flow / Execute call, or the dashboard **Run** action | `"trigger_type": "manual"`; call `POST /flows/:id/start` with `contact_id` and a `context` object                                                                                            |

Two golden rules before the recipes:

1. **Gate business-event triggers.** `contact.created` fires on API imports and CSV uploads too, not just your signup form. Put a Condition first, or a 50,000-row import tags along behind your welcome SMS.
2. **Variables are flat.** Write `{{first_name}}`, never `{{contact.first_name}}`. A placeholder for a missing key renders as an empty string — so a webhook flow referencing a key the caller didn't post sends an empty body. Validate with Test mode before publishing.

## 2. Recipe 1 — welcome series

**Shape:** contact created → gate → welcome SMS → wait 24h → follow-up email.

New contacts get an SMS immediately and an email the next day. The condition gate keeps imports and API batches from entering the series.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome series",
    "trigger_type": "event",
    "definition": {
      "trigger": { "type": "event", "event": "contact.created" },
      "nodes": [
        {
          "id": "gate_source",
          "type": "condition",
          "data": { "field": "source", "operator": "equals", "value": "signup_form" }
        },
        {
          "id": "welcome_sms",
          "type": "sendSms",
          "data": {
            "to": "{{phone}}",
            "body": "Welcome to Acme, {{first_name}}! Reply HELP for help, STOP to opt out."
          }
        },
        { "id": "wait_24h", "type": "delay", "data": { "amount": 24, "unit": "hours" } },
        {
          "id": "followup_email",
          "type": "sendEmail",
          "data": {
            "to": "{{email}}",
            "subject": "Getting started with Acme",
            "body": "Hi {{first_name}}, here are three things worth doing first."
          }
        }
      ],
      "edges": [
        { "source": "gate_source", "sourceHandle": "yes", "target": "welcome_sms" },
        { "source": "welcome_sms", "target": "wait_24h" },
        { "source": "wait_24h", "target": "followup_email" }
      ]
    }
  }'
```

**Expected behavior.** Each `contact.created` event starts one run. Runs where `source` is not `signup_form` exit at the gate (`no` has no target, so the run ends cleanly). The 24-hour delay parks the run in `waiting` status and resumes automatically — long waits never send early. The same series is available in the dashboard's **New Flow** template list under **Welcome SMS**; the API route below adds the import gate and the email step.

## 3. Recipe 2 — appointment reminder

**Shape:** booking webhook → confirm-or-reschedule at T-24h → final ping at T-1h.

Your booking system POSTs when an appointment is scheduled; the flow fires the webhook into `wait_24h` (a T-24 reminder, booked appointments get a confirmation request) and then the T-1 ping. Booking-time context carries `appointment_at` — the delay offsets are scheduled against it.

The two wait nodes are date-relative, so a booking made Monday for Wednesday lands the confirmation message Tuesday, not two days after booking.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Appointment reminder",
    "trigger_type": "webhook",
    "definition": {
      "trigger": { "type": "webhook", "event": "appointment.booked" },
      "nodes": [
        {
          "id": "check_optin",
          "type": "condition",
          "data": { "field": "sms_opt_in", "operator": "equals", "value": "true" }
        },
        {
          "id": "wait_t24",
          "type": "delay",
          "data": { "amount": -24, "unit": "hours", "relativeTo": "appointment_at" }
        },
        {
          "id": "reminder_t24",
          "type": "sendSms",
          "data": {
            "to": "{{phone}}",
            "body": "Hi {{first_name}} — your appointment is tomorrow at {{appointment_time}}. Reply C to confirm, or {{reschedule_link}} to change."
          }
        },
        {
          "id": "wait_t1",
          "type": "delay",
          "data": { "amount": -1, "unit": "hours", "relativeTo": "appointment_at" }
        },
        {
          "id": "reminder_t1",
          "type": "sendSms",
          "data": {
            "to": "{{phone}}",
            "body": "See you in an hour at {{location}}. Running late? Call {{office_phone}}."
          }
        }
      ],
      "edges": [
        { "source": "check_optin", "sourceHandle": "yes", "target": "wait_t24" },
        { "source": "wait_t24", "target": "reminder_t24" },
        { "source": "reminder_t24", "target": "wait_t1" },
        { "source": "wait_t1", "target": "reminder_t1" }
      ]
    }
  }'
```

**Expected behavior.** Your booking system POSTs `{ "appointment_at", "appointment_time", "first_name", "phone", "location", "reschedule_link", "sms_opt_in": "true", ... }` to the flow's webhook URL. Contacts without opt-in exit at the gate. The T-24 message carries the confirm/reschedule call-to-action; the T-1 message is a bare logistics ping on the channel the tenant's inbox monitors. The dashboard template **Appointment Reminder (24h + 1h)** is the same skeleton — clone it from **New Flow** if you'd rather click than curl.

## 4. Recipe 3 — support triage

**Shape:** inbound message → classify intent → AI agent answers → complaints escalate to a human.

Inbound SMS (or WhatsApp) hits an inbound-routing rule that starts the flow. An AI Classify node routes by intent; the agent handles routine questions; complaints fan out to your team's escalation webhook; anything unrecognized ends for manual pickup.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support triage",
    "trigger_type": "event",
    "definition": {
      "trigger": { "type": "event", "event": "message.inbound" },
      "nodes": [
        {
          "id": "classify",
          "type": "aiClassify",
          "data": { "intents": "question,billing,complaint,other" }
        },
        {
          "id": "support_agent",
          "type": "aiAgent",
          "data": { "agent_id": "agent_support_01" }
        },
        {
          "id": "billing_agent",
          "type": "aiAgent",
          "data": { "agent_id": "agent_billing_01" }
        },
        {
          "id": "escalate",
          "type": "webhook",
          "data": {
            "url": "https://your-app.example.com/hooks/support-escalation",
            "method": "POST"
          }
        }
      ],
      "edges": [
        { "source": "classify", "sourceHandle": "question", "target": "support_agent" },
        { "source": "classify", "sourceHandle": "billing", "target": "billing_agent" },
        { "source": "classify", "sourceHandle": "complaint", "target": "escalate" }
      ]
    }
  }'
```

**Expected behavior.** The classifier routes on the matching intent edge; `yes`/`no` handles exist as fallbacks — `other` and unrecognized classifications end the run when no edge carries them. The webhook node's body carries `execution_id` and the sanitized run context, so your helpdesk gets the message text and intent. Webhook URLs must be public; private or internal addresses are rejected. Provision the two agents first — `agent_id` pointing at a non-existent agent fails the run at that node (with an entry in the step trace), not a graceful fallback.

Routing start: wire the inbound-routing rule to this flow (see [Where Flows Fire](/flows/overview#where-flows-fire)).

## 5. Recipe 4 — survey collection

**Shape:** post-call event → wait → send the survey → responses arrive at your webhook.

After a call completes, wait a day, then send the NPS or CSAT survey over SMS. Recipients answer on the hosted page via the single-use token link in the message; each response fires a webhook event you capture at your endpoint.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Post-call NPS survey",
    "trigger_type": "event",
    "definition": {
      "trigger": { "type": "event", "event": "call.completed" },
      "nodes": [
        {
          "id": "check_consent",
          "type": "condition",
          "data": { "field": "survey_opt_out", "operator": "not_equals", "value": "true" }
        },
        { "id": "wait_24h", "type": "delay", "data": { "amount": 24, "unit": "hours" } },
        {
          "id": "send_nps",
          "type": "sendSurvey",
          "data": {
            "survey_id": "survey_nps_quarterly",
            "channel": "sms",
            "contact_id": "{{contact_id}}"
          }
        }
      ],
      "edges": [
        { "source": "check_consent", "sourceHandle": "yes", "target": "wait_24h" },
        { "source": "wait_24h", "target": "send_nps" }
      ]
    }
  }'
```

**Expected behavior.** `survey_id` references a survey you created in the Surveys API; `channel` accepts `sms`, `whatsapp`, `email`, `viber`, `rcs`, or `push`. A missing or unsupported value skips the node with a `skipped` output in the step trace — check it in Test mode, because a typo'd channel does not fail the run. Recipients receive the single-use survey link; responses land as `survey.nps.response_recorded` and `survey.csat.response_recorded` webhook events, and a low score additionally fires `survey.response.detractor`. Subscribe to those events (see [Webhook Events](/webhooks/events)) to close the loop — page a CSM on a detractor, for example.

## 6. Recipe 5 — order updates

**Shape:** commerce webhook → SMS + email fan-out.

Your commerce layer (Shopify, a custom storefront, an OMS) POSTs order events — confirmation, shipped, delivered, exception. The flow fans out on both channels. Draw a branch per event stage by chaining a condition on the `event` key, or run one flow per stage with its own trigger filter.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order delivered notification",
    "trigger_type": "webhook",
    "definition": {
      "trigger": { "type": "webhook", "event": "order.delivered" },
      "nodes": [
        {
          "id": "delivered_sms",
          "type": "sendSms",
          "data": {
            "to": "{{phone}}",
            "body": "Order {{order_id}} was delivered. Questions? Just reply to this text."
          }
        },
        {
          "id": "has_email",
          "type": "condition",
          "data": { "field": "email", "operator": "not_equals", "value": "" }
        },
        {
          "id": "delivered_email",
          "type": "sendEmail",
          "data": {
            "to": "{{email}}",
            "subject": "Delivered: order {{order_id}}",
            "body": "Hi {{first_name}}, order {{order_id}} shows as delivered. Rate your delivery experience: {{review_link}}"
          }
        }
      ],
      "edges": [
        { "source": "delivered_sms", "target": "has_email" },
        { "source": "has_email", "sourceHandle": "yes", "target": "delivered_email" }
      ]
    }
  }'
```

**Expected behavior.** The storefront POSTs the order event with flat keys (`order_id`, `first_name`, `phone`, `email`, `tracking_url`, `review_link`); both messages render those values inline. Every contact gets the SMS; the `has_email` gate skips the email step for phone-only contacts (an empty `email` resolves to an empty string, so the test is `email != ""`). When one flow covers several stages, chain conditions on the `event` key the storefront posts — `order.confirmed`, `order.shipped`, `order.delivered`.

## 7. Validate before launch

Three tools, in the order to use them:

**1. Structural validation.** Check the definition parses and has no obvious wiring mistakes before you test behavior:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/flows/flow_abc123/validate \
  -H "X-API-Key: dv_live_sk_..."
```

**2. Test mode and the simulator.** Click **Test** in the builder toolbar to walk the flow with sample data and watch the exact branch each node takes. Then, before any schedule or event trigger touches real traffic, dry-run the flow against your real audience size — no contact is enrolled, nothing is sent:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows/flow_abc123/simulate \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"audience_size": 10000, "default_country": "US"}'
```

The response projects the per-step funnel, the channel mix, the projected send cost at your live rates, and a predicted conversion. If the first step drops 90% of the audience, the gate is eating the traffic — fix the trigger filter before publishing.

**3. Analytics after launch.** Once runs accumulate, read the actual funnel:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/flows/flow_abc123/analytics" \
  -H "X-API-Key: dv_live_sk_..."
```

`GET /flows/:id/analytics` returns entered / completed / converted counts and the per-node drop-off funnel; `GET /flows/:id/ab-results` rolls up any `abTest` node variants with the leading variant. The simulator's assumed drop-off converges on this history as runs accumulate, so forecasts get sharper over time.

**Where to wire webhooks first.** A list of every webhook event the platform emits lives at [Webhook Events](/webhooks/events); subscribe at the endpoint in [Webhooks](/webhooks/overview) and verify the HMAC signature in [Webhook Security](/webhooks/security). For the surveys recipe, the survey-response events are the wire that closes the loop; for failures, `flow.execution.failed` pages you instead of a silent drop-off.

## Next steps

* [Flows overview](/flows/overview) — trigger catalog, node taxonomy, edge semantics
* [Build your first automation flow](/guides/build-first-flow) — the design walkthrough for recipe-style flows
* [Flow Builder](/flows/builder) — canvas reference and the template library (**New Flow** dialog)
* [Flow Executions](/flows/executions) — the step-trace reference for debugging runs
* [Webhook Events](/webhooks/events) — every event you can subscribe to
