# Campagnes e-mail de bout en bout

Ce parcours utilise les mêmes fonctions métier depuis l'interface, l'API REST et l'outil MCP `prescriptio_campagne`. Les routes REST prennent un JSON en `POST /api/mcp/v1/{operation}` et une autorisation OAuth `mcp:read` ou `mcp:write` selon la fiche de l'opération. Les noms MCP correspondants sont les valeurs `action` ci-dessous.

| Étape | REST | Action MCP | Résultat à contrôler |
|---|---|---|---|
| Domaine | `email.domains.create` puis `email.domains.list` | `expediteur` | `fields.dns[].preuve_txt` fournit le nom et la valeur TXT à publier |
| Vérification | `email.domains.verify` | `verifier_domaine` | `fields.utilisable` doit être `true` avant de choisir ce domaine comme expéditeur |
| Brouillon | `email.campaigns.create` | `creer` | `fields.campagne_id` ; aucun message ne part |
| Message | `email.campaigns.get`, `email.messages.upsert` | `statut`, `variante` | lire `variante_id` ; utiliser `format`=`texte`, `html` ou `builder` avec `blocs` |
| Modèles | `email.templates.list/save/apply/delete` | `modeles`, `modele_sauver`, `modele_appliquer`, `modele_supprimer` | un modèle appliqué remplace le premier message |
| Test | `email.messages.test` | `tester` | résultat indiquant le mode local (aucun envoi réel), ou l'acceptation du fournisseur ailleurs |
| Audience | `email.recipients.preview` puis `email.recipients.add` | `destinataires` | prévisualiser avant d'écrire ; seules les adresses déjà détenues par l'espace entrent dans la liste |
| Envoi | `email.campaigns.schedule` | `planifier` | le calendrier est posé ; le job expédie ensuite dans le créneau ouvré de Paris |
| Suivi | `email.campaigns.get`, `email.metrics.get` | `statut`, `stats` | acceptation, livraison, échecs, clics et désinscriptions ont des sens distincts |

## Studio et suivi sans interface

- `email.identity.get/update` lit et modifie l’identité par défaut (administrateur pour la modification). Un champ absent est conservé ; une chaîne vide efface une adresse facultative.
- `email.audiences.list/create/update/delete` gère les listes et segments. `contacts` contient des identifiants `base:<UUID>` ou `crm:<UUID>`. `liste_id` désigne une liste de la base, ou `audience:<UUID>` pour appliquer une audience enregistrée à `email.recipients.preview/add`.
- `email.templates.create/update` compose directement un modèle texte, HTML ou blocs. Une modification remplace le contenu ; transmettre l’objet et le corps ou les blocs.
- `email.messages.preview` rend `objet`, `texte` et `html` personnalisés avec les valeurs de `personnalisation` ; ses liens de désinscription et pixels fictifs n’agissent sur aucun destinataire réel.
- `email.recipients.list` lit tout le calendrier par pages de 1 à 200 touches : reporter `decalage_suivant` dans `decalage` jusqu’à `null`. Éviter de modifier le calendrier pendant cette lecture. `email.recipients.delete` retire une touche non envoyée ; `email.recipients.reply` annule les relances après une réponse déclarée.
- `email.campaigns.delete` refuse les campagnes ayant déjà envoyé un message. `email.quotas.update` exige un administrateur ; ses champs absents reviennent aux valeurs par défaut, comme dans l’interface.
- `email.deliveries.refresh` contrôle au plus trois livraisons dues par appel ; un contrôle récent n’est pas refait avant cinq minutes. Le traitement périodique complète ces contrôles.

## Envoi unitaire et rejeu

`email.messages.send` (MCP `envoyer`) demande `cle_idempotence`, `email`, `objet`, le contenu (`corps` ou `blocs`) et une adresse de réponse, explicite ou enregistrée dans l’identité. Il utilise le domaine vérifié, les exclusions et les plafonds communs. Il est immédiatement présenté au moteur d’envoi ; un quota atteint le laisse en attente. Un support de suivi est créé automatiquement dans les campagnes, sans préparation de séquence ni relance.

```json
{"cle_idempotence":"commande-123-confirmation","email":"personne@example.invalid","objet":"Confirmation","corps":"Votre demande est enregistrée.","repondre_a":"equipe@example.invalid"}
```

Conserver la clé pour chaque message logique : la même clé et la même requête retrouvent le même `envoi_id`. Une autre requête avec cette clé est refusée. Les clés sont conservées tant que l’organisation existe. Après un délai dépassé, rejouer la requête identique ou appeler `email.messages.get` ; ne pas créer une nouvelle clé. `en_cours` peut signifier une issue fournisseur encore inconnue : aucun renvoi automatique n’est effectué. `mode=journal` signifie aucun envoi réel, même si le statut de traitement est `envoye`.

## événements consommables

`email.events.poll` (MCP `evenements`) lit le journal de l’organisation par pages de 200 maximum. Premier appel : `curseur:"0"`. Après traitement réussi de toute la page, mémoriser `fields.curseur_suivant`. Une page vide laisse le curseur inchangé. Un rejeu restitue les mêmes identifiants : dédupliquer par `id`. Le journal conserve les événements depuis son installation, sans reprise des anciennes campagnes ni purge automatique.

Les types `message.*` décrivent le traitement (`en_cours`, `envoye`, `echec`, `annule`, `desinscrit`, `repondu`), `livraison.*` le retour fournisseur (`sent`, `failed`, etc.), `desinscription` une adresse exclue et `activite.*` un clic, un envoi ou une réponse. `detail.mode=journal` distingue les simulations. Les événements de livraison arrivent après synchronisation fournisseur ; aucune garantie de temps réel. Les dates du journal sont en UTC ISO 8601. Le curseur est une chaîne décimale, à conserver telle quelle.

Exemple de blocs pour une variante existante :

```json
{"campagne_id":"<UUID>","variante_id":"<UUID>","objet":"Bonjour [prenom]","format":"builder","blocs":[{"kind":"titre","text":"Bonjour [prenom]"},{"kind":"texte","text":"Découvrez notre équipe."},{"kind":"bouton","text":"En savoir plus","url":"[lien1]"}]}
```

Déclarez le lien avec `email.links.replace` avant de planifier. Une adresse `expediteur` sur un domaine seulement déclaré est refusée. La vérification requiert la preuve TXT, puis les enregistrements d'authentification et le contrôle du fournisseur. L'API rend les valeurs DNS pour chaque domaine de l'espace ; elle ne modifie pas la zone DNS chez votre hébergeur.

Le test est limité à cinq essais sur dix minutes par organisation. En développement local, il compose le message et enregistre un essai au statut `journal`, sans livraison. Les statistiques d'ouverture sont indicatives, car un service automatique peut charger l'image. `envoye` signifie accepté par la passerelle ; la livraison se confirme séparément. Les routes REST et le MCP partagent les quotas d'appels et les mêmes droits d'organisation.

`email.metrics.get` rend `fields.suivi[]` : pour chaque campagne, `contacts_ayant_ouvert` compte les destinataires uniques ayant chargé le pixel, et `livraisons[]` porte les couples `statut` / `nombre` connus du fournisseur (`sent` = livré, `failed` = échec). Ces mesures sont les mêmes que celles de l'écran Performances. Une liste de livraisons vide signifie qu'aucune confirmation n'est enregistrée. Le champ historique `delivres` des lignes du canal désigne l'acceptation, pas la livraison confirmée. Les tableaux `calendrier` et `par_contact` sont bornés à 200 touches ; les agrégats couvrent toute la campagne. Une campagne absente de l'espace provoque une erreur, pas des compteurs à zéro.
