Un agent qui trie la boîte mail
Classer les messages, préparer des brouillons et proposer des créneaux, sans jamais rien envoyer à ta place.
Deux cents messages non lus, dont peut-être six qui demandent une action de ta part aujourd'hui. Le coût n'est pas la lecture, c'est le tri : rouvrir la boîte quinze fois par jour pour vérifier qu'on n'a rien raté. Un agent fait très bien ce tri, à condition qu'on lui interdise formellement de répondre à ta place.
C'est la ligne rouge de ce guide, et elle n'est pas décorative. Un agent qui envoie un mail engage ton nom auprès d'un client, d'un candidat, d'une administration. Un agent qui prépare un brouillon ne fait que te faire gagner les huit minutes de rédaction. Le rapport bénéfice / risque est incomparable, et il coûte exactement une ligne de code de différence.
Deuxième point à regarder en face avant de commencer : ce qui transite. Une boîte mail professionnelle contient des noms, des adresses, des montants, parfois des données de santé ou des CV. Tout ce que l'agent lit part chez le fournisseur du modèle. Ce guide montre comment restreindre ce flux plutôt que comment l'ignorer.
Ce que l'agent fait, et ce qu'il ne fait pas
| Il fait | Il ne fait pas |
|---|---|
| Classer les messages par urgence et par type | Décider qu'un message n'a pas besoin de réponse |
| Rédiger un brouillon | Envoyer quoi que ce soit |
| Repérer les créneaux libres | Accepter ou refuser une invitation |
| Signaler une échéance | Créer un événement dans ton agenda |
La colonne de droite n'est pas une question de capacité technique. C'est une question de qui porte la responsabilité quand c'est faux.
Le périmètre du jeton ne suffit pas à empêcher l'envoi
Sur l'API Gmail, créer un brouillon (users.drafts.create) exige l'un des
périmètres gmail.compose, gmail.modify ou https://mail.google.com/. Le
périmètre gmail.readonly ne suffit pas. Or gmail.compose est décrit comme
« gérer les brouillons et envoyer des e-mails » : il n'existe pas de périmètre
qui autorise le brouillon sans autoriser l'envoi. La garantie « il n'envoie
pas » doit donc venir de ton code, qui n'appelle jamais la méthode d'envoi,
pas du consentement OAuth.
Avant de commencer
- Un compte Google Workspace ou Gmail, et un projet Google Cloud
- Python ou Node.js, et une clé API de modèle
- Une définition écrite de ce qui est « urgent » chez toi
1Demander le minimum de périmètres
Deux jetons valent mieux qu'un. Le premier lit, le second écrit des brouillons. Le script de tri n'utilise que le premier.
| Périmètre | Ce qu'il ouvre |
|---|---|
https://www.googleapis.com/auth/gmail.readonly | Lecture des messages et des réglages |
https://www.googleapis.com/auth/gmail.compose | Gestion des brouillons, et envoi |
https://www.googleapis.com/auth/calendar.readonly | Lecture des agendas accessibles |
https://www.googleapis.com/auth/calendar.events | Lecture et modification des événements |
Pour la préparation d'agenda décrite plus bas, calendar.readonly suffit :
l'agent propose des créneaux, c'est toi qui crées l'événement.
2Filtrer avant d'envoyer au modèle
C'est l'étape que tout le monde saute, et c'est la plus rentable, en coût comme en confidentialité. Le modèle n'a pas besoin du corps complet de deux cents messages pour les trier. L'expéditeur, l'objet et les trois cents premiers caractères suffisent dans la très grande majorité des cas.
import base64
from googleapiclient.discovery import build
service = build("gmail", "v1", credentials=creds)
reponse = service.users().messages().list(
userId="me", q="is:unread newer_than:2d", maxResults=60
).execute()
resume = []
for ref in reponse.get("messages", []):
msg = service.users().messages().get(
userId="me", id=ref["id"], format="metadata",
metadataHeaders=["From", "Subject", "Date"],
).execute()
entetes = {h["name"]: h["value"] for h in msg["payload"]["headers"]}
resume.append({
"id": ref["id"],
"de": entetes.get("From", ""),
"objet": entetes.get("Subject", ""),
"date": entetes.get("Date", ""),
"extrait": msg.get("snippet", "")[:300],
})
Le format="metadata" avec une liste d'en-têtes explicite est le point clé :
tu ne télécharges même pas le corps du message. Le snippet renvoyé par Gmail
donne déjà l'aperçu, et il est court par construction.
Ce qui ne doit pas partir ne doit pas être lu
Exclus des dossiers entiers en amont, avec la requête de recherche, plutôt
qu'en demandant au modèle d'ignorer certains sujets. -label:RH -label:Sante
dans le q coûte zéro jeton et ne dépend d'aucun jugement du modèle.
3Le prompt de tri
Un seul appel sur la liste entière. Le modèle voit tout d'un coup, ce qui lui permet de repérer les relances et les fils dupliqués.
Tu tries une boite mail professionnelle. Voici [N] messages au format
identifiant, expediteur, objet, date, extrait.
Classe chaque message dans exactement une categorie :
- ACTION : demande explicite qui attend une reponse de moi
- ECHEANCE : contient une date limite qui me concerne
- INFORMATION : a lire, sans action attendue
- IGNORER : notification automatique, newsletter, accuse de reception
Regles :
1. Dans le doute entre ACTION et INFORMATION, choisis ACTION.
2. Si plusieurs messages appartiennent au meme fil, garde le plus recent
et signale les autres comme relances.
3. Pour chaque ACTION et chaque ECHEANCE, ecris en une ligne ce qui est
attendu de moi, et la date si elle est mentionnee.
4. Ne resume pas les IGNORER. Donne seulement leur nombre.
5. Tu ne rediges aucune reponse a cette etape.
[LISTE]
Le point 5 n'est pas superflu. Sans lui, un modèle un peu zélé commence à proposer des formulations, et tu perds la lisibilité du tri.
4Rédiger des brouillons, sans envoi possible
Deuxième appel, uniquement sur les messages classés ACTION, et cette fois avec le corps complet du message. La règle : le script crée le brouillon, point.
brouillon = service.users().drafts().create(
userId="me",
body={"message": {"threadId": fil_id, "raw": corps_encode}},
).execute()
Il n'y a pas d'appel à drafts().send() ni à messages().send() dans le
fichier. C'est ça, la garantie. Si tu veux la rendre visible pour l'équipe,
ajoute un commentaire au-dessus qui dit pourquoi elle n'y est pas.
Le threadId place le brouillon dans le bon fil de discussion, ce qui te
permet de le relire dans son contexte plutôt que dans un dossier à part.
5Préparer l'agenda sans y toucher
Même logique côté agenda : l'agent lit les événements de la semaine, croise avec les ECHEANCE sorties du tri, et rend une proposition en texte. C'est toi qui cliques.
from datetime import datetime, timedelta, timezone
debut = datetime.now(timezone.utc)
fin = debut + timedelta(days=7)
cal = build("calendar", "v3", credentials=creds)
evenements = cal.events().list(
calendarId="primary",
timeMin=debut.isoformat(),
timeMax=fin.isoformat(),
singleEvents=True,
orderBy="startTime",
).execute().get("items", [])
Le singleEvents=True développe les événements récurrents en occurrences
réelles. Sans lui, une réunion hebdomadaire apparaît une seule fois et tes
créneaux « libres » sont faux.
Le rendu quotidien
Vise dix lignes. Trois actions, deux échéances, un créneau proposé, un compteur de messages ignorés. Un rapport plus long ne sera pas lu, et un agent secrétaire qui n'est pas lu ne fait qu'ajouter une notification de plus à ta journée.
Garde aussi une trace : la liste des identifiants de messages classés IGNORER, dans un fichier. Le jour où quelque chose passe à travers, tu sauras si c'est le tri qui a failli ou la collecte.
Quelques prompts pour aller plus loin
| Objectif | Prompt à adapter |
|---|---|
| Écrire ta définition de l'urgence | Voici 20 mails que j'ai traites le jour meme et 20 que j'ai laisses une semaine. Deduis-en une regle de classement en trois lignes, formulee pour un agent de tri. |
| Calibrer le tri | Voici ma consigne de tri et 40 messages avec mon propre verdict pour chacun. Signale uniquement les cas ou ta consigne aurait donne un verdict different du mien, et pourquoi. |
| Réduire la donnee transmise | Voici mon script de collecte Gmail. Liste chaque champ envoye au modele et signale ceux qui ne servent pas au classement. Propose la version minimale. |
| Écrire un brouillon sobre | Voici un mail recu et le contexte : [CONTEXTE]. Redige une reponse de 5 lignes maximum, sans formule d'enthousiasme, qui repond a la question posee et rien d'autre. Si une information me manque pour repondre, dis-le au lieu d'inventer. |
| Auditer le perimetre | Voici les perimetres OAuth demandes par mon application et la liste des appels API qu'elle effectue reellement. Signale tout perimetre demande mais jamais utilise. |
Tu as d’autres ressources comme ça ?
Ce que tu viens de lire est entièrement gratuit. Une nouvelle ressource part chaque semaine dans la newsletter : guides, modèles à copier et liens triés, sans compte à créer.
Me contacter
Une question, un projet, une envie de collaborer ? Écris-moi.
Cette page est en accès libre, n’hésite pas à la partager.