Email Suppression
La suppression list, c'est la blocklist GLOBALE de Trackily. Un email dedans est interdit d'enrollment ET d'envoi, sur toutes les listes simultanément. C'est la pierre angulaire de ta réputation de sender — la traiter à la légère, c'est se faire bannir par Gmail dans la semaine.

Concept
Les ESP sérieux distinguent trois mécanismes :
- Unsubscribe par liste (
email_subscribers.status='unsubscribed') — la personne dit "stop pour cette newsletter spécifique, mais OK pour les autres". L'opérateur peut la réenroller plus tard sur une liste différente, légitimement. - Suppression globale (
email_suppressionrow) — la personne dit "stop pour TOUT chez ce sender". Aucune liste n'a le droit de la contacter. C'est immuable côté worker. - Rebond dur — au moment de l'envoi, le serveur SMTP refuse définitivement l'adresse (« 550 user unknown »…). Trackily l'ajoute automatiquement à
email_suppression(raisonhard_bounce) et passe l'abonné enbounced.
Les retours, à brancher. Les refus immédiats du serveur SMTP sont vus à l'envoi. Un rebond différé (le serveur accepte, puis renvoie un avis de non-remise plus tard) et une plainte (« Marquer comme spam ») arrivent par la porte des retours : webhooks Brevo, Mailgun, Postmark, SendGrid, SES, ou avis bruts d'un serveur maison. Tant qu'elle n'est pas branchée, ils ne remontent pas — ajoute alors ces adresses à la main (UI, import CSV ou MCP, raison
complaint).
La suppression globale est cross-list par design : si quelqu'un te marque comme spam depuis la liste A, le sentiment se transfère à toutes tes autres listes (même domaine d'envoi, même IP). Mieux vaut ne plus jamais le contacter, point.
Pourquoi c'est critique
Trois raisons techniques :
1. Sender reputation
Les providers (Gmail, Outlook) calculent un score de réputation pour ton IP + ton domaine. Trois inputs principaux :
- Complaint rate :
% (Mark As Spam) sur total envois. Au-dessus de 0.3%, tu commences à passer en spam. À 1%, tu es blacklisté. - Bounce rate :
% hard bounces sur total envois. Au-dessus de 5%, idem. - Engagement : opens, clicks, replies. Le 0 absolu (envoi à des adresses mortes) tue la réputation.
Si tu n'as pas de suppression list, tu ré-envoies à des adresses qui ont déjà bounce / complained → ton score explose → tous tes mails partent en spam, même vers des destinataires en or.
2. Coût
Mailgun et compagnie te facturent par tentative d'envoi, pas par succès. Renvoyer 50k mails à des adresses suppressed, c'est 50k$/mois en pure perte (au pricing typique).
3. Compliance
GDPR + CAN-SPAM + CASL : un opt-out doit être honoré universellement et immédiatement. Une personne qui se désinscrit d'une liste et te recontacte deux mois plus tard parce qu'elle a reçu un autre mail de toi via une liste différente → plainte CNIL légitime.
Le modèle de données
Schema email_suppression (volontairement minimaliste) :
| Colonne | Type | Note |
|---|---|---|
id |
SERIAL PK | |
email |
TEXT NOT NULL | normalisé en lowercase (cf. index unique) |
reason |
TEXT NOT NULL | automatiques : hard_bounce (refus immédiat ou rebond différé), complaint (plainte reçue), rebonds_temporaires_repetes ; à la main : manual (défaut de l'UI), complaint, unsubscribe, manual_mcp (défaut de l'outil MCP sans raison) |
added_at |
TIMESTAMPTZ DEFAULT NOW() | |
notes |
TEXT | libre, pour traçabilité |
Index unique : UNIQUE (lower(email)) — un email = une ligne max.
Qu'est-ce qui peuple cette table ?
Automatique
| Événement | Source | Reason stocké |
|---|---|---|
| Rebond dur immédiat (le serveur SMTP refuse définitivement l'adresse à l'envoi) | le moteur d'envoi (email-engine.js) |
hard_bounce |
| Rebond dur différé (avis de non-remise reçu après l'acceptation) | la porte des retours | hard_bounce |
| Trois rebonds temporaires sur trois envois distincts en 30 jours, sans ouverture ni clic depuis le premier | la porte des retours | rebonds_temporaires_repetes |
Plainte (« C'est du spam », ARF, spamreport, Complaint…) |
la porte des retours | complaint |
Un retour n'agit que s'il se rattache à un envoi de Trackily (en-tête X-Trackily-Send-Id, Message-ID, ou envoi récent à cette adresse) : un faux avis ne supprime personne. Une adresse déjà présente garde sa raison d'origine. Chaque retour reçu est inscrit dans le journal (email_retours, section Retours reçus de la page Suppression).
Ce qui ne peuple pas la suppression :
- un rebond temporaire (boîte pleine, serveur lent) : l'envoi est retenté jusqu'à 5 fois, puis passe
failed— l'adresse n'est pas supprimée (sauf trois rebonds temporaires reçus sur trois envois distincts, voir plus haut) ; - une erreur du serveur (identifiant refusé, connexion, relais refusé, IP sur liste noire, rejet de politique
5.7.x) : ce n'est jamais un rebond, le destinataire n'y est pour rien — à l'envoi comme dans un retour ; - un clic sur
{{unsubscribe_url}}ou le désabonnement en un clic (List-Unsubscribe, Gmail/Outlook) : l'abonné passeunsubscribedsur sa liste et ses envois en file sont annulés, mais l'adresse n'entre pas dans la suppression globale (voir les anti-patterns plus bas) ; - un retour qui ne se rattache à aucun envoi de Trackily (effet
sans_envoi), un avis ignoré (livraison, différé) ou illisible.
Manuel
- Tool
add_to_email_suppression - Bouton "Add to suppression" dans l'UI (page Suppression), ou l'import CSV de cette page
- Demande GDPR de suppression → l'une des deux voies ci-dessus
Comment Trackily applique la suppression
À trois moments distincts :
1. Au moment de l'enrollment
enrollSubscriber() (email-engine.js) regarde d'abord la suppression (comparaison sans la casse) et s'arrête là : { skipped: true, reason: 'suppressed' }. C'est silencieux : le subscriber n'est pas créé, aucun email ne sera envoyé. Comportement intentionnel pour éviter que l'opérateur (ou un script qui spam-enroll) puisse réveiller un email mort.
2. À la constitution d'un envoi ponctuel
L'audience d'un envoi ponctuel (et le compte d'un segment) exclut les adresses de la suppression : elles n'entrent même pas en file. Les étapes de séquence, elles, sont mises en file sans cette lecture — c'est le point 3 qui les arrête.
3. Au moment de l'envoi
Dernière vérification juste avant l'appel transport.sendMail(), pour TOUT envoi (étape, envoi ponctuel, confirmation) : une adresse ajoutée à la suppression après sa mise en file passe skipped (motif suppressed) et ne reçoit rien.
Et sur la page de préférences : une adresse supprimée la voit entièrement désactivée, avec la phrase qui l'explique — elle ne peut ni se réinscrire à une liste, ni régler quoi que ce soit. La page ne réinscrit jamais non plus une liste où l'adresse a rebondi ou porté plainte.
Ajouter à la suppression
Via MCP
// Request
{
"name": "add_to_email_suppression",
"arguments": {
"email": "marie@example.com",
"reason": "manual",
"notes": "GDPR removal request 2026-05-18 ticket #142"
}
}
// Response
{ "ok": true, "email": "marie@example.com" }
Idempotent : ré-appeler avec le même email ne crée pas de doublon (une ligne par adresse, casse ignorée) ; la raison et la note sont remplacées par les dernières.
Via l'UI
Sidebar → Email Marketing → Suppression → Add. Coller l'email, choisir une reason dans le dropdown, optionnellement saisir une note (pour le ticket de support, la date de demande GDPR, etc.).
Lister la suppression
// Request
{ "name": "list_email_suppression", "arguments": { "limit": 50 } }
// Response (excerpt)
{
"count": 2,
"suppressed": [
{
"id": 1024,
"email": "marie@example.com",
"reason": "manual",
"notes": "GDPR removal request 2026-05-18 ticket #142",
"added_at": "2026-05-18T18:22:11.421Z"
},
{
"id": 1023,
"email": "bounced@dead.example",
"reason": "hard_bounce",
"notes": "550 5.1.1 <bounced@dead.example>: Recipient address rejected",
"added_at": "2026-05-18T17:11:02.812Z"
}
]
}
Retirer de la suppression (par l'opérateur seulement)
Volontairement non exposé en MCP. Un LLM qui retire des emails de la suppression à grande échelle = catastrophe. Si tu dois vraiment retirer un email (faux positif documenté, demande explicite de la personne), fais-le depuis la page Suppression de l'admin (bouton de retrait sur la ligne, permission settings:write). Documente la raison dans un ticket interne : la ligne retirée ne laisse pas de trace.
Seuils de surveillance
À monitorer mensuellement, avec ces query rapides :
-- Croissance de la suppression sur 30 jours
SELECT reason, COUNT(*) AS n
FROM email_suppression
WHERE added_at >= NOW() - INTERVAL '30 days'
GROUP BY reason
ORDER BY n DESC;
-- Taux de bounce sur les 30 derniers jours
SELECT
COUNT(*) FILTER (WHERE status = 'bounced')::float / NULLIF(COUNT(*), 0) AS bounce_rate
FROM email_sends
WHERE created_at >= NOW() - INTERVAL '30 days';
Si bounce_rate > 3%, tu envoies à des listes périmées ou achetées — arrête tout et nettoie. Si complaint/jour augmente sensiblement, ton contenu pose problème (subject misleading, fréquence trop élevée, ciblage à côté).
Anti-patterns
- Vider la table pour "tester" — tu détruis l'historique de protection. Une fois supprimés, les hard-bounces vont re-bounce à la prochaine campagne et tuer ta réputation à la racine.
- Désactiver le check au scheduling — déjà arrivé "pour livrer une dernière fois" → 800 plaintes en une matinée → Mailgun ferme le compte.
- Confondre
unsubscribed(par liste) etemail_suppression(global) — un unsub depuis la liste A ne supprime PAS de la liste B par défaut. Si la politique opérateur est "un unsub = global", ajoute manuellement àemail_suppressionen plus de l'unsub par liste. - Ré-importer un CSV ancien sans dédup — ça enroll en masse des adresses suppressed (qui sont silencieusement skipped) mais aussi des adresses jamais consenties. Toujours filtrer le CSV contre la suppression list AVANT import.
Erreurs courantes
- "Subscribers s'enrollent mais ne reçoivent rien" — souvent un hit silencieux sur
email_suppression. Checklist_email_suppressionavec l'email en question. - Un rebond ou une plainte visible chez Mailgun/SES mais absent de la suppression — les retours sont-ils branchés ? Regarde « dernier reçu » dans Serveurs SMTP → Brancher les retours, puis le journal Retours reçus : effet
sans_envoi(le retour ne se rattache à aucun envoi de Trackily),note(rebond temporaire ou faute du serveur) ouillisible. Sinon, ajoute l'adresse à la main (raisoncomplaintpour une plainte). - Doublons en suppression — impossible côté DB (UNIQUE index lowercase). Si tu en vois deux pour la "même" adresse, c'est qu'une casse différente est passée en lowercase à un moment et plus à l'autre — bug à signaler.
Voir aussi
- Lists — où vivent les
email_subscribers - Sequences — quand on planifie un send, on check la suppression
- SMTP — rebond dur vs erreur du serveur, ce que le moteur capte à l'envoi
- Retours — brancher les rebonds différés et les plaintes
- Envois ponctuels — l'audience exclut la suppression dès sa constitution
- Centre de préférences — l'alternative douce à la désinscription : moins d'e-mails, ou une pause