E-Mails mit Python lesen: imaplib
Für die Neuauflage meines Scripting-Buchs habe ich mich zuletzt mit dem Senden und Empfangen von E-Mails per SMTP und IMAP beschäftigt. Dieser Blog-Beitrag stellt das Standardmodul imaplib vor. Damit können Sie per IMAP Postfächer abrufen und verwalten. Die Anwendung ist ein wenig umständlich, aber dafür können Sie wirklich praktisch alles machen, was das Protokoll IMAP vorsieht.
Kurz erklärt: IMAP und Authentifizierung
IMAP (Internet Message Access Protocol) ist das Standardprotokoll zum Abrufen und Verwalten von E-Mails auf einem Mailserver. Die Verbindung erfolgt normalerweise über Port 993 mit SSL/TLS. Im Unterschied zu POP3 verbleiben die Nachrichten auf dem Server und lassen sich in Ordnern organisieren – IMAP eignet sich daher besonders gut für die automatisierte Verarbeitung.
Klassische Unix/Linux-Mailserver erlauben die Anmeldung mit Benutzername und Passwort. Bei Google-Konten ist das anders: Gmail erfordert entweder App-Passwörter (16-stellige Codes, die Sie nach Aktivierung der Zwei-Faktor-Authentifizierung unter myaccount.google.com/apppasswords erstellen) oder OAuth2. App-Passwörter sind dabei die einfachere Variante für Scripts.
Passwörter sollten nie direkt im Scriptcode stehen. Die Beispiele hier lesen die Zugangsdaten aus Umgebungsvariablen, die wiederum aus einer Datei credentials.env geladen werden:
# credentials.env (chmod 600; in .gitignore aufnehmen!)
IMAP_HOST=mail.example.com
IMAP_USER=username
IMAP_PASS=topSecret
In Python laden Sie die Datei am einfachsten mit python-dotenv:
from dotenv import load_dotenv
load_dotenv("credentials.env")
Das Modul installieren Sie mit pip install python-dotenv oder – wenn Sie uv verwenden – mit uv add python-dotenv.
imaplib: Verbinden, suchen und herunterladen
imaplib ist seit Python 2 Bestandteil der Standardbibliothek und deckt den vollständigen IMAP-Befehlssatz ab. Das folgende Beispiel-Script stellt die Verbindung zur Inbox her, lädt die zehn neuesten E-Mails herunter und zeigt die wichtigsten Metadaten tabellarisch an – mit * als Indikator für noch ungelesene Nachrichten:
* 13-05 17:53 papa.xxx@xxx... AW: Halbjahresabrechnungen
12-05 20:48 kundenxxx@xxx... Terminanfrage Sendungszustel...
12-05 04:02 info@weltxxx... Japan, Korsika oder Sansibar ...
11-05 20:51 noreply@xxx... OpenAI Dev News: Realtime 2.0...
05-05 18:58 cron@webxxx Invitation updated: iprot dev
04-05 08:28 externexxx@fhxxx... Vertrag bereit zur Abbrechnung
imaplib.IMAP4_SSL stellt die Verbindung zum IMAP-Server auf Port 993 her. select öffnet einen Ordner, search liefert eine Liste von Nachrichten-IDs als leerzeichen-getrennte Byte-Zeichenkette, fetch lädt die Rohdaten einer einzelnen oder mehrerer Nachrichten.
Der Parameter readonly=True bei select stellt sicher, dass das Abrufen der Nachrichten keine Nebeneffekte hat – insbesondere werden keine Nachrichten als gelesen markiert. Das ist wichtig, weil IMAP-Server den Status einer Nachricht bereits beim Öffnen verändern können.
search(None, "ALL") liefert eine aufsteigend sortierte Liste aller Nachrichten-IDs im Postfach. Mit split()[-10:] extrahieren Sie die zehn neuesten. Diese IDs werden, getrennt durch Kommas, zu einem einzigen fetch-Aufruf zusammengefasst. Als Fetch-Attribute übergeben Sie FLAGS für den Lesestatus sowie BODY.PEEK[HEADER.FIELDS (...)], um nur die benötigten Header-Felder abzurufen. PEEK verhindert dabei, dass der Server die Nachrichten automatisch als gelesen kennzeichnet.
Jede Zeile der Server-Antwort ist ein Tupel aus Metadaten und rohen Header-Bytes. ParseFlags wertet die Metadaten aus und gibt die IMAP-Flags als Tupel zurück. Wenn \Seen fehlt, gilt die Nachricht als ungelesen. message_from_bytes wandelt die Header-Bytes in ein auswertbares Objekt um.
parsedate_to_datetime macht aus einem RFC-2822-Datum ein datetime-Objekt, das dann mit strftime nach eigenen Vorstellungen formatiert werden kann. parseaddr zerlegt das From-Feld in ein (Name, Adresse)-Tupel. MIME-kodierte Header-Zeichenketten wie =?utf-8?q?...?= entschlüsselt decode_header aus dem Modul email.header.
Die Ergebnisse werden zunächst in einer Liste gesammelt, da die Reihenfolge der Server-Antwort nicht zwingend der Reihenfolge der angefragten IDs entspricht. reversed dreht die Liste beim Ausgeben um, sodass die neueste Nachricht zuerst erscheint.
# Beispieldatei fetch-imap.py
import os, email, imaplib
from email.header import decode_header, make_header
from email.utils import parsedate_to_datetime, parseaddr
from dotenv import load_dotenv
# IMAP-Konfigurationsparameter lesen
load_dotenv("credentials.env")
HOST = os.environ["IMAP_HOST"]
USER = os.environ["IMAP_USER"]
PASS = os.environ["IMAP_PASS"]
# RFC-2047-Header (=?utf-8?q?...?=) dekodieren
def decode_mime(value: str | None) -> str:
return str(make_header(decode_header(value or "")))
# Zeichenkette auf max_len verkürzen
def truncate(text: str, n: int) -> str:
return text if len(text) <= n else text[: n - 3] + "..."
# Verbindung zum IMAP-Server
with imaplib.IMAP4_SSL(HOST) as imap:
imap.login(USER, PASS)
imap.select("INBOX", readonly=True) # nichts ändern!
# IDs aller Nachrichten ermitteln -> Byte-String für fetch()
_, data = imap.search(None, "ALL")
id_set = b",".join(data[0].split()[-10:]) # max. 10
# IDs der ungelesenen Nachrichten
_, unseen_data = imap.search(None, "UNSEEN")
unseen = set(unseen_data[0].split())
# relevante Spalten abfragen (IMAP-Syntax); PEEK, damit
# das Seen-Flag nicht gesetzt wird
fields = "(FLAGS BODY.PEEK[HEADER.FIELDS (DATE FROM SUBJECT)])"
_, rows = imap.fetch(id_set, fields)
# Daten/Spalten dekodieren und formatieren
messages = []
for row in rows:
if not isinstance(row, tuple):
continue
meta, raw_headers = row
# Sequence-Nummer am Beginn der Metadaten (b'4 (FLAGS ...')
seq = meta.split()[0]
is_new = seq in unseen
msg = email.message_from_bytes(raw_headers)
dt = parsedate_to_datetime(msg["Date"])
date = dt.strftime("%d-%m %H:%M")
addr = parseaddr(decode_mime(msg.get("From", "")))[1]
sender = truncate(addr, 20)
raw_sub = decode_mime(msg.get("Subject", "(no subject)"))
subject = truncate(raw_sub, 40)
messages.append((is_new, date, sender, subject))
# in umgekehrter Reihenfolge ausgeben, die neueste Mail zuerst
for is_new, date, sender, subject in reversed(messages):
flag = "*" if is_new else " "
print(f"{flag} {date} {sender:<20} {subject}")
Was imaplib sonst noch kann
Das obige Beispiel kratzt nur an der Oberfläche. imaplib deckt den vollständigen IMAP-Befehlssatz ab:
- Vollständige Nachrichten laden: Mit
fetchkönnen Sie nicht nur Header, sondern auch den kompletten Nachrichtentext inklusive Anhänge abrufen. - Filtern:
searchakzeptiert IMAP-Suchkriterien wieFROM,SUBJECT,SINCEoderUNSEEN, um gezielt nach bestimmten Nachrichten zu suchen. - Flags setzen:
storesetzt oder entfernt Flags wie\Seen,\Flaggedoder\Deleted. - Löschen:
expungelöscht die als\Deletedmarkierten Nachrichten endgültig. - Ordner verwalten:
copyverschiebt Nachrichten zwischen Ordnern,listgibt alle verfügbaren Mailboxen zurück,createunddeletelegen neue Ordner an oder entfernen sie.
Kurz gesagt: Von der einfachen Posteingangsübersicht bis zur vollautomatisierten Mailbox-Verwaltung ist alles drin.
Alternativen zu imaplib
Wer die etwas sperrige API von imaplib als mühsam empfindet, kann auf IMAPClient ausweichen. Die Bibliothek ist ein High-Level-Wrapper um imaplib, der insbesondere den Umgang mit Nachrichten-IDs, Flags und Ordnern deutlich angenehmer macht. Das folgende Mini-Beispiel zeigt die Verbindung und das Abrufen der Betreffzeilen aller ungelesenen Nachrichten:
from imapclient import IMAPClient
with IMAPClient(HOST) as client:
client.login(USER, PASS)
client.select_folder("INBOX", readonly=True)
messages = client.search("UNSEEN")
for uid, data in client.fetch(messages, "ENVELOPE").items():
print(data[b"ENVELOPE"].subject.decode())
IMAPClient installieren Sie mit pip install imapclient bzw. uv add imapclient.
Wer IMAP in einem asyncio-Kontext benötigt, greift zu aioimaplib – einer vollständig async-fähigen Bibliothek, da imaplib synchron blockiert. Und imbox ist eine sehr einsteigerfreundliche Abstraktion, die E-Mails als Python-Objekte mit direkt zugänglichen Attributen wie .subject oder .attachments liefert. Der Nachteil: weniger Kontrolle bei komplexen Operationen.