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

# Build for real conversations

> Handle consent, corrections, retries, delays and people returning later.

An agent that works in a demo meets new problems the day real people write to it. None of them are about the model. Each section below says what happens, what [Flow Messaging](https://docs.flow.engineer/unified-messaging-api/quickstart.md) does for you, and what you must do yourself if your agent talks through another channel.

## Consent and STOP

You may not message just anyone. In the US, a business may text first only someone who agreed to it, and anyone can text STOP to stop all messages. WhatsApp needs the person's opt-in, and lets you write freely only within 24 hours of their last message. On Telegram and iMessage the person writes first (on iMessage, unless writing first is switched on for a dedicated line).

People also write STOP, "stop texting me" or "unsubscribe" in the middle of a conversation. Your agent must not answer a STOP with a cheerful reply.

* **On Flow Messaging:** the [send gate](https://docs.flow.engineer/unified-messaging-api/concepts/send-gate.md) applies each channel's rule to every send. On SMS, a start needs the contact's recorded `consent` unless they texted first; STOP (and STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE, OPT OUT, OPTOUT) opts the contact out of all your SMS numbers until they text START, and Flow sends the keyword reply itself. You still receive the message; a send to an opted-out contact answers `403` [`permission`](https://docs.flow.engineer/unified-messaging-api/errors/permission) with `contact_opted_out`. Do not retry it.
* **On another channel:** your SMS provider may block sends after STOP, but check what it does and which words it treats as STOP. Record consent before you text anyone first. Make your agent recognise STOP and its variants, send nothing more, and record the opt-out where every part of your system that sends can see it.

## Quiet hours

A text at 3 a.m. wakes people up. Messages you start (reminders, follow-ups) should wait for the daytime in the person's time zone. A reply to someone who just wrote to you is fine at any hour.

* **On Flow Messaging:** on SMS, a send to a contact who has not texted in the last hour respects the sender's quiet hours (by default 21:00 to 08:00 in the contact's time zone, worked out from the area code). Inside them it answers `409` [`outside_window`](https://docs.flow.engineer/unified-messaging-api/errors/outside_window) with `quiet_hours` and `retry_after`, and a queued message is held until morning instead of sent at night.
* **On another channel:** keep a time zone per person (from their area code, or ask), and check it in every job that sends without being asked. Check the rules that apply to your messages and your users.

## Message length and splitting

A long, formatted answer reads well in a web chat and badly on a phone. One SMS holds 160 plain characters, or 70 if the text has any emoji or non-Latin letter; a longer text goes as several segments, each billed, and some phones show them out of order. Markdown is shown as raw asterisks and pound signs on SMS. On chat apps, one wall of text is harder to read than two or three short messages.

* **On Flow Messaging:** `reply()` splits your model's text into chat-sized bubbles at paragraph and sentence ends. Flow sends text exactly as written, Markdown marks included, so ask the model for plain text (or style it with a channel's own block, such as `telegram.parse_mode`). See [Streaming replies](https://docs.flow.engineer/unified-messaging-api/concepts/streaming-replies.md). On SMS a text is capped at 1530 characters and 10 segments.
* **On another channel:** ask the model for short answers, strip Markdown for SMS, and split long answers at paragraph or sentence ends yourself (the [bubble rule](https://docs.flow.engineer/unified-messaging-api/concepts/streaming-replies.md#the-bubble-rule-for-any-language) works for any channel). If you send Observe [traces](https://docs.flow.engineer/unified-messaging-api/observe/traces.md), report each part you actually sent as its own `flow.agent_reply`.

## Duplicates and retries

The same message can reach your code twice: webhooks are delivered at least once, providers retry when your server answers slowly, and people send the same thing again when no reply comes. Your send can be retried too. Without care, the person gets two answers, or the same answer twice.

* **On Flow Messaging:** deduplicate on the event's `id` (see [delivery rules](https://docs.flow.engineer/unified-messaging-api/concepts/events-and-webhooks.md#delivery-rules)), and send every reply with an `Idempotency-Key` built from it, so a retried send never sends twice. See [Idempotency](https://docs.flow.engineer/unified-messaging-api/concepts/idempotency.md).
* **On another channel:** deduplicate on your provider's message id before you run the agent, and use your provider's idempotency feature on sends if it has one. Answer your provider's webhook at once and run the agent afterwards, so a slow model does not trigger a retry. Set `flow.message_id` on your traces, so Observe stores the person's message once even when your code ran twice (it still records every reply you actually sent).

## People who come back days later

People answer a question three days later with "yes", or start a new topic in an old thread. Your agent needs the history to know what "yes" means, and it must not treat old context as current ("your order is on its way" from last week).

Observe counts a **conversation** as one run of messages: after 48 hours with no messages, the next one starts a new conversation. Billing counts the same way.

* **On Flow Messaging:** one person on one sender is one [conversation](https://docs.flow.engineer/unified-messaging-api/concepts/conversations.md) object, however long the gap. Load the recent history with `GET /v0/conversations/{conversation_id}/messages`, and check message times when context may be stale. On WhatsApp, more than 24 hours after the person's last message you can send only a [template](https://docs.flow.engineer/unified-messaging-api/guides/whatsapp-agent.md#5-the-24-hour-window-and-templates).
* **On another channel:** keep your own history per thread (for SMS, per pair of your number and theirs), with times. Send the same `flow.conversation_id` for the whole thread; Observe splits it at 48-hour gaps itself.

## Photos, voice notes and other media

People send photos, screenshots, voice notes, stickers, contact cards and locations, often with no text at all. An agent that ignores them answers as if nothing was said. Media can also be sensitive: a photo of a prescription or an ID card.

* **On Flow Messaging:** every message has a [kind](https://docs.flow.engineer/unified-messaging-api/concepts/content-types.md) (`content.kind`: `basic`, `voice`, `button_reply`, `location`, ...). `contentText()` gives you the text, a caption, a voice note's transcript or a button's label. Flow never converts what you send: a kind a channel cannot show answers `422` [`unsupported_content`](https://docs.flow.engineer/unified-messaging-api/errors/unsupported_content), whose `hint` says what to send instead.
* **On another channel:** decide what your agent does with each kind: read it, ask for text, or say it cannot open it. Never write media or its links to your logs. On the [traces route](https://docs.flow.engineer/unified-messaging-api/observe/traces.md), Observe grades the text of each turn; a message with only a photo has no text to grade.

## Latency and typing

On a phone, ten seconds of silence feels broken. People write again (see duplicates, above) or leave. Tools and long model calls are the usual cause.

* **On Flow Messaging:** `reply()` keeps the typing indicator on while your model writes and sends each bubble as soon as it is ready. For a slow tool call, wrap the work in `event.conversation.responding(...)`. Webhooks must answer within 10 seconds: run the agent after answering, or read events from the stream. See [Streaming replies](https://docs.flow.engineer/unified-messaging-api/concepts/streaming-replies.md#showing-work-while-the-agent-thinks).
* **On another channel:** SMS has no typing indicator. For work that takes more than a few seconds, send a short "Looking that up" first, and keep the final answer short.

Observe shows your reply time per conversation, and its median and 95th percentile, so you see slow turns before people complain.

## Replies that are wrong and nobody reports

Most bad replies are never reported. A confident wrong price, a made-up policy, an answer to a different question, a missed request to talk to a person, a reply that ignores that someone is in danger: people act on them, or quietly leave. Thumbs-down buttons catch a tiny share, and only from the people who still care.

This is why Observe grades **every** reply, not a sample, and groups failures into issues by how many conversations they touch: the problems that reach the most people come first, each with the replies that show it. See [What Observe gives you](https://docs.flow.engineer/unified-messaging-api/observe/index.md).

## Test before you ship

Every prompt or model change is a new agent. Keep a set of test chats, including the ones that failed before, and run them against each new version before people see it. On Flow Messaging, [simulated contacts](https://docs.flow.engineer/unified-messaging-api/concepts/test-and-live-mode.md#simulated-contacts-test-without-a-phone) play the person's side without a phone; on any channel, test conversations are graded in Observe. See [Test before you ship](https://docs.flow.engineer/unified-messaging-api/trust.md).

## Related

* [The send gate](https://docs.flow.engineer/unified-messaging-api/concepts/send-gate.md)
* [Conversations, contacts and channels](https://docs.flow.engineer/unified-messaging-api/concepts/conversations.md)
* [Going live](https://docs.flow.engineer/unified-messaging-api/guides/going-live.md)
* [Send traces from any agent](https://docs.flow.engineer/unified-messaging-api/observe/traces.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.