0Pricing
AI Agents · Leçon

Lire et envoyer des e-mails par programmation

Répertoriez les messages, récupérez leur contenu et envoyez des e-mails MIME.

Lire et envoyer des e-mails par programmation est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.

Répertorier les messages avec l’API Gmail

La première étape pour lire des e-mails consiste à répertorier les messages qui correspondent à vos critères. service.users().messages().list() renvoie les identifiants des messages et des conversations, et non leur contenu complet. Vous récupérez ensuite chaque message individuellement. Cette approche en deux étapes permet à l’appel de répertoriage de rester rapide.

def list_messages(service, user_id='me', query='', max_results=10):
    results = service.users().messages().list(
        userId=user_id,
        q=query,          # Gmail search query
        maxResults=max_results
    ).execute()

    messages = results.get('messages', [])
    print(f'Found {len(messages)} messages')
    return messages

# Examples of Gmail search queries:
# 'is:unread' — unread messages
# 'from:boss@company.com is:unread' — unread from boss
# 'subject:invoice label:inbox' — invoices in inbox
# 'after:2026/05/01 has:attachment' — recent with attachments
messages = list_messages(gmail_service, query='is:unread label:inbox')

Récupérer un message complet

Utilisez service.users().messages().get() pour récupérer le contenu complet d’un message. Le paramètre format contrôle la quantité de données renvoyées : 'full' inclut les en-têtes et le corps, 'metadata' renvoie uniquement les en-têtes et 'minimal' renvoie seulement les identifiants et les libellés.

def get_message(service, message_id, user_id='me'):
    message = service.users().messages().get(
        userId=user_id,
        id=message_id,
        format='full'  # 'full', 'metadata', or 'minimal'
    ).execute()
    return message

# Fetch the first unread message
messages = list_messages(gmail_service, query='is:unread', max_results=1)
if messages:
    msg = get_message(gmail_service, messages[0]['id'])
    print('Thread ID:', msg['threadId'])
    print('Labels:', msg['labelIds'])
    print('Snippet:', msg['snippet'][:100])

Extraire les en-têtes d’un e-mail

Les en-têtes (From, To, Subject, Date) sont stockés dans message['payload']['headers'] sous forme de liste de dictionnaires {'name': ..., 'value': ...}. Écrivez une fonction utilitaire qui extrait les en-têtes par nom : vous l’utiliserez constamment.

def get_header(message, name):
    headers = message.get('payload', {}).get('headers', [])
    for h in headers:
        if h['name'].lower() == name.lower():
            return h['value']
    return ''

def extract_email_meta(message):
    return {
        'id': message['id'],
        'from': get_header(message, 'From'),
        'to': get_header(message, 'To'),
        'subject': get_header(message, 'Subject'),
        'date': get_header(message, 'Date'),
        'snippet': message.get('snippet', '')
    }

meta = extract_email_meta(msg)
print(f'From: {meta["from"]}')
print(f'Subject: {meta["subject"]}')
print(f'Date: {meta["date"]}')

Décoder le corps de l’e-mail (base64)

Les corps des e-mails dans l’API Gmail sont encodés en base64url, une variante de base64 compatible avec les URL dans laquelle + devient - et / devient _. Utilisez base64.urlsafe_b64decode() pour les décoder. Gérez à la fois les e-mails simples (à partie unique) et ceux comportant plusieurs parties.

import base64

def decode_body(data):
    if not data:
        return ''
    decoded_bytes = base64.urlsafe_b64decode(data + '==')
    return decoded_bytes.decode('utf-8', errors='replace')

def get_email_body(message):
    payload = message.get('payload', {})
    mime_type = payload.get('mimeType', '')

    # Simple (non-multipart) email
    if 'body' in payload and payload['body'].get('data'):
        return decode_body(payload['body']['data'])

    # Multipart email: find the text/plain or text/html part
    parts = payload.get('parts', [])
    for part in parts:
        if part.get('mimeType') == 'text/plain':
            return decode_body(part['body'].get('data', ''))

    # Fallback: try text/html
    for part in parts:
        if part.get('mimeType') == 'text/html':
            return decode_body(part['body'].get('data', ''))

    return message.get('snippet', '')

# --- demo ---
encoded = base64.urlsafe_b64encode(b'Hello from the agent!').decode().rstrip('=')
print('Decoded body:', decode_body(encoded))

message = {
    'payload': {
        'mimeType': 'multipart/alternative',
        'parts': [
            {'mimeType': 'text/plain', 'body': {'data': encoded}}
        ]
    }
}
print('Email body:', get_email_body(message))

Gérer récursivement les e-mails à plusieurs parties

Les e-mails complexes (avec pièces jointes, images intégrées ou contenu mixte) sont des structures imbriquées à plusieurs parties. Les parties du corps peuvent être imbriquées à une profondeur quelconque. Une fonction récursive qui parcourt l’arbre des parties prend en charge tous les cas.

import base64

def extract_parts(payload, target_mime='text/plain'):
    parts_text = []
    mime_type = payload.get('mimeType', '')

    if mime_type == target_mime:
        data = payload.get('body', {}).get('data', '')
        if data:
            decoded = base64.urlsafe_b64decode(data + '==').decode('utf-8', errors='replace')
            parts_text.append(decoded)

    # Recurse into sub-parts
    for part in payload.get('parts', []):
        parts_text.extend(extract_parts(part, target_mime))

    return parts_text

def get_plain_text(message):
    payload = message.get('payload', {})
    texts = extract_parts(payload, 'text/plain')
    return '\n\n'.join(texts) if texts else message.get('snippet', '')

body_text = get_plain_text(msg)
print(f'Body ({len(body_text)} chars):', body_text[:200])

Marquer les messages comme lus

Après avoir traité un e-mail, votre agent doit le marquer comme lu en supprimant le libellé UNREAD. Utilisez service.users().messages().modify() avec removeLabelIds=['UNREAD']. Vous pouvez également ajouter des libellés comme PROCESSED pour suivre les e-mails traités par l’agent.

def mark_as_read(service, message_id, user_id='me'):
    service.users().messages().modify(
        userId=user_id,
        id=message_id,
        body={'removeLabelIds': ['UNREAD']}
    ).execute()
    print(f'Marked {message_id} as read')

def add_label(service, message_id, label_id, user_id='me'):
    service.users().messages().modify(
        userId=user_id,
        id=message_id,
        body={'addLabelIds': [label_id]}
    ).execute()

# Get label ID by name
def get_label_id(service, label_name, user_id='me'):
    labels = service.users().labels().list(userId=user_id).execute()
    for label in labels.get('labels', []):
        if label['name'].lower() == label_name.lower():
            return label['id']
    return None

# --- demo: minimal stand-in for the Gmail API's service object ---
class _Exec:
    def __init__(self, result):
        self._result = result
    def execute(self):
        return self._result

class _FakeUsers:
    def messages(self):
        return self
    def modify(self, **kwargs):
        print(f'[gmail api] messages.modify({kwargs})')
        return _Exec({'id': kwargs.get('id')})
    def labels(self):
        return self
    def list(self, **kwargs):
        return _Exec({'labels': [{'id': 'Label_1', 'name': 'Processed'}]})

class _FakeService:
    def users(self):
        return _FakeUsers()

service = _FakeService()
mark_as_read(service, 'msg_42')
add_label(service, 'msg_42', 'Label_1')
print('Label id for "Processed":', get_label_id(service, 'Processed'))

Composer un e-mail avec MIMEText

Pour envoyer un e-mail, composez-le d’abord sous forme de message MIME à l’aide de la bibliothèque standard email de Python. Encodez ensuite les octets bruts en base64url et envoyez-les à l’API Gmail avec POST. MIMEText prend en charge l’encodage correct du corps du message.

import base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def create_message(sender, to, subject, body_text, body_html=None):
    if body_html:
        msg = MIMEMultipart('alternative')
        msg.attach(MIMEText(body_text, 'plain', 'utf-8'))
        msg.attach(MIMEText(body_html, 'html', 'utf-8'))
    else:
        msg = MIMEText(body_text, 'plain', 'utf-8')

    msg['From'] = sender
    msg['To'] = to
    msg['Subject'] = subject

    # Encode as base64url
    raw = base64.urlsafe_b64encode(msg.as_bytes()).decode('utf-8')
    return {'raw': raw}

message = create_message(
    sender='agent@yourcompany.com',
    to='recipient@example.com',
    subject='Weekly Summary',
    body_text='Hello,\n\nHere is your summary.\n\nBest,\nAgent'
)

# --- demo ---
print('Message keys:', list(message.keys()))
print('Base64 length:', len(message['raw']))

Envoyer un e-mail avec l’API Gmail

Envoyez le message composé à l’aide de service.users().messages().send(). Le paramètre userId='me' désigne l’utilisateur authentifié. L’API renvoie le message envoyé avec son identifiant et son identifiant de conversation.

from googleapiclient.errors import HttpError

def send_message(service, message, user_id='me'):
    try:
        sent = service.users().messages().send(
            userId=user_id,
            body=message
        ).execute()
        print(f'Message sent! ID: {sent["id"]}')
        return sent
    except HttpError as e:
        import json
        body = json.loads(e.content.decode())
        print(f'Send failed ({e.resp.status}): {body.get("error", {}).get("message")}')
        return None

# Send the message
message = create_message(
    sender='me',
    to='team@company.com',
    subject='Agent Report',
    body_text='Processing complete. 42 tasks handled.'
)
send_message(gmail_service, message)

Créer et envoyer des brouillons d’e-mails

Au lieu d’envoyer immédiatement un e-mail, les agents peuvent créer des brouillons pour qu’un humain les examine. Utilisez service.users().drafts().create(). Un humain peut ensuite examiner le brouillon et l’envoyer depuis l’interface de Gmail. C’est la méthode recommandée pour tout e-mail nécessitant une approbation humaine.

def create_draft(service, message, user_id='me'):
    draft = service.users().drafts().create(
        userId=user_id,
        body={'message': message}
    ).execute()
    print(f'Draft created: {draft["id"]}')
    return draft

def send_draft(service, draft_id, user_id='me'):
    sent = service.users().drafts().send(
        userId=user_id,
        body={'id': draft_id}
    ).execute()
    print(f'Draft sent as message: {sent["id"]}')
    return sent

# Create a draft for review
message = create_message(
    sender='me',
    to='client@example.com',
    subject='Proposal Follow-up',
    body_text='Dear Client,\n\nFollowing up on our proposal...'
)
draft = create_draft(gmail_service, message)
# Human reviews in Gmail, then agent sends:
# send_draft(gmail_service, draft['id'])

Traiter plusieurs e-mails par lots

Lorsque vous traitez de nombreux e-mails, ne les récupérez pas un par un dans une boucle serrée : vous atteindriez les limites de quota. Utilisez une boucle contrôlée avec de courtes pauses, ou la fonctionnalité de requêtes groupées de l’API Gmail pour regrouper plusieurs opérations dans un seul appel HTTP.

import time

def process_unread_emails(service, max_emails=20):
    messages = list_messages(
        service,
        query='is:unread label:inbox',
        max_results=max_emails
    )

    processed = []
    for i, msg_ref in enumerate(messages):
        # Rate-limit: process max 5 per second
        if i > 0 and i % 5 == 0:
            time.sleep(1)

        msg = get_message(service, msg_ref['id'])
        meta = extract_email_meta(msg)
        body = get_plain_text(msg)

        result = {
            'id': msg['id'],
            'from': meta['from'],
            'subject': meta['subject'],
            'body_preview': body[:200]
        }
        processed.append(result)
        mark_as_read(service, msg['id'])

    return processed

Répondre à un e-mail dans la conversation

Pour envoyer une réponse dans une conversation, définissez les en-têtes In-Reply-To et References sur la valeur de l’en-tête Message-ID du message d’origine, puis transmettez le threadId à l’appel d’envoi. La réponse reste ainsi dans la même conversation Gmail.

import base64
from email.mime.text import MIMEText

def create_reply(original_message, reply_text, sender='me'):
    original_msg_id = get_header(original_message, 'Message-ID')
    to = get_header(original_message, 'From')
    subject = get_header(original_message, 'Subject')
    if not subject.startswith('Re:'):
        subject = 'Re: ' + subject

    msg = MIMEText(reply_text, 'plain', 'utf-8')
    msg['From'] = sender
    msg['To'] = to
    msg['Subject'] = subject
    msg['In-Reply-To'] = original_msg_id
    msg['References'] = original_msg_id

    raw = base64.urlsafe_b64encode(msg.as_bytes()).decode('utf-8')
    return {
        'raw': raw,
        'threadId': original_message['threadId']  # keeps it in thread
    }

Vérification rapide : corps d’e-mail base64

Vérifiez votre compréhension de la gestion des messages avec l’API Gmail.

Récapitulatif de la lecture et de l’envoi d’e-mails

Votre agent peut maintenant lire et envoyer des e-mails par programmation :

  • Répertorier : messages().list(q='is:unread') renvoie les identifiants ; utilisez la syntaxe de recherche Gmail
  • Récupérer : messages().get(id=..., format='full') renvoie le message complet
  • Analyser les en-têtes : extraire From, Subject et Date de payload.headers
  • Décoder le corps : base64.urlsafe_b64decode(data) pour le texte ; parcourez récursivement les parties à plusieurs éléments
  • Envoyer : composer avec MIMEText, encoder en base64url et envoyer avec POST via messages().send()
  • Répondre dans la conversation : définir l’en-tête In-Reply-To et le threadId dans le corps de l’envoi

Questions Fréquemment Posées

La leçon « Lire et envoyer des e-mails par programmation » est-elle gratuite ?

Oui — le texte complet de « Lire et envoyer des e-mails par programmation » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Lire et envoyer des e-mails par programmation » ?

Répertoriez les messages, récupérez leur contenu et envoyez des e-mails MIME. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Agents ?

Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Lire et envoyer des e-mails par programmation » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?

Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Se connecter à Gmail via l’API
  2. Lire et envoyer des e-mails par programmation
  3. Créer et interroger des événements d’agenda
  4. Créer un assistant e-mail simple sous forme d’agent
← Retour à AI Agents