Les e-mails restent au cœur de la vie numérique des entreprises. Support client, demandes commerciales, factures, alertes internes, candidatures, confirmations de commande, relances fournisseurs : tout finit tôt ou tard dans une boîte de réception. Le problème n’est pas seulement le volume. Le vrai problème, c’est le tri.
Sans classification automatique, chaque e-mail doit être lu et orienté manuellement. C’est une perte de temps considérable, source d’erreurs et difficile à scaler. Un système de classification automatique peut traiter des milliers d’e-mails par jour, les router vers les bonnes équipes et libérer du temps pour des tâches à valeur ajoutée.
Dans ce tutoriel, vous allez construire un système de classification d’e-mails avec Python, de la préparation des données jusqu’au déploiement d’un modèle capable de prédire automatiquement la catégorie d’un e-mail entrant.
Comprendre le problème de classification d’e-mails
La classification de texte est l’une des tâches les plus courantes en NLP (Natural Language Processing). Il s’agit d’assigner automatiquement une étiquette ou une catégorie à un texte en entrée.
Types de classification d’e-mails
- Classification binaire : spam vs. non-spam
- Classification multi-classes : support, commercial, comptabilité, RH, technique…
- Classification multi-labels : un e-mail peut appartenir à plusieurs catégories
Approches disponibles
- Approche classique : TF-IDF + algorithme de ML (SVM, Random Forest, Naive Bayes)
- Approche deep learning : transformers (BERT, CamemBERT pour le français)
- Approche LLM avec few-shot : utiliser ChatGPT ou un modèle local avec des exemples
Dans ce tutoriel, nous allons couvrir l’approche classique (robuste, rapide, interprétable) et l’approche LLM (sans données d’entraînement).
Préparer l’environnement et les données
Installation des dépendances
pip install scikit-learn pandas numpy matplotlib joblib
pip install nltk spacy
python -m spacy download fr_core_news_sm
Structure d’un dataset d’e-mails
import pandas as pd
# Exemple de dataset
data = {
"email": [
"Bonjour, mon produit est défectueux, je voudrais un remboursement.",
"Suite à notre échange, je vous transmets le devis mis à jour.",
"Votre facture du 01/04 est en attente de règlement.",
"Candidature pour le poste de développeur Python.",
"Le serveur de production est down depuis 30 minutes.",
"Nous souhaiterions organiser une démo de votre solution.",
],
"categorie": [
"support",
"commercial",
"comptabilite",
"rh",
"technique",
"commercial"
]
}
df = pd.DataFrame(data)
print(df["categorie"].value_counts())
💡 Conseil : la qualité d’un classifieur dépend directement de la qualité et de la quantité des données d’entraînement. Visez au minimum 50 à 100 exemples par catégorie pour un modèle utilisable en production.
Construire un classifieur classique avec scikit-learn
Prétraitement du texte
import re
import unicodedata
def preprocess_email(text: str) -> str:
"""Nettoie et normalise le texte d'un e-mail."""
# Supprimer les métadonnées (De:, À:, Objet:, etc.)
text = re.sub(r'^(De|À|Cc|Objet|Date)s*:.*$', '', text, flags=re.MULTILINE | re.IGNORECASE)
# Supprimer les URLs
text = re.sub(r'https?://S+', '', text)
# Supprimer les adresses e-mail
text = re.sub(r'S+@S+.S+', '', text)
# Normaliser les accents et mettre en minuscules
text = unicodedata.normalize('NFC', text.lower())
# Supprimer la ponctuation excessive
text = re.sub(r'[^ws]', ' ', text)
# Supprimer les espaces multiples
text = re.sub(r's+', ' ', text).strip()
return text
# Test
email_test = "De: client@example.com
Bonjour, mon produit réf. #1234 est défectueux !"
print(preprocess_email(email_test))
Pipeline complet d’entraînement
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report
import joblib
# Préparation des données
df["email_clean"] = df["email"].apply(preprocess_email)
X = df["email_clean"]
y = df["categorie"]
# Séparation train/test
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Pipeline TF-IDF + Régression Logistique
pipeline = Pipeline([
('tfidf', TfidfVectorizer(
ngram_range=(1, 2), # unigrammes et bigrammes
max_features=10000,
sublinear_tf=True, # normalisation log
min_df=2,
)),
('classifier', LogisticRegression(
max_iter=1000,
C=1.0,
class_weight='balanced' # compense les classes déséquilibrées
))
])
# Entraînement
pipeline.fit(X_train, y_train)
# Évaluation
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))
# Sauvegarde du modèle
joblib.dump(pipeline, "email_classifier.joblib")
print("Modèle sauvegardé.")
Comprendre les métriques d’évaluation
- Précision : parmi les e-mails classés dans une catégorie, combien y appartiennent vraiment ?
- Rappel : parmi les e-mails d’une catégorie, combien ont été correctement identifiés ?
- F1-score : moyenne harmonique entre précision et rappel
Améliorer les performances du modèle
Comparaison de plusieurs algorithmes
from sklearn.svm import LinearSVC
from sklearn.naive_bayes import MultinomialNB
from sklearn.ensemble import RandomForestClassifier
import numpy as np
classifiers = {
"Logistic Regression": LogisticRegression(max_iter=1000, class_weight='balanced'),
"SVM Linéaire": LinearSVC(class_weight='balanced'),
"Naive Bayes": MultinomialNB(),
}
tfidf = TfidfVectorizer(ngram_range=(1, 2), max_features=10000, sublinear_tf=True)
X_tfidf = tfidf.fit_transform(X)
for name, clf in classifiers.items():
scores = cross_val_score(clf, X_tfidf, y, cv=5, scoring='f1_macro')
print(f"{name}: F1 = {scores.mean():.3f} (+/- {scores.std():.3f})")
Optimisation des hyperparamètres
from sklearn.model_selection import GridSearchCV
param_grid = {
'tfidf__max_features': [5000, 10000],
'tfidf__ngram_range': [(1, 1), (1, 2)],
'classifier__C': [0.1, 1.0, 10.0],
}
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='f1_macro', n_jobs=-1)
grid_search.fit(X_train, y_train)
print("Meilleurs paramètres:", grid_search.best_params_)
print("Meilleur F1:", grid_search.best_score_)
💡 Conseil : le SVM linéaire (LinearSVC) est souvent le meilleur choix pour la classification de texte en termes de rapport performance/vitesse. La régression logistique est légèrement moins performante mais donne des probabilités de confiance, ce qui est utile pour les cas ambigus.
Classification avec un LLM pour les cas sans données d’entraînement
Si vous n’avez pas de données labellisées, vous pouvez utiliser un LLM avec quelques exemples (few-shot learning).
Classification zero-shot avec Ollama
import requests
CATEGORIES = ["support", "commercial", "comptabilite", "rh", "technique", "spam"]
def classify_email_llm(email_text: str, categories: list[str]) -> dict:
categories_str = ", ".join(categories)
prompt = f"""Tu es un système de classification d'e-mails professionnels.
Catégories disponibles : {categories_str}
Règles :
- Réponds UNIQUEMENT avec le nom exact d'une catégorie de la liste
- Ne donne aucune explication
- Si tu n'es pas sûr, choisis la plus probable
E-mail à classifier :
{email_text}
Catégorie :"""
response = requests.post(
"http://localhost:11434/api/chat",
json={
"model": "gemma3",
"messages": [{"role": "user", "content": prompt}],
"stream": False
},
timeout=30
)
predicted = response.json()["message"]["content"].strip().lower()
# Valider que la prédiction est dans la liste
if predicted in categories:
return {"category": predicted, "confidence": "high"}
else:
# Trouver la catégorie la plus proche
for cat in categories:
if cat in predicted:
return {"category": cat, "confidence": "medium"}
return {"category": "unknown", "confidence": "low"}
# Test
email = "Bonjour, je n'arrive pas à me connecter à mon compte depuis ce matin."
result = classify_email_llm(email, CATEGORIES)
print(result)
Déployer en production : API et intégration
Créer une API de classification avec FastAPI
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
app = FastAPI(title="Email Classifier API")
model = joblib.load("email_classifier.joblib")
class EmailRequest(BaseModel):
email: str
class ClassificationResult(BaseModel):
category: str
confidence: float
@app.post("/classify", response_model=ClassificationResult)
def classify_email(request: EmailRequest):
email_clean = preprocess_email(request.email)
category = model.predict([email_clean])[0]
proba = model.predict_proba([email_clean]).max()
return ClassificationResult(
category=category,
confidence=round(float(proba), 3)
)
# Lancer : uvicorn api:app --reload
Intégration avec un système de messagerie
Une fois l’API déployée, vous pouvez l’appeler depuis un webhook ou un script de surveillance des e-mails pour router automatiquement chaque nouveau message vers la bonne équipe.
import imaplib, email, requests
def watch_inbox_and_classify(imap_server, user, password):
mail = imaplib.IMAP4_SSL(imap_server)
mail.login(user, password)
mail.select("INBOX")
_, msg_ids = mail.search(None, "UNSEEN")
for msg_id in msg_ids[0].split():
_, data = mail.fetch(msg_id, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
body = ""
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True).decode()
else:
body = msg.get_payload(decode=True).decode()
# Classifier via API
response = requests.post(
"http://localhost:8000/classify",
json={"email": body}
)
result = response.json()
print(f"Email classifié : {result['category']} (confiance: {result['confidence']})")
Surveiller et améliorer le modèle en continu
Détecter la dérive du modèle
Un modèle de classification se dégrade avec le temps si le langage des e-mails évolue. Mettez en place une surveillance simple :
- Journalisez toutes les prédictions avec leur score de confiance
- Alertez quand le score de confiance moyen descend sous un seuil (ex. 0.65)
- Créez une file de révision manuelle pour les e-mails avec faible confiance
💡 Conseil : planifiez un réentraînement mensuel ou trimestriel du modèle avec les nouvelles données collectées. Les modèles de classification de texte ont tendance à se dégrader progressivement au fil des mois si le vocabulaire métier évolue.
Conclusion
Construire un système de classification automatique d’e-mails avec Python est un projet accessible, à fort impact opérationnel. Avec scikit-learn et une centaine d’exemples labellisés, vous pouvez obtenir un modèle suffisamment précis pour automatiser le tri de 80 à 90% des e-mails entrants. Pour les cas sans données, l’approche LLM avec few-shot learning offre un point de départ immédiat.
La clé du succès n’est pas tant l’algorithme choisi que la qualité des données, la rigueur du prétraitement et la mise en place d’un processus de surveillance et d’amélioration continue.
Vous souhaitez approfondir le NLP, l’automatisation Python ou le déploiement d’applications IA ? Retrouvez tous nos tutoriels pratiques sur ITSkillsCenter.io pour développer des compétences directement applicables dans votre contexte professionnel.