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

# Delivery guarantees

> What Flow Framework promises about the messages an agent sends and receives: what a send means, ordering, duplicates, and what your own provider must do.

What Flow Framework promises about an agent's messages on every channel, and what it does not.
Each promise is checked against every channel adapter the framework ships by its conformance suite,
before every release.

## What a send means

A channel's send **resolves once the platform has accepted the message**, and **rejects when the
platform refused it**, with the platform's reason. Accepted means the platform took it to deliver:

| Channel                                                           | Resolves when                                                                                                                                                                                  |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Flow Messaging (WhatsApp, SMS, Telegram, iMessage)                | Flow reports `message.sent`: the channel itself took it, not only Flow's `202`, which means queued. `waitFor: "delivered"` waits for `message.delivered` instead, where the channel reports it |
| `whatsapp(provider)`, `sms(provider)`, `customUI(name, provider)` | Your provider's `sendMessage` (or `sendCarousel`, `sendTemplate`) resolves. Write it to resolve when your platform's API accepted the message, not before                                      |
| `webChat()`                                                       | The visitor's outbox in the agent's store kept it, under the visitor's next number: the chat box reads it from there. It rejects when that write fails                                         |
| The playground and the test cloud                                 | The simulator has it                                                                                                                                                                           |

A send may resolve with how far the message got: `sent`, `delivered`, `queued` (a provider that
does not wait, such as Flow Messaging with `waitFor: "queued"`), or `unconfirmed`. `unconfirmed`
means no signal came within the provider's wait (30 seconds by default for Flow Messaging): the
message may still arrive and is **never sent again**. The agent emits `message.slow` and carries on.

What follows from it:

* A send tool's result tells the agent `delivered` only once the platform accepted the message.
* `message.out` is emitted, and the message kept in the history, only then.
* A rejected send comes back to the agent once, as a “Not delivered” note with the reason
  (`message.undelivered`). So does a failure Flow Messaging reports after the send resolved.
* A carousel the channel sends card by card resolves once the platform accepted its last card.
* An edit or unsend resolves once the platform accepted the call, and rejects when it refuses it.
* Typing indicators, read receipts and status lines are courtesies: their failures never fail a
  turn. Typing is kept up while the agent works and never outlives the turn.

## Ordering

* **One conversation, one message at a time.** An agent's messages to one person go to the channel
  in the order it wrote them, and each goes only once the one before it was accepted. Messages the
  framework sends itself (an acknowledgement, a scheduled message, a nudge) wait for the turn in
  progress and follow the same rule.
* **Inbound, per conversation.** A conversation runs one turn at a time and takes the person's
  messages in the order the channel gave them; double texts fold into the turn in progress. Flow
  Messaging delivers events in order per conversation, by stream and webhook alike.
* **Across conversations, nothing is ordered.** Different people's conversations run side by side.
* **Across one person's channels,** each channel is ordered on its own; the agent is told which
  channel each message came on.
* **A person's other conversations** (under another key, sharing a verified phone number: a
  WhatsApp chat and a web chat signed in with that number) are shown to each turn as their recent
  messages, read from the store when the turn starts: a message the other conversation is taking at
  that moment may show only from the next turn, and a turn whose read fails goes on without them
  (a warning is logged). Replies go only on the channel of the conversation whose turn it is. Only one agent's conversations are ever linked, and `forgetPerson` deletes all
  of them ([one person, several conversations](https://flow.engineer/docs/conversations.md#one-person-several-conversations)).
* What a platform does after accepting a message is the platform's. Flow Messaging sends one message
  at a time per conversation, in the order accepted; Telegram and WhatsApp deliver in that order in
  practice but promise nothing more.

## Inbound: at least once in, taken once

Platforms deliver at least once: a webhook is retried until it is answered, and Flow Messaging
redelivers events it is not sure arrived. The agent takes each message once:

* A message whose id the conversation has already taken is a `message.duplicate` and starts no
  turn. With a database (`sqlite(file)`, `postgres(...)`) that holds across restarts and processes;
  with the default in-memory store, until the process stops.
* Flow Messaging events already handled are dropped, so a redelivery, or the same event by both the
  stream and a webhook, reaches the agent once. A failure to handle an event answers the webhook
  `500` (or does not move the stream's cursor), so Flow delivers it again.
* The web chat's box sends each message with its own id, the same on every retry, so a retry is a
  `message.duplicate`. `POST /messages` answers `202` only once the store has the message; a write
  that failed answers `503`, and the box sends it again.

## Outbound: when exactly once is true, and when it is not

* **Messages the business starts** (a nudge, a scheduled message, a template) carry an idempotency
  key made from their id, the same in every process. A provider that honours keys, as Flow Messaging
  does for 24 hours, sends each **once**, even when two processes or a restart send it again. A
  nudge already settled in the store is never sent again.
* **Retries of one send** (Flow Messaging's on a `429` or `5xx`) repeat the same key: never a second
  message.
* **The agent's replies** are sent once: a send that was accepted is in the history and never sent
  again, also after a restart. One case is **at least once**: a process that stops after the
  platform accepted a reply but before the store recorded it runs the turn again, and the agent may
  say it again.
* **A send that timed out** (`unconfirmed`) is never sent again: at most once from the framework.
  The platform may still deliver it.
* **Flow Messaging's own resend.** When a channel took a message but never answered, Flow resends it
  once, so a rare duplicate is possible on Telegram; an outcome still unknown fails with
  `channel_timeout_unknown_outcome` (it may have arrived). Idempotency keys cannot undo that.
* **One process works on a conversation at a time** (a lease in the database), so a deploy's overlap
  never answers one message twice.
* **Scheduled messages and wake-ups** are rows in the store. When one comes due, one process claims
  it and starts its turn in one transaction, so it runs once, also with several processes or across
  a restart. A scheduled message found more than 30 minutes late is missed, never sent late; a
  wake-up found late still runs, and the agent is told when it was due.

### The web chat

**The web chat shows each message once, in order.**

* What the box shows waits in the visitor's outbox in the agent's store, numbered per visitor.
  Their tabs and devices share it, and their own messages are echoed into it.
* The box asks for everything after the last number it showed, and ignores a number it already
  showed. A phone that slept, a dropped connection, a restart or a deploy loses nothing and repeats
  nothing.
* A request waiting on one process wakes when that process keeps an event, and checks the store
  every 2 seconds for what another process kept (a deploy's overlap).
* The outbox keeps a week; a box asking for older reads the history again.
* Typing and status lines are live only: one sent while no box is connected is lost.

## Writing your own provider

`whatsapp(provider)`, `sms(provider)` and `customUI(name, provider)` keep these guarantees only if
the provider does its part:

* Resolve `sendMessage` when your platform's API has accepted the message, and reject with a
  readable reason when it refused it. Never resolve early to go faster: the next message would
  overtake it, and the agent would be told it was delivered.
* Pass `idempotencyKey` on to your platform when it takes one.
* If your platform's acceptance can take long, wait a bounded time and resolve
  `{ status: "unconfirmed" }` past it, rather than hanging the conversation.

To check an adapter of your own, add it to the framework's conformance suite
([`conformance/`](https://github.com/flow-engineer/flow/tree/main/conformance), `npm run test:conformance`).

> **[Web chat](https://flow.engineer/docs/messaging/web-chat.md)**
>
> The agent in a chat box on your site.

> **[Handle retries and restarts](https://flow.engineer/docs/self-hosting.md#handle-retries-and-restarts)**
>
> Message ids, leases and recovery when you host it yourself.
