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

# Sending rules

> The server governs sending. Each rule holds a message back or refuses it, and the response says why. The rules apply twice: when the message is created, then at the moment it is sent.

## Overview

| Rule | Threshold | Effect |
|---|---|---|
| [Gradual warm-up](#montee-progressive) | 5, 10, 15, then 20 e-mails a day per inbox, week after week. | Holds back: beyond the step, the message waits for the next working day. |
| [Daily quota](#quota-du-jour) | Per inbox: its step, capped by its maximum quota. Per key: a cap, if it carries one. | Holds back at the inbox. Refuses at the key: `key_daily_cap`. |
| [Working hours](#heures-ouvrees) | By default Monday to Friday, 9 am to 6 pm, Paris time, public holidays excluded. | Holds back: the message leaves at the first open moment. |
| [Human approval](#validation-humaine) | Requested per message, or required by the space. | Holds back: the message stays a draft until `approve_message`. |
| [Unsubscribe](#desinscription) | A one-click link in every e-mail; a “STOP” reply. | Refuses any new send to the address: `recipient_suppressed`. |
| [Circuit breaker](#coupe-circuit) | 5% bounces (3% during warm-up), or 0.1% complaints. | Suspends the inbox. Refuses: `inbox_suspended`. |

- **Holds back**: the message waits, and the response gives the earliest departure. It is not an error: do not send it again.
- **Refuses**: the response carries a stable `code`, a `message` in French and a lead (`hint`). See [Errors](https://escouade.io/en/mail/docs/erreurs.md).

Four checks come on top: the space's [plan](#forfait), the [duplicate](#doublon), the forgotten [merge field](#champ-de-fusion) and [idempotency](https://escouade.io/en/mail/docs/messages.md#idempotence).

In the server's responses, the gradual warm-up is called “réchauffage”, and working hours the “créneau d'envoi” (sending window).

## Gradual warm-up

Every inbox starts at 5 e-mails a day, then moves to 10, 15 and 20: one step per week, from the day it was created. The last step then holds.

| Week | Sends per day |
|---|---|
| 1 | 5 |
| 2 | 10 |
| 3 | 15 |
| 4 and after | 20 |

An agent can neither lift nor speed up the warm-up. `inbox_status` returns the current week, the step and the steps to come (`warmup`).

## Daily quota

**Per inbox.** The daily quota is the smaller of two numbers: the week's step and the inbox's maximum quota (20 by default). It counts the messages sent since midnight, Paris time. A draft or a queued message only uses it at the moment it leaves.

When the quota is reached, the next message stays in the queue and leaves on the next working day:

```text
Message msg_7f2a91c4e8b34d0fa6c25e1b9d3f7a48 en file dans le fil thr_0e5a7c2b9f1d48e6a3c7b5d0e2f9a814 : de agent@exemple.fr à nadia.morel@exemple.com, « Rendez-vous de jeudi ». Départ au plus tôt : mercredi 14 octobre à 9 h. Quota du jour de la boîte : 5/5 (semaine 1 du réchauffage, palier de 5 par jour).
```

**Per key.** A key can carry a cap on messages per day, all inboxes together. It counts the messages created during the day, drafts included. Beyond it, creation is refused with the code `key_daily_cap`, until midnight. The key returned by `sign_up` has no cap.

## Working hours

A message leaves within the space's sending window. By default: Monday to Friday, 9 am to 6 pm, Paris time, excluding the eleven French public holidays. Outside the window, the message waits for the first open moment: an e-mail written on Sunday leaves on Monday.

- **Spacing.** Two sends from the same inbox are spaced out: 10 minutes by default, plus a random share.
- **A reply in a conversation does not wait.** As soon as the correspondent has written in the thread, `reply_to_thread` leaves without waiting for the sending window and goes ahead of first messages in the queue. It still counts towards the daily quota and respects spacing. A follow-up with no reply, on the other hand, waits for the window.
- **`send_after`** pushes back a departure: the sending window and the quotas apply afterwards.

The sending window, the days without sending and spacing are settings of the space: its human changes them from their assistant, with `update_settings`. `inbox_status` returns the window in plain words (`send_window`), `get_settings` details it. See [Space settings](https://escouade.io/en/mail/docs/espace.md).

## Human approval

With `requires_approval`, or if the space requires it, a message stays a draft (`draft`). Only an access that carries the `approve` permission approves it, with `approve_message`: the human's, from their assistant connected at `https://mail.escouade.io/mcp`. The key returned by `sign_up` does not carry it: an agent does not approve its own sends. See [Drafts and approval](https://escouade.io/en/mail/docs/brouillons.md).

## Unsubscribe

- Every e-mail carries a one-click unsubscribe link (RFC 8058), in its headers.
- As long as the correspondent has not written in the thread, an unsubscribe notice is added in the message's footer.
- A “STOP” reply counts as unsubscribing.
- An unsubscribed address, a complaint or a dead address shut off any new send: `recipient_suppressed`. Whatever was waiting to leave towards it is cancelled.

See [Unsubscribes and blocks](https://escouade.io/en/mail/docs/desinscription.md).

## Circuit breaker

The server judges each inbox's latest sends, in batches of 100 at most.

| Measure | Threshold | During warm-up |
|---|---|---|
| Bounces | above 5% | above 3% |
| Complaints (message reported as spam) | above 0.1% | above 0.1% |

The shares are computed over at least 40 sends: two bounces out of ten sends count as 5%, not 20%.

Above a threshold, the inbox is suspended: `status` = `suspended`, and the reason is written in `status_reason`. Any send is refused with the code `inbox_suspended`. The inbox keeps receiving. Reinstatement is manual, by an Escouade Mail administrator: before asking for it, check the list of recipients.

## Plan

The space's plan decides what an inbox can send, before the other rules.

| Situation | What leaves | Otherwise |
|---|---|---|
| Before payment | A test send (`test`), to the space's human or another of its inboxes. A draft can be prepared, but not approved. | Refusal: `payment_required`. |
| Plan paid, inbox on the shared domain `subagentmail.fr` | A reply to a correspondent who has written in the thread. | Refusal: `own_domain_required`. |
| Plan paid, inbox on the customer's domain | A first message, a reply, a follow-up. | — |
| Month's volume reached | Nothing, until the next period. A test send always leaves. | Holds back: the message stays in the queue. |

- **The month's volume** counts the e-mails sent since the start of the paid period: 800, 2,000 or 6,000 depending on the plan. A test send does not count.
- **A reached volume is not a refusal.** Messages wait, replies included, and leave in the next period. `billing_status` gives the volume sent and the end of the period.
- **The daily quota does not depend on the plan**: 20 e-mails a day per inbox, after the warm-up.
- **A late payment** keeps sending open for three days. After that, the space is treated as before payment.

Plans, their prices and payment: [Pricing and payment](https://escouade.io/en/pricing).

## Duplicate

An inbox does not write two first messages to the same recipient within 30 days. The second `send_email` is refused with the code `duplicate_recipient`, and the lead gives the existing thread. To follow up, reply in that thread with `reply_to_thread`: a reply in a thread is not a duplicate. A test send (`test`) escapes the rule.

## Merge field

A field in double braces left in the subject, the text or the HTML, such as `{{prenom}}`, blocks the send: code `unresolved_placeholder`. Only `{{unsubscribe_url}}` is accepted: the server replaces it with the unsubscribe link.

## What an agent cannot do

- Lift or speed up the warm-up.
- Send outside the sending window, except a reply in a conversation.
- Approve its own drafts.
- Pay for the plan, or cancel it.
- Write a first message from an inbox on the shared domain.
- Lift an unsubscribe, a complaint or a dead address.
- Reinstate a suspended inbox.
- Adjust the space, create or revoke a key, declare a webhook, close the space.
