> For an agent: the map of this documentation is at https://escouade.io/en/mail/docs/llms.txt; every page exists in Markdown, at the same address with `.md` appended.

# Drafts and approval

> A send can wait for human approval. The message stays a draft, a human reviews it in their assistant, then approves, corrects or cancels it. An agent does not approve its own sends.

## When a message stays a draft

- **The message asks for it**: `send_email` or `reply_to_thread` with `requires_approval` = `true`.
- **The space requires it**: every send stays a draft. `inbox_status` says so (`require_approval`). The space's human sets it from their assistant, with `update_settings`.

A draft has the status `draft`. Files are attached to a draft: see [Attachments and copies](https://escouade.io/en/mail/docs/pieces-jointes.md#joindre). It only counts towards its inbox's daily quota at the moment it leaves. It can be prepared even before payment: it can only be approved with an active plan.

The response tells the agent where its human approves:

```text
Brouillon msg_7f2a91c4e8b34d0fa6c25e1b9d3f7a48 dans le fil thr_0e5a7c2b9f1d48e6a3c7b5d0e2f9a814 : de agent@exemple.fr à nadia.morel@exemple.com, « Rendez-vous de jeudi ». Il attend une validation humaine : votre humain (m•••@exemple.fr) branche son assistant (Claude, ChatGPT…) à l’adresse https://mail.escouade.io/mcp, s’y connecte avec cet e-mail, puis relit le brouillon et le valide ou le corrige. Dites-le-lui : aucun avis ne lui est envoyé. Rien ne part avant.
```

## Who approves

Approval needs the `approve` permission. The server checks it on every call to `approve_message`.

- **The key returned by `sign_up`** carries `send`, `read` and `manage_inboxes`. It does not carry `approve`: the agent that signed up cannot approve its own sends.
- **The human gets `approve` through the connector.** They connect their assistant (Claude, ChatGPT…) at `https://mail.escouade.io/mcp`, sign in with their Escouade account, at the address attached to their space, then authorise. There is no key to copy. See [Connector](https://escouade.io/en/mail/docs/connecteur.md).
- **Approval happens in the assistant**: through an explicit request from the human (“approve the first one”), which becomes a call to `approve_message`, or through the “Valider” (approve) gesture in the drafts view.
- **The server does not tell the human** that a draft is waiting: the agent tells them.

The full path, on the agent's side and the human's side: [Approve an agent's sends](https://escouade.io/en/mail/docs/guide-validation.md).

Without the permission, the call is refused:

```text
approve_message demande la permission approve, que cette clé n’a pas. (code forbidden)
→ La validation est un geste humain : votre humain l’accomplit depuis son assistant (Claude, ChatGPT…), branché à l’adresse https://mail.escouade.io/mcp où il se connecte avec son e-mail.
```

## `list_drafts`

Lists the drafts waiting for approval, with their full text, newest first. Permission: `read` or `approve`.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `inbox_id` | `inb_…` identifier | no | A single inbox. |
| `cursor` | text | no | The cursor of the next page: the `next_cursor` value from the previous call. |
| `limit` | integer | no | The number of items returned, from 1 to 100. 20 by default. |

```text
1 brouillon(s) à valider :

[msg_7f2a91c4e8b34d0fa6c25e1b9d3f7a48 · sortant · en brouillon (attend une validation) · mardi 13 octobre à 14 h 02]
De : Atelier Exemple <agent@exemple.fr>
À : nadia.morel@exemple.com
Sujet : Rendez-vous de jeudi

Bonjour Madame Morel,

Pouvons-nous déplacer notre rendez-vous de jeudi à vendredi, à la même heure ?

Atelier Exemple
```

The structured object carries `items`, `next_cursor` and `key`: the key that reads, with its name and permissions. Each draft carries its version (`revision`), to pass back to `approve_message`.

## `update_draft`

Changes a draft's subject, text or HTML, as long as it is not approved. The recipient and the inbox do not change: for that, cancel the draft and write a new message. Every correction is noted in the log, with its author. Permission: `send` or `approve`.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `message_id` | `msg_…` identifier | yes | The draft (see `list_drafts`). |
| `subject` | text | no | The new subject. |
| `text` | text | no | The new text. |
| `html` | text | no | The new HTML. An empty text removes the HTML version. |

## `approve_message`

Approves a draft: it moves to the queue and will leave within the sending window, according to the quotas. Permission: `approve`.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `message_id` | `msg_…` identifier | yes | The draft (see `list_drafts`). |
| `revision` | text | no | The version of the draft as it was read: the `revision` field returned by `list_drafts`. |

```text
Validé par Claude (marie@exemple.fr) : msg_7f2a91c4e8b34d0fa6c25e1b9d3f7a48 (agent@exemple.fr → nadia.morel@exemple.com, « Rendez-vous de jeudi ») est en file. Il partira dans le créneau, selon le quota de la boîte (inbox_status).
```

- The name of the access that approved is written in the message (`approved_by`) and in its log: the application's name, followed by the human's e-mail.
- Without an active plan, approval is refused with the code `payment_required`. A draft of a test send can always be approved.
- A draft written from the inbox on the shared domain can only be approved if it replies to a correspondent who wrote: otherwise, refusal `own_domain_required`.
- If the recipient has been blocked since the draft was created, approval is refused with the code `recipient_suppressed`.
- A message that is no longer a draft is refused with the code `not_draft`.
- With `revision`, approval only applies to what was read: if the draft has changed since, it is refused with the code `draft_changed`. The drafts view passes it on by itself.
- Before approving, the human sees everything that will leave: `list_drafts` also returns the reply-to address (`reply_to`) and the HTML version (`has_html`, `html`), and points them out in its text.

## Cancelling a draft

`cancel_message` cancels a draft or a queued message. See [Messages](https://escouade.io/en/mail/docs/messages.md#cancel_message).

## In the assistant

In an assistant that displays MCP Apps views, `list_drafts` comes with a view: the queue of drafts, with the gestures “Valider” (approve) and “Refuser” (refuse). “Valider” calls `approve_message` with the version displayed; “Refuser” calls `cancel_message`. Without the `approve` permission, “Valider” is absent.

Correcting a draft is asked for in the conversation: the assistant calls `update_draft`. Without a view, everything is read and approved through the text. See [Views](https://escouade.io/en/mail/docs/vues.md#valider).
