<!-- Generated from the canonical Teloring documentation source. Do not edit this file. -->

Canonical page: https://docs.teloring.com/docs/product/studio/examples
Last modified: 2026-08-20T20:49:57.000Z

# Flow recipes

Complete, working flows you can build in a few minutes each. Every recipe lists the blocks, their settings, and the reasoning behind them.

:::tip
Every recipe below can also be described to [Hermes](./ai-flow-builder.md) in a sentence — *"qualify a new WhatsApp lead and send them to sales or support"* — and built for you. The recipes are still worth reading: they show what a good flow looks like, which makes it easy to check the AI's work.
:::

| Recipe | Trigger | Good for |
| --- | --- | --- |
| [Instant welcome reply](#1-instant-welcome-reply) | Incoming Message | Any new inbox |
| [After-hours auto-reply](#2-after-hours-auto-reply) | Incoming Message | Teams with fixed hours |
| [Qualify and route a new lead](#3-qualify-and-route-a-new-lead) | Incoming Message | Sales and support split |
| [Escalate an unhappy customer](#4-escalate-an-unhappy-customer) | Conversation Changed | Retention |
| [Push a new lead into an external CRM](#5-push-a-new-lead-into-an-external-crm) | Form filled | Integrations |
| [Start a conversation from an external system](#6-start-a-conversation-from-an-external-system) | Incoming webhook | Order and delivery updates |
| [Daily morning summary](#7-daily-morning-summary) | Scheduled Time | Managers |
| [Alert a manager when a metric slips](#8-alert-a-manager-when-a-metric-slips) | Analytics Alert | Operations |
| [Phone menu with agent transfer](#9-phone-menu-with-agent-transfer) | Incoming Call | Voice |

---

## 1. Instant welcome reply

Answer within a second, so nobody waits in silence.

```text
Incoming Message  (Inboxes: WhatsApp)
   └─ Reply Message
        └─ End Session
```

| Block | Settings |
| --- | --- |
| **Incoming Message** | Tick your WhatsApp inbox. |
| **Reply Message** | `Hi {{contact.name}}! Thanks for contacting Acme. One of our team will be with you shortly.` · Wait for client response: **off** |
| **End Session** | Resolve the conversation: **off** |

**Why End Session?** Without it, Studio keeps owning the conversation and it sits in the Studio Bot queue. End Session releases it so it appears in **Waiting in line** for an agent to pick up.

:::tip
`{{contact.name}}` is empty for a first-time contact on some channels. Either write a greeting that reads well without it, or branch on `contact.name` with the **exists** operator.
:::

---

## 2. After-hours auto-reply

Say something useful when you are closed, and stay quiet when you are open.

```text
Incoming Message
   └─ Business Hours
        ├─ Open ──── (nothing — let agents handle it)
        └─ Closed ── Reply Message ── End Session
```

| Block | Settings |
| --- | --- |
| **Incoming Message** | Leave inboxes empty to cover every channel. |
| **Business Hours** | Pick the schedule you created in Settings → Business Hours. |
| **Reply Message** (Closed) | `Thanks for your message! Our team is offline right now. We're open Sunday–Thursday, 09:00–18:00, and will reply first thing.` |
| **End Session** | Resolve: **off** |

Leaving the **Open** port unconnected is deliberate — during working hours the flow should do nothing at all and let agents reply normally.

---

## 3. Qualify and route a new lead

Ask one question, then send the conversation to the right team with the right label.

```text
Incoming Message
   └─ Reply Message  (wait for reply)
        └─ Condition If/Else
             ├─ Sales ───── Change Conversation (label: sales, hand over to queue)
             ├─ Support ─── Change Conversation (label: support, hand over to queue)
             └─ Else ────── Reply Message "Sorry, I didn't catch that…"
```

| Block | Settings |
| --- | --- |
| **Reply Message** | `Hi! Are you contacting us about **sales** or **support**? Just reply with one word.` · Wait for client response: **on** · Wait timeout: `60` |
| **Condition If/Else** | Match mode: **First match only** · Add ELSE branch: **on** |
| — IF 1 | Label `Sales` · Value `message.content` · Operator **contains** · Compare as Text · Compare to `sales` |
| — IF 2 | Label `Support` · Value `message.content` · Operator **contains** · Compare as Text · Compare to `support` |
| **Change Conversation** (Sales) | Labels mode: Add · Labels: `sales` · Human intervention: **on** · Hand over to: **Waiting in line** |
| **Change Conversation** (Support) | Same, with the `support` label. |
| **Reply Message** (Else) | `Sorry, I didn't catch that. Please reply with "sales" or "support".` · Wait for client response: **on** |

**Why `contains` and not `equals`?** People type *"sales please"* and *"I need SALES"*. `contains` is not case-sensitive and matches inside a sentence.

**The ELSE loop.** The ELSE branch asks again and waits again, so the customer gets another chance. Consider adding a **Save as Variable** counter, or a third strike that hands over to a human, so a confused customer is never stuck.

---

## 4. Escalate an unhappy customer

React the moment AI detects frustration.

```text
Conversation Changed  (AI Signals: Feelings / emotion detected)
   └─ Condition If/Else
        └─ Negative ── Change Conversation (priority: urgent, flag: important)
                          └─ Private Note
                               └─ Send Email
```

| Block | Settings |
| --- | --- |
| **Conversation Changed** | AI Signals: **Feelings / emotion detected**. Leave the other filters on *any*. |
| **Condition If/Else** | IF `Negative` · Value `conversation.customer_emotion` · Operator **less than** · Compare as Number · Compare to `3` |
| **Change Conversation** | Priority: **Urgent** · Flag: **Important** · Human intervention: **on** · Hand over to: **Waiting in line** |
| **Private Note** | `⚠️ AI detected negative sentiment ({{conversation.customer_emotion}}). Signal: {{ai.message}}` |
| **Send Email** | To your team lead · Subject `Escalation: {{contact.name}}` · Body includes `{{conversation.id}}` and `{{ai.message}}` |

---

## 5. Push a new lead into an external CRM

Send every form submission straight to another system, and record the result.

```text
Form filled  (Form: Contact us)
   └─ HTTP Request  (capture response: on)
        └─ Condition If/Else
             ├─ Success ── Save as Variable ── Private Note
             └─ Else ───── Send Email (alert the admin)
```

| Block | Settings |
| --- | --- |
| **Form filled** | Pick the specific form. |
| **HTTP Request** | Method **POST** · URL your CRM endpoint · Header `Content-Type: application/json` · Body type **JSON** · Capture response **on** · Timeout `15` |
| **Condition If/Else** | IF `Success` · Value `http.ok` · Operator **equals** · Compare as Boolean · Compare to `true` |
| **Save as Variable** | Key `var.{{contact.id}}.crm_id` · Value `{{http.json.id}}` · Type Text |
| **Private Note** | `Lead synced to CRM as {{http.json.id}}.` |
| **Send Email** (Else) | To your admin · Body includes `{{http.status_code}}` and `{{http.error}}` |

JSON body:

```json
{
  "name": "{{answers.full_name}}",
  "email": "{{answers.email}}",
  "phone": "{{answers.phone}}",
  "source": "{{sourceParams.utm_source}}",
  "answers": {{answers.list}},
  "teloring_contact_id": "{{contact.id}}",
  "teloring_execution_id": "{{system.execution_id}}"
}
```

**Note the unquoted `{{answers.list}}`.** Because the variable fills a whole value, Studio inserts the real array instead of a string. See [Variables in a JSON body](./variables.md#variables-in-a-json-body).

---

## 6. Start a conversation from an external system

Let your order platform tell a customer their delivery is on the way.

```text
Incoming webhook
   └─ Reply Message
        └─ Contact Update
```

| Block | Settings |
| --- | --- |
| **Incoming webhook** | Copy the URL · Allowed methods **POST** · Learn next request **on**, then send one real request. |
| **Reply Message** | Conversation ID `{{webhook.body.conversation_id}}` · Message `Good news {{contact.name}} — order {{webhook.body.order_id}} is out for delivery. Track it here: {{webhook.body.tracking_url}}` |
| **Contact Update** | Field `last_order_status` → `{{webhook.body.status}}` |

**Getting the conversation ID.** A webhook flow has no conversation of its own, so `{{conversation.id}}` is empty. Either have the caller send a conversation ID in the payload, as above, or look it up with an HTTP Request first.

---

## 7. Daily morning summary

A private note in a chosen conversation, or an email, every working morning.

```text
Scheduled Time  (Sun–Thu, 08:30, Asia/Jerusalem)
   └─ HTTP Request  (capture response: on)
        └─ Send Email
```

| Block | Settings |
| --- | --- |
| **Scheduled Time** | Five rows: Sunday–Thursday, hour `8`, minute `30`, timezone `Asia/Jerusalem`. |
| **HTTP Request** | Fetch whatever numbers you want to report. Capture response **on**. |
| **Send Email** | To the team · Subject `Morning summary — {{datetime.result}}` · Body built from `{{http.json...}}` values |

Add a **Date and Time** block before the email (operation **Format date**, format `%d/%m/%Y`) if you want a friendly date in the subject.

---

## 8. Alert a manager when a metric slips

Wire an Analytics alert to a real notification, and let people know when it recovers.

```text
Analytics Alert  (Alerts: "Waiting conversations over 20")
   ├─ Alert ────── Send Email ── Change Conversation (priority: urgent)
   └─ Recovered ── Send Email
```

| Block | Settings |
| --- | --- |
| **Analytics Alert** | Tick the alert you created in Analytics. |
| **Send Email** (Alert) | Subject `🔴 {{alert.name}}` · Body `{{alert.report_title}} is {{alert.current_value}} ({{alert.direction}} the threshold of {{alert.threshold}}) as of {{alert.crossed_at}}.` |
| **Send Email** (Recovered) | Subject `🟢 {{alert.name}} recovered` · Body `{{alert.report_title}} is back to {{alert.current_value}}.` |

The alert fires **once** per crossing, so you get one email — not one per minute while the number stays high.

---

## 9. Phone menu with agent transfer

A complete inbound call experience.

```text
Incoming Call  (record: on)
   └─ Play Sound  "Welcome to Acme. This call may be recorded."
        └─ Business Hours
             ├─ Open ──── IVR Menu
             │              ├─ 1 · Sales ──── Agent Availability
             │              │                    ├─ Online ── Forward to Agent ──┬─ Completed ─ HTTP Request
             │              │                    │                               └─ Rejected ── Play Sound ─ Hang Up
             │              │                    └─ Offline ─ Play Sound ─ Hang Up
             │              ├─ 2 · Support ── Forward to External Phone
             │              ├─ Timeout ────── Play Sound ─ Hang Up
             │              └─ Invalid ────── Play Sound ─ Hang Up
             └─ Closed ── Play Sound ─ Hang Up
```

| Block | Settings |
| --- | --- |
| **Incoming Call** | Voice inbox: your number · Record this call: **on** |
| **Play Sound** (greeting) | WAV that includes the recording announcement. |
| **Business Hours** | Your schedule. |
| **IVR Menu** | Menu prompt WAV · Digits `1 · Sales`, `2 · Support` · Input timeout `7000` · Max attempts `2` |
| **Agent Availability** | Check **Specific agents** → your sales team · Consider online when **At least one** |
| **Forward to Agent** | Routing **Any available agent** · Ring timeout `30` |
| **HTTP Request** (Completed) | Log `{{call.duration_seconds}}`, `{{call.talk_duration_seconds}}`, `{{call.agent_name}}` to your system. |
| **Forward to External Phone** | Your support line, in international form. **Bills credits.** |
| **Hang Up** | Cause **Normal clearing** |

See [Voice call flows](./voice-flows.md) for the full block reference.

---

## Patterns worth reusing

| Pattern | How |
| --- | --- |
| **Always give people an exit** | Every waiting flow needs an ELSE branch or a handover, so a customer is never trapped. |
| **Label before you hand over** | A conversation that arrives in the queue already labelled and prioritized saves the agent a step. |
| **Leave a note, not a mystery** | A Private Note explaining what the bot collected is worth more to an agent than a clean timeline. |
| **Log the IDs** | Send `{{conversation.id}}`, `{{contact.id}}`, and `{{system.execution_id}}` to any external system you call. |
| **Sticky-note your branches** | Six months later, a sticky note explaining *why* a branch exists is the difference between improving a flow and rebuilding it. |
| **One question per message** | Two questions get one answer, and your condition has nothing clean to match. |

## Next

- [Troubleshooting](./troubleshooting.md) — when a recipe does not behave.
- [Testing & publishing](./publishing.md) — the safe way to release a flow.
