Aller au contenu

Skills Celuga

mep-celuga

Préparer, auditer ou dépanner une mise en production : Coolify, SMTP, Brevo, indexation.

Quand : Préparer, auditer ou dépanner une mise en production : Coolify, SMTP, Brevo, indexation.

Portée : machine (~/.agents/skills) · Fichiers : 3

Copier le bloc et le coller dans un terminal. Il recrée tous les fichiers du skill.

dir="$HOME/.agents/skills/mep-celuga"
mkdir -p "$dir"
cat > "$dir/SKILL.md" <<'CELUGA_SKILL_EOF'
---
name: mep-celuga
description: Préparer, auditer ou dépanner une mise en production Celuga. Utiliser pour une MEP, une préproduction, un déploiement Coolify/Docker, un contrôle SMTP, Brevo ou formulaire Payload, ou une vérification d'indexation SEO selon l'environnement.
---
# MEP Celuga
Sécuriser le passage en ligne par des **preuves observables** sur l'environnement réellement déployé. Un build qui passe n'est pas une MEP prête.
Charger **celuga-docker-payload-astro** pour la stack, **celuga-astro-payload-cache** pour les preuves de cache, **celuga-seo** pour le contrat SEO.
## 1. Établir le contexte
1. Lire `AGENTS.md`, `README.md` et `docs/DEPLOIEMENT.md`, `docs/ENVIRONNEMENT.md` s'ils existent. Une documentation peut retarder sur le code : en cas de doute, le code et le Compose priment.
2. `git status` : préserver les modifications existantes, ne rien committer sans demande. Vérifier si un push déclenche un déploiement.
3. Identifier stack, plateforme, domaine de production et domaines dev/préprod, et pour chacun son statut.
4. Classer chaque variable selon son lecteur : les `PUBLIC_*` du Web sont fixées au **build** et lues aussi au runtime ; le reste est lu au runtime. Vérifier son passage par les arguments de build, le stage final et Compose : seule une variable référencée par Compose arrive au conteneur.
5. Identifier la base visée par le `.env` local **avant** toute commande : elle peut être partagée.
Ne jamais afficher ni committer un secret ; pour comparer deux valeurs, rapporter seulement présence ou égalité.
**Terminé quand :** un tableau domaine → environnement et un tableau variable → moment de lecture → source sont établis.
## 2. Portes qualité locales
Utiliser les scripts du dépôt ; habituellement :
```bash
bun install --frozen-lockfile
bun run verify # tests, lint, frontières, typage, builds, EcoIndex de l'accueil
bun audit # aucune vulnérabilité high
```
`verify` n'applique aucune migration et ne prouve ni une connexion runtime, ni le transport e-mail. La mesure EcoIndex exige une base joignable ; sans base, `CELUGA_ECOINDEX=off` la saute et le rapport doit le dire. Les `.astro` ne sont pas typés.
Après `bun run generate`, aucun diff ne doit rester. Tests navigateur : `bun run test:e2e`, sur une instance de test ; les configurations refusent les domaines de production. Tests sur base réelle : nom de base `*_test` local uniquement.
Pour l'éco-score : mesurer un **build de production** servi compressé ; la cible du manifeste ne porte que sur l'accueil. Une mesure en `dev` ne vaut rien.
Séparer les échecs préexistants (vérifiés sur la branche principale) des régressions.
**Terminé quand :** chaque commande a un résultat noté (passe, échec préexistant, régression) avec sa sortie utile.
## 3. Stack déployée
Commencer par les observations en lecture seule :
- CMS et Web `healthy`, `/api/health` à 200 (la sonde du Web prouve Web → CMS → PostgreSQL) ;
- migrations relues et appliquées par le service ponctuel, après sauvegarde : le CMS ne migre rien au démarrage ; `schema:check` sur la cible si autorisé ;
- volume uploads persistant sur le CMS, sauvegarde et restauration disponibles avant une opération risquée ;
- secrets réels et distincts : `SITE_SECRET` identique côté CMS et Web, `PAYLOAD_SECRET` inchangé sur une installation existante (il chiffre les clés d'intégration) ;
- premier administrateur créé par `admin:create`, variables retirées ensuite ;
- cache : tags, preview hors cache, erreurs non stockées, selon **celuga-astro-payload-cache** ;
- tâches de fond actives (panneau Santé, sonde), notifications en échec relancées ;
- confidentialité : durée de conservation des demandes définie avec le client ;
- médias référencés présents sur le serveur (`/media/*` à 200), logs CMS et Web relus après le smoke test.
Arrêter le CMS pour prouver le 503 est une simulation de panne : à faire sur un pilote isolé ou une cible où l'interruption est autorisée.
**Terminé quand :** chaque point est prouvé par une commande ou une observation, ou listé comme action externe restante.
## 4. Parcours critiques
Sur le domaine déployé : accueil, une page, un article, 404, preview, un média, un document, formulaire critique, `/robots.txt`, `/sitemap.xml`, `/llms.txt` si présent, `<head>` (titre unique, canonical, `og:image` en 200), chaque langue active.
Formulaire et e-mail : lire [`references/email-smtp.md`](references/email-smtp.md) dès qu'un envoi est en jeu. N'envoyer qu'un message de test vers une boîte contrôlée, et seulement si la demande l'autorise. CAPTCHA : vérifier que les vraies clés s'appliquent sur le domaine final, et pas des clés de test.
**Terminé quand :** chaque parcours a atteint son effet observable (page rendue, fichier servi, message **reçu**) ou son blocage externe exact est identifié.
## 5. Indexation par environnement
Tout domaine public est considéré **indexable** tant que le réglage contraire n'a pas été observé.
`PUBLIC_ALLOW_INDEXING` : `true` uniquement en production ; il pilote meta robots, `X-Robots-Tag` et `/robots.txt`. Le changer impose un rebuild. Le domaine du CMS répond toujours `Disallow: /`. Un Compose qui fige la valeur l'emporte sur l'interface de la plateforme : le lire.
```sh
curl -sI https://domaine.example | grep -i x-robots-tag
curl -s https://domaine.example/robots.txt
curl -s https://domaine.example/ | grep -io '<meta name="robots"[^>]*>'
curl -s https://domaine.example/sitemap.xml | head
```
En production, contrôler aussi l'absence de blocage WAF/CDN pour les crawlers de recherche et la soumission du sitemap (voir **celuga-seo**). Une requête `curl -A` vérifie seulement la réponse à cette requête ; compléter par les outils webmaster et les journaux.
Si un environnement non productif a déjà été indexé, rapporter l'action et, seulement sur demande, la suppression dans les outils webmaster.
**Terminé quand :** chaque domaine connu est classé, son réglage est connu et son effet vérifié sur l'URL déployée, ou l'accès manquant est rendu comme action externe.
## Périmètre
Respecter le périmètre déjà autorisé par la demande ; une autorisation existante n'exige pas une nouvelle confirmation. Une préparation ou un audit ne vaut pas autorisation de déploiement, de migration, de changement DNS, de publication ou d'envoi réel. Un envoi en masse ou une opération destructive exige son propre périmètre.
## Compte rendu
1. Verdict : **prêt**, **prêt avec réserves** ou **bloqué**.
2. Preuves des validations exécutées (commandes, statuts, observations).
3. Variables ou actions externes restantes, sans valeur secrète.
4. Avertissements préexistants séparés des régressions.
**Prêt** exige : portes qualité passées, stack saine, parcours critiques prouvés jusqu'à leur effet, indexation conforme à chaque environnement.
CELUGA_SKILL_EOF
mkdir -p "$dir/agents"
cat > "$dir/agents/openai.yaml" <<'CELUGA_SKILL_EOF'
interface:
display_name: "MEP Celuga"
short_description: "Sécuriser les mises en production Celuga"
default_prompt: "Utilise $mep-celuga pour préparer et vérifier la mise en production de ce projet."
CELUGA_SKILL_EOF
mkdir -p "$dir/references"
cat > "$dir/references/email-smtp.md" <<'CELUGA_SKILL_EOF'
# E-mail et SMTP — diagnostic de bout en bout
Tracer le fournisseur choisi, l'adaptateur Payload, le code qui construit le message, puis la boîte et le routage du destinataire. Lire d'abord le code du projet ; points d'entrée habituels :
- `apps/cms/src/lib/email.ts` : `getEmailConfig()` et adaptateur ;
- `apps/cms/src/lib/mail-transport.ts` : choix du fournisseur à l'exécution, Brevo, SMTP externe ;
- `apps/cms/src/lib/notifications.ts`, `plugins/forms.ts`, `endpoints/forms.ts` : notifications des demandes ;
- `apps/cms/src/endpoints/mail-test.ts` : bouton de test de l'administration.
## Fournisseurs
**Configuration → Intégrations** choisit le service ; la présence d'une clé ne le sélectionne pas. Les réglages s'appliquent aux prochains envois, sans redémarrage, y compris aux mails système de Payload. Les clés sont chiffrées avec `PAYLOAD_SECRET` : si ce secret change, les ressaisir.
| Service | Réglage |
| --- | --- |
| SMTP interne (défaut) | `EMAIL_HOST`, `EMAIL_USER`, `EMAIL_PASS`, `EMAIL_PORT` dans l'environnement du CMS |
| SMTP externe | serveur, port et adresse d'expédition dans l'administration, TLS obligatoire ; mot de passe facultatif, utilisateur requis s'il est renseigné |
| Brevo | clé API v3 (une clé SMTP ne convient pas) et expéditeur validé, avec domaine authentifié chez Brevo |
| Désactivé | la demande est conservée sans notification, et cet état est affiché |
## Transport interne
Noms : `EMAIL_HOST`, `EMAIL_USER`, `EMAIL_PASS`, `EMAIL_PORT`. Ne pas inventer `SMTP_*` ou `EMAIL_PASSWORD`.
- `EMAIL_HOST` est obligatoire, sans valeur par défaut ; le CMS refuse sinon de démarrer. `localhost` pour Mailpit local.
- `EMAIL_USER` : adresse complète, utilisée comme expéditeur et pour l'authentification.
- `EMAIL_PASS` vide : aucune authentification, le relais doit autoriser l'IP de sortie du serveur. Renseigné : `auth` envoyé.
- `EMAIL_PORT` : entier, 587 par défaut avec STARTTLS obligatoire ; 465 : TLS direct. Seul un hôte local sans mot de passe échappe à l'exigence TLS.
- `skipVerify: true` reporte la connexion à l'envoi : un build réussi ou un CMS démarré ne prouve pas l'accès SMTP. Aucun contact SMTP pendant un build.
Valeurs injectées au runtime du CMS par Compose ; une modification exige un redéploiement du CMS. Ne pas désactiver la validation TLS. Un relais autorisé par IP refuse les envois depuis un poste local : y utiliser Mailpit.
## Destinataires et notifications
`FORM_NOTIFICATION_TO` ou le destinataire de l'administration, sinon `EMAIL_USER`. Une adresse de réponse propre au message, comme celle du visiteur, conserve sa priorité.
Les notifications passent par une file avec réservation : au plus cinq tentatives, réservations expirées reprises après cinq minutes. Un bouton « Relancer la notification » remet une demande en attente. SMTP ne garantit pas une livraison exactement une fois : un crash après acceptation peut provoquer un doublon. Contrôler le serveur avant de relancer.
Formulaire public : quota par IP, CAPTCHA vérifié côté CMS avant enregistrement, secret `form` dérivé entre Web et CMS. Si le CAPTCHA est indisponible, l'envoi est refusé sans perdre les champs saisis.
## Lire les erreurs par étape
| Erreur | Ce qui est déjà validé | Correction à examiner |
| --- | --- | --- |
| `535`, `EAUTH`, `AUTH PLAIN` | Connexion et dialogue SMTP atteints | Identité, secret, autorisation du compte |
| `550 5.1.1`, `RCPT TO`, `User unknown` | Dialogue jusqu'au destinataire | Adresse ou alias absent, destinataire obsolète, routage du domaine |
| `Relay access denied` | Serveur joignable | IP de sortie autorisée, droits du compte, expéditeur |
| Timeout, DNS, connexion refusée | Rien au-delà du runtime | Hôte, port, réseau, pare-feu |
| Certificat, STARTTLS | Connexion TLS tentée ou exigée | Certificat, nom d'hôte, capacité STARTTLS du serveur |
| API Brevo 401, 400 | API jointe | Clé API v3, expéditeur validé |
Une erreur `RCPT TO` ne justifie pas de modifier l'authentification à l'aveugle. `transport.verify()` ne prouve pas qu'un destinataire sera accepté. Un mail accepté par une API peut encore être rejeté : lire les journaux du fournisseur et les indésirables.
Critère de fin : le message de test autorisé est reçu dans une boîte contrôlée, destinataire et contenu relus, ou le blocage externe précis est identifié. La réception dans Mailpit prouve le circuit local, pas le compte ni les boîtes de production.
CELUGA_SKILL_EOF
mkdir -p "$HOME/.claude/skills" "$HOME/.codex/skills"
[ -e "$HOME/.claude/skills/mep-celuga" ] || ln -s "$dir" "$HOME/.claude/skills/mep-celuga"
[ -e "$HOME/.codex/skills/mep-celuga" ] || ln -s "$dir" "$HOME/.codex/skills/mep-celuga"
SKILL.md
---
name: mep-celuga
description: Préparer, auditer ou dépanner une mise en production Celuga. Utiliser pour une MEP, une préproduction, un déploiement Coolify/Docker, un contrôle SMTP, Brevo ou formulaire Payload, ou une vérification d'indexation SEO selon l'environnement.
---
# MEP Celuga
Sécuriser le passage en ligne par des **preuves observables** sur l'environnement réellement déployé. Un build qui passe n'est pas une MEP prête.
Charger **celuga-docker-payload-astro** pour la stack, **celuga-astro-payload-cache** pour les preuves de cache, **celuga-seo** pour le contrat SEO.
## 1. Établir le contexte
1. Lire `AGENTS.md`, `README.md` et `docs/DEPLOIEMENT.md`, `docs/ENVIRONNEMENT.md` s'ils existent. Une documentation peut retarder sur le code : en cas de doute, le code et le Compose priment.
2. `git status` : préserver les modifications existantes, ne rien committer sans demande. Vérifier si un push déclenche un déploiement.
3. Identifier stack, plateforme, domaine de production et domaines dev/préprod, et pour chacun son statut.
4. Classer chaque variable selon son lecteur : les `PUBLIC_*` du Web sont fixées au **build** et lues aussi au runtime ; le reste est lu au runtime. Vérifier son passage par les arguments de build, le stage final et Compose : seule une variable référencée par Compose arrive au conteneur.
5. Identifier la base visée par le `.env` local **avant** toute commande : elle peut être partagée.
Ne jamais afficher ni committer un secret ; pour comparer deux valeurs, rapporter seulement présence ou égalité.
**Terminé quand :** un tableau domaine → environnement et un tableau variable → moment de lecture → source sont établis.
## 2. Portes qualité locales
Utiliser les scripts du dépôt ; habituellement :
```bash
bun install --frozen-lockfile
bun run verify # tests, lint, frontières, typage, builds, EcoIndex de l'accueil
bun audit # aucune vulnérabilité high
```
`verify` n'applique aucune migration et ne prouve ni une connexion runtime, ni le transport e-mail. La mesure EcoIndex exige une base joignable ; sans base, `CELUGA_ECOINDEX=off` la saute et le rapport doit le dire. Les `.astro` ne sont pas typés.
Après `bun run generate`, aucun diff ne doit rester. Tests navigateur : `bun run test:e2e`, sur une instance de test ; les configurations refusent les domaines de production. Tests sur base réelle : nom de base `*_test` local uniquement.
Pour l'éco-score : mesurer un **build de production** servi compressé ; la cible du manifeste ne porte que sur l'accueil. Une mesure en `dev` ne vaut rien.
Séparer les échecs préexistants (vérifiés sur la branche principale) des régressions.
**Terminé quand :** chaque commande a un résultat noté (passe, échec préexistant, régression) avec sa sortie utile.
## 3. Stack déployée
Commencer par les observations en lecture seule :
- CMS et Web `healthy`, `/api/health` à 200 (la sonde du Web prouve Web → CMS → PostgreSQL) ;
- migrations relues et appliquées par le service ponctuel, après sauvegarde : le CMS ne migre rien au démarrage ; `schema:check` sur la cible si autorisé ;
- volume uploads persistant sur le CMS, sauvegarde et restauration disponibles avant une opération risquée ;
- secrets réels et distincts : `SITE_SECRET` identique côté CMS et Web, `PAYLOAD_SECRET` inchangé sur une installation existante (il chiffre les clés d'intégration) ;
- premier administrateur créé par `admin:create`, variables retirées ensuite ;
- cache : tags, preview hors cache, erreurs non stockées, selon **celuga-astro-payload-cache** ;
- tâches de fond actives (panneau Santé, sonde), notifications en échec relancées ;
- confidentialité : durée de conservation des demandes définie avec le client ;
- médias référencés présents sur le serveur (`/media/*` à 200), logs CMS et Web relus après le smoke test.
Arrêter le CMS pour prouver le 503 est une simulation de panne : à faire sur un pilote isolé ou une cible où l'interruption est autorisée.
**Terminé quand :** chaque point est prouvé par une commande ou une observation, ou listé comme action externe restante.
## 4. Parcours critiques
Sur le domaine déployé : accueil, une page, un article, 404, preview, un média, un document, formulaire critique, `/robots.txt`, `/sitemap.xml`, `/llms.txt` si présent, `<head>` (titre unique, canonical, `og:image` en 200), chaque langue active.
Formulaire et e-mail : lire [`references/email-smtp.md`](references/email-smtp.md) dès qu'un envoi est en jeu. N'envoyer qu'un message de test vers une boîte contrôlée, et seulement si la demande l'autorise. CAPTCHA : vérifier que les vraies clés s'appliquent sur le domaine final, et pas des clés de test.
**Terminé quand :** chaque parcours a atteint son effet observable (page rendue, fichier servi, message **reçu**) ou son blocage externe exact est identifié.
## 5. Indexation par environnement
Tout domaine public est considéré **indexable** tant que le réglage contraire n'a pas été observé.
`PUBLIC_ALLOW_INDEXING` : `true` uniquement en production ; il pilote meta robots, `X-Robots-Tag` et `/robots.txt`. Le changer impose un rebuild. Le domaine du CMS répond toujours `Disallow: /`. Un Compose qui fige la valeur l'emporte sur l'interface de la plateforme : le lire.
```sh
curl -sI https://domaine.example | grep -i x-robots-tag
curl -s https://domaine.example/robots.txt
curl -s https://domaine.example/ | grep -io '<meta name="robots"[^>]*>'
curl -s https://domaine.example/sitemap.xml | head
```
En production, contrôler aussi l'absence de blocage WAF/CDN pour les crawlers de recherche et la soumission du sitemap (voir **celuga-seo**). Une requête `curl -A` vérifie seulement la réponse à cette requête ; compléter par les outils webmaster et les journaux.
Si un environnement non productif a déjà été indexé, rapporter l'action et, seulement sur demande, la suppression dans les outils webmaster.
**Terminé quand :** chaque domaine connu est classé, son réglage est connu et son effet vérifié sur l'URL déployée, ou l'accès manquant est rendu comme action externe.
## Périmètre
Respecter le périmètre déjà autorisé par la demande ; une autorisation existante n'exige pas une nouvelle confirmation. Une préparation ou un audit ne vaut pas autorisation de déploiement, de migration, de changement DNS, de publication ou d'envoi réel. Un envoi en masse ou une opération destructive exige son propre périmètre.
## Compte rendu
1. Verdict : **prêt**, **prêt avec réserves** ou **bloqué**.
2. Preuves des validations exécutées (commandes, statuts, observations).
3. Variables ou actions externes restantes, sans valeur secrète.
4. Avertissements préexistants séparés des régressions.
**Prêt** exige : portes qualité passées, stack saine, parcours critiques prouvés jusqu'à leur effet, indexation conforme à chaque environnement.
agents/openai.yaml
interface:
display_name: "MEP Celuga"
short_description: "Sécuriser les mises en production Celuga"
default_prompt: "Utilise $mep-celuga pour préparer et vérifier la mise en production de ce projet."
references/email-smtp.md
# E-mail et SMTP — diagnostic de bout en bout
Tracer le fournisseur choisi, l'adaptateur Payload, le code qui construit le message, puis la boîte et le routage du destinataire. Lire d'abord le code du projet ; points d'entrée habituels :
- `apps/cms/src/lib/email.ts` : `getEmailConfig()` et adaptateur ;
- `apps/cms/src/lib/mail-transport.ts` : choix du fournisseur à l'exécution, Brevo, SMTP externe ;
- `apps/cms/src/lib/notifications.ts`, `plugins/forms.ts`, `endpoints/forms.ts` : notifications des demandes ;
- `apps/cms/src/endpoints/mail-test.ts` : bouton de test de l'administration.
## Fournisseurs
**Configuration → Intégrations** choisit le service ; la présence d'une clé ne le sélectionne pas. Les réglages s'appliquent aux prochains envois, sans redémarrage, y compris aux mails système de Payload. Les clés sont chiffrées avec `PAYLOAD_SECRET` : si ce secret change, les ressaisir.
| Service | Réglage |
| --- | --- |
| SMTP interne (défaut) | `EMAIL_HOST`, `EMAIL_USER`, `EMAIL_PASS`, `EMAIL_PORT` dans l'environnement du CMS |
| SMTP externe | serveur, port et adresse d'expédition dans l'administration, TLS obligatoire ; mot de passe facultatif, utilisateur requis s'il est renseigné |
| Brevo | clé API v3 (une clé SMTP ne convient pas) et expéditeur validé, avec domaine authentifié chez Brevo |
| Désactivé | la demande est conservée sans notification, et cet état est affiché |
## Transport interne
Noms : `EMAIL_HOST`, `EMAIL_USER`, `EMAIL_PASS`, `EMAIL_PORT`. Ne pas inventer `SMTP_*` ou `EMAIL_PASSWORD`.
- `EMAIL_HOST` est obligatoire, sans valeur par défaut ; le CMS refuse sinon de démarrer. `localhost` pour Mailpit local.
- `EMAIL_USER` : adresse complète, utilisée comme expéditeur et pour l'authentification.
- `EMAIL_PASS` vide : aucune authentification, le relais doit autoriser l'IP de sortie du serveur. Renseigné : `auth` envoyé.
- `EMAIL_PORT` : entier, 587 par défaut avec STARTTLS obligatoire ; 465 : TLS direct. Seul un hôte local sans mot de passe échappe à l'exigence TLS.
- `skipVerify: true` reporte la connexion à l'envoi : un build réussi ou un CMS démarré ne prouve pas l'accès SMTP. Aucun contact SMTP pendant un build.
Valeurs injectées au runtime du CMS par Compose ; une modification exige un redéploiement du CMS. Ne pas désactiver la validation TLS. Un relais autorisé par IP refuse les envois depuis un poste local : y utiliser Mailpit.
## Destinataires et notifications
`FORM_NOTIFICATION_TO` ou le destinataire de l'administration, sinon `EMAIL_USER`. Une adresse de réponse propre au message, comme celle du visiteur, conserve sa priorité.
Les notifications passent par une file avec réservation : au plus cinq tentatives, réservations expirées reprises après cinq minutes. Un bouton « Relancer la notification » remet une demande en attente. SMTP ne garantit pas une livraison exactement une fois : un crash après acceptation peut provoquer un doublon. Contrôler le serveur avant de relancer.
Formulaire public : quota par IP, CAPTCHA vérifié côté CMS avant enregistrement, secret `form` dérivé entre Web et CMS. Si le CAPTCHA est indisponible, l'envoi est refusé sans perdre les champs saisis.
## Lire les erreurs par étape
| Erreur | Ce qui est déjà validé | Correction à examiner |
| --- | --- | --- |
| `535`, `EAUTH`, `AUTH PLAIN` | Connexion et dialogue SMTP atteints | Identité, secret, autorisation du compte |
| `550 5.1.1`, `RCPT TO`, `User unknown` | Dialogue jusqu'au destinataire | Adresse ou alias absent, destinataire obsolète, routage du domaine |
| `Relay access denied` | Serveur joignable | IP de sortie autorisée, droits du compte, expéditeur |
| Timeout, DNS, connexion refusée | Rien au-delà du runtime | Hôte, port, réseau, pare-feu |
| Certificat, STARTTLS | Connexion TLS tentée ou exigée | Certificat, nom d'hôte, capacité STARTTLS du serveur |
| API Brevo 401, 400 | API jointe | Clé API v3, expéditeur validé |
Une erreur `RCPT TO` ne justifie pas de modifier l'authentification à l'aveugle. `transport.verify()` ne prouve pas qu'un destinataire sera accepté. Un mail accepté par une API peut encore être rejeté : lire les journaux du fournisseur et les indésirables.
Critère de fin : le message de test autorisé est reçu dans une boîte contrôlée, destinataire et contenu relus, ou le blocage externe précis est identifié. La réception dans Mailpit prouve le circuit local, pas le compte ni les boîtes de production.