Règles de nettoyage
Pipeline déterministe appliqué à chaque email ingéré. Les règles sont exécutées dans l'ordre ci-dessous. Toute modification de leur comportement s'accompagne d'un bump de CLEANING_VERSION— ce qui permet de rejouer le nettoyage sur l'ensemble des emails bruts déjà en base et de comparer les deux versions.
- R-01
Sélection de la partie MIME
étape 1 / 10On choisit d'abord la partie texte de l'email à nettoyer. Si la version `text/plain` existe et fait au moins 200 caractères, on la prend telle quelle (chemin minimal). Sinon, on bascule sur la version `text/html` — ce qui active les règles suivantes de suppression HTML.
Seuil de bascule- text/plain ≥ 200 caractères → chemin texte
- sinon → chemin HTML
- R-02
Décodage
étape 2 / 10Les emails reçus peuvent être encodés en quoted-printable ou base64, et contiennent des entités HTML (`&`, `'`, etc.). Cette étape est effectuée en amont par `mailparser` au moment de l'ingestion : le `html_body` et le `text_body` stockés en base sont déjà décodés en UTF-8 et prêts à consommer. Aucune passe supplémentaire n'est nécessaire ici.
Délégué àmailparser (étape d'ingestion, pas de nettoyage).
- R-03
Suppression structurelle HTML
étape 3 / 10On enlève les balises qui n'ont pas de sens dans un texte de lecture : scripts, styles, métadonnées, liens CSS, commentaires HTML. Cela élimine aussi les éventuels JS embarqués qui pourraient s'activer si l'email était rendu sans sandbox.
Balises supprimées- <script>
- <style>
- <head>
- <meta>
- <link>
- <noscript>
- commentaires HTML (<!-- ... -->)
- R-04
Suppression des éléments cachés
étape 4 / 10Beaucoup de newsletters insèrent des éléments invisibles (pixels de tracking, contenu de précédent A/B test). On les détecte via leur style inline ou leurs dimensions 1×1 et on les retire.
Critères de détection (style inline)- display:none
- visibility:hidden
- opacity:0
- dimensions explicites width="1" height="1"
- R-05
Suppression des images de tracking
étape 5 / 10Les expéditeurs mesurent l'ouverture des emails via des pixels invisibles hébergés sur des domaines dédiés (Mailchimp, SendGrid, Substack…). On supprime tout `<img>` dont le `src` contient un pattern connu — liste ci-dessous, versionnée avec `CLEANING_VERSION`.
Patterns traqués- list-manage.com
- list-manage1.com
- mailchimp.com/track
- sendgrid.net/wf/open
- sendgrid.net/tracking
- beehiiv.net/open
- beehiv.net/open
- substack.com/pixel
- substackcdn.com/pixel
- email.substack.com/tracking
- convertkit.com/open
- kit.com/open
- customer.io/e/o/
- /open.gif
- /pixel.gif
- /tracking.gif
Match par sous-chaîne (case-sensitive). Ajouts → bump de CLEANING_VERSION.
- R-06
Nettoyage des URLs
étape 6 / 10Les liens des newsletters sont truffés de paramètres de tracking (utm_*, fbclid, mc_cid…). On les supprime sans toucher au reste de l'URL : le lien reste fonctionnel, mais on ne balance plus l'identifiant d'ouverture / de campagne à la page de destination. Appliqué sur les `href` des `<a>` **et** sur les URLs trouvées dans le corps texte.
Paramètres supprimés- utm_source
- utm_medium
- utm_campaign
- utm_term
- utm_content
- utm_id
- mc_cid
- mc_eid
- fbclid
- gclid
- gclsrc
- dclid
- msclkid
- _hsenc
- _hsmi
- __hstc
- __hssc
- __hsfp
- R-07
Conversion en Markdown
étape 7 / 10Le HTML nettoyé est converti en Markdown via `turndown` + plugin GFM. On préserve la structure sémantique (titres h1–h6, paragraphes, listes, liens, citations, emphases, tables) tout en écartant les styles. C'est le format le plus digeste pour un LLM en aval.
Options turndown- headingStyle: atx (# titres)
- bulletListMarker: - (listes)
- codeBlockStyle: fenced (```)
- plugin GFM activé (tables, strikethrough, task lists)
- R-08
Extraction du footer
étape 8 / 10Le bas de la plupart des newsletters contient un bloc boilerplate : lien unsubscribe, gestion des préférences, adresse postale de l'expéditeur. Ce n'est pas du bruit exploitable pour la synthèse, mais on ne le supprime pas : on le détecte par regex et on le stocke séparément dans `footer_extracted`. Le `cleaned_markdown` ne l'inclut plus.
Patterns de détection- unsubscribe
- manage preferences / manage your preferences
- view in browser / view this email in
- se désabonner
Détection best-effort. Les faux négatifs (footer non reconnu) laissent le footer dans le corps — c'est accepté pour la v1.
- R-09
Normalisation des espaces
étape 9 / 10Cosmétique final : on trim les espaces en fin de ligne, et on collapse les enchaînements de plus de 2 retours à la ligne en 2 retours. Résultat : un Markdown propre, sans blocs de blancs inutiles qui gonfleraient la consommation de tokens en aval.
- R-10
Versionnement
étape 10 / 10Ce n'est pas une transformation : c'est la discipline qui permet de tout rejouer. Chaque résultat de nettoyage est tagué avec la valeur de `CLEANING_VERSION` au moment du run. Si on modifie une règle, on bump la version, on relance le pipeline sur `emails_raw`, et on obtient une nouvelle série de `emails_cleaned` comparable à la précédente sans avoir perdu aucune donnée brute.
Contrat- Même (raw_email_id, cleaning_version) → même sortie, toujours (idempotence)
- Le brut (emails_raw) n'est jamais modifié par le nettoyage
- En cas d'erreur : row emails_cleaned avec status='failed' + error_message (pas de fail silencieux)
Invariants
- Idempotence. Même entrée + même
cleaning_versiondonnent toujours la même sortie. - Le brut est immuable. La table
emails_rawn'est jamais modifiée par le nettoyage — on peut toujours repartir du MIME original. - Fail-loud.En cas d'erreur pendant le nettoyage, une row
emails_cleanedavecstatus='failed'est créée avec le message d'erreur — jamais d'échec silencieux.