# Messagerie et politique anti-bannissement

> Les plafonds, classes de destinataires et codes d'erreur qu'evaluateSendPolicy applique à chaque envoi WhatsApp.

# Messagerie et politique anti-bannissement

Le WhatsApp d'une boutique est un vrai numéro de téléphone sur WhatsApp
Web ; WhatsApp bannit les numéros qui se comportent comme des spammeurs.
`evaluateSendPolicy` (`src/libs/services/WhatsAppSendPolicy.ts`) refuse au
nom du tenant avant tout envoi effectif, sur chaque route capable de mettre
un message sur le fil — pas seulement l'API.

## Classes de destinataires

Chaque envoi 1:1 est classé avant que les plafonds ci-dessous ne soient
vérifiés :

| Classe | Signification |
|---|---|
| `known` | correspond à une ligne `CustomerModel`, ou le numéro a déjà écrit à la boutique |
| `declared` | pas connu, mais le tenant l'a déclaré via `POST …/contacts/consented` et la déclaration est toujours active (ni révoquée, ni expirée) |
| `cold` | ni l'un ni l'autre — un véritable inconnu |
| `group` | le destinataire est un groupe WhatsApp (`@g.us`), jamais known/declared/cold |

## Plafonds (1:1)

| Règle | Par défaut | Code |
|---|---|---|
| Contenu | ≤ 4096 caractères, ≤ 1 média, texte ou média requis | `MESSAGE_TOO_LONG` / `TOO_MANY_MEDIA` / `EMPTY_MESSAGE` |
| Désabonnement | le dernier message entrant du destinataire était STOP/ARRÊT/DÉSABONNER/UNSUBSCRIBE | `RECIPIENT_OPTED_OUT` |
| Montée en puissance (jours depuis la connexion de la session WhatsApp) | j0–2 : 20/jour · j3–6 : 50 · j7–13 : 100 · j14+ : 250 | `DAILY_CAP_REACHED` |
| Horaire | max(10, plafond quotidien ÷ 6) | `HOURLY_CAP_REACHED` |
| Destinataires froids | 10 % du plafond quotidien, minimum 5 | `COLD_RECIPIENT_CAP_REACHED` |
| Destinataires déclarés | budget propre, configurable par le tenant — distinct de celui des « froids », ne déborde pas dessus | `DECLARED_RECIPIENT_CAP_REACHED` |
| Par destinataire | ≤ 3 messages / 24h, ≥ 10 min d'écart sauf s'il a répondu depuis | `RECIPIENT_RATE_LIMITED` |
| Motif en masse | texte identique envoyé à plus de 20 numéros distincts en 1h | `BULK_PATTERN_BLOCKED` |

Les tenants peuvent ajuster `coldPerDay`/`declaredPerDay` et les réglages
associés, dans un plancher et un plafond fixés par la plateforme, depuis
**Paramètres → Messagerie** ; une valeur hors de [plancher, plafond] est
rejetée côté serveur, jamais simplement plafonnée en silence.

## Plafonds (groupes)

Envoyer à un groupe utilise un budget entièrement séparé de celui du 1:1,
basé sur `groupsPerDay`/`groupPerRecipient24h`, parce qu'un groupe n'est
pas une personne :

| Règle | Code |
|---|---|
| L'appelant doit fournir le contexte du groupe (nombre de participants, statut admin, indicateur annonce) | `GROUP_CONTEXT_REQUIRED` |
| Le numéro de la boutique doit administrer le groupe — requis pour chaque groupe, pas seulement ceux en mode « annonces », car le désabonnement ne peut pas fonctionner par membre et la simple appartenance n'est pas un consentement à la diffusion | `GROUP_NOT_ADMINISTERED` |
| Plafond quotidien pondéré : une « part » de `groupsPerDay` par tranche de `GROUP_SIZE_UNIT` participants (arrondi au supérieur, minimum 1) — un groupe de 200 membres coûte plus qu'un groupe de 5, car il touche plus de monde | `GROUP_DAILY_CAP_REACHED` |
| Par groupe | ≤ `groupPerRecipient24h` messages / 24h | `RECIPIENT_RATE_LIMITED` |

`GET /shops/{idOrName}/groups` renvoie `isAdmin`/`announce` par groupe, ce
qui permet à l'appelant de filtrer et d'anticiper un refus probable avant de
tenter un envoi — mais c'est la politique ci-dessus qui fait réellement
autorité ; le champ `canSend` de la route est indicatif.

## Appliqué dans le bot quel que soit l'appelant

Le destinataire doit exister sur WhatsApp (`rejected`, code
`RECIPIENT_NOT_ON_WHATSAPP`) ; un seul envoi à la fois par boutique avec un
intervalle aléatoire de 6 à 20 secondes entre les envois ; une présence
« en train d'écrire » (composing) affichée avant chaque message ; une pause
de 15 minutes de toute la file d'attente de la boutique sur un signal de
limitation venant de WhatsApp lui-même ; et en cas de perte de session,
chaque message en file échoue avec `WHATSAPP_SESSION_LOST` et le canal est
signalé jusqu'à la reconnexion. Si le WhatsApp propre à une boutique est
déconnecté, certains envois peuvent passer par le pool partagé de la
plateforme à la place, accompagnés d'une attribution visible « envoyé via
OmniManager » — jamais silencieusement au nom du tenant.

Bonne pratique : écrivez à des personnes qui s'y attendent, variez la
formulation, étalez les envois sur la journée, et respectez immédiatement
les désabonnements et les réponses.
