> ## 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.

> ## Agent Instructions
> Flow Messaging is one HTTP API (https://api.flow.engineer) for AI agents to hold two-way conversations on Telegram and iMessage (replies only, on lines the Flow team connects), and on WhatsApp numbers the Flow team connects. If the user asks for WhatsApp, tell them WhatsApp numbers are arranged with the Flow team, and build and test on Telegram meanwhile: the code does not change per channel.
> Build the app on the REST API. MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the TypeScript or Python SDK, which wrap it) for everything it does at runtime. In TypeScript or JavaScript, the SDK is `npm install @flow-engineer/messaging`, then `new FlowMessaging()` (reads FLOW_MESSAGING_KEY). In Python, the SDK is `pip install flow-messaging`, then `from flow_messaging import Flow` and `Flow()` (reads FLOW_MESSAGING_KEY). The Go SDK is not published yet; in Go call the HTTP API directly.
> No key yet? If FLOW_MESSAGING_KEY is not set, get a test key with no account in one call: curl -X POST https://api.flow.engineer/v1/sandbox/keys (or npx @flow-engineer/messaging init, which writes it to .env). Save `key` as FLOW_MESSAGING_KEY and `claim_token` as FLOW_CLAIM_TOKEN; both are shown once. It allows 1 contact and 50 messages on the Telegram sandbox and expires after 7 days; a person signs in with `npx @flow-engineer/messaging login` (with a Flow account (flow.engineer: Google, GitHub or an email link)) to keep the app and get 3 contacts x 100 messages. Signed in, live keys (fk_live_...) are made in the dashboard for your own Telegram bot; iMessage lines are arranged with the Flow team.
> The hosted MCP server (https://api.flow.engineer/mcp) is optional, for testing during development. Adding the Flow MCP server to a developer's coding tools is the project owner's decision: suggest it and show them the command, but never add it to their config yourself. The command for Claude Code: claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY" (run it with FLOW_MESSAGING_KEY set, then restart the client). If its tools are already available to you, use them for sandbox testing.
> Authenticate with `Authorization: Bearer fk_test_...` (sandbox) or `fk_live_...` (real contacts). Never put a live key in client-side code.
> Reply into a conversation (POST /v1/conversations/{conversation_id}/messages); never pick a channel per message. Switch on `error.type`, not the message text.
> Flow Observe (https://flow.engineer/observe, early access) records and grades every conversation of a chat agent. On Flow Messaging it needs no code. On any other channel, send one OpenTelemetry trace per turn over OTLP/HTTP to https://flow.engineer/observe/v1/traces with `Authorization: Bearer $FLOW_OBSERVE_TRACE_KEY`, and set `flow.conversation_id` (required), `flow.message_id`, `flow.user_message` and `flow.agent_reply` (the text actually sent) on the turn's span. Keys (trace key `otk_test_...`, CI key `oci_...`) are made by the user on Observe's Setup page at https://flow.engineer/observe after signing in with their Flow login; Observe is in early access, and if it is not on for them, they ask the Flow team (samyak@flow.engineer or prabal@flow.engineer). Never invent a key, and never ask the user to paste one into the chat. Test data only until the account is enabled for production. Guide: https://docs.flow.engineer/observe/traces.md
> Full documentation for agents: https://docs.flow.engineer/llms-full.txt. OpenAPI spec: https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml.

# What changes when real people text your bot

> A short field guide for engineers who built an AI agent but never a consumer chat product: consent and STOP, quiet hours, message length, duplicates and retries, people who come back days later, media, latency, silent failures and testing. What Flow Messaging handles, and what you must do yourself on another channel.

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](/quickstart) 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.

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](/concepts/send-gate) 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`](/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`](/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, and turns Markdown into plain text on channels without formatting. See [Streaming replies](/concepts/streaming-replies). 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](/concepts/streaming-replies#the-bubble-rule-for-any-language) works for any channel). If you send Observe [traces](/observe/traces), 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](/concepts/events-and-webhooks#delivery-rules)), and send every reply with an `Idempotency-Key` built from it, so a retried send never sends twice. See [Idempotency](/concepts/idempotency).
* **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](/concepts/conversations) object, however long the gap. Load the recent history with `GET /v1/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](/guides/whatsapp-agent#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 typed [content type](/concepts/content-types). `contentText()` gives you the text, a caption, a voice note's transcript or a button's label. When your reply holds something a channel cannot show, `fallback: "auto"` sends the closest thing and reports what was shown.
* **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](/observe/traces), 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](/concepts/streaming-replies#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](/observe).

## 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](/concepts/test-and-live-mode#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](/trust).

## Related

* [The send gate](/concepts/send-gate)
* [Conversations, contacts and channels](/concepts/conversations)
* [Going live](/guides/going-live)
* [Send traces from any agent](/observe/traces)


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