Eine E-Mail-Adresse in Python zu validieren bedeutet drei separate Prüfungen, und diejenige, die die meisten Tutorials zeigen – eine Regex – ist die am wenigsten nützliche davon. Echte Validierung ist geschichtet: Syntax, dann ein DNS-Lookup, dann eine SMTP-Postfachprüfung. Pythons Standardbibliothek und eine kleine Abhängigkeit decken die ersten beiden ab; die dritte ist ein Netzwerkproblem, das man besser delegiert. Diese Anleitung baut jede Schicht mit funktionierendem Code auf.
Die kurze Antwort#
Verwenden Sie eine permissive Regex (oder email.utils.parseaddr) für die Syntax,
dnspython für den MX-Lookup und eine Verifizierungs-API für die SMTP-Postfachprüfung –
die günstigste zuerst, wobei Sie abbrechen, sobald eine davon entscheidend ist.
Greifen Sie nicht zu smtplib, um Postfächer aus Ihrer App heraus zu prüfen: Der
ausgehende Port 25 ist auf den meisten Hostern blockiert, und das Ergebnis hängt von
der Reputation der sendenden IP und von
Greylisting ab, das Sie nicht selbst nachbauen wollen.
Schicht 1: Syntax#
Halten Sie es permissiv – fangen Sie Tippfehler ab, bauen Sie nicht RFC 5322 nach (was ohnehin nicht helfen würde):
import re
SYNTAX = re.compile(r'^[^\s@"]+(?:\.[^\s@"]+)*@[^\s@.]+(?:\.[^\s@.]+)+$')
def is_valid_syntax(email: str) -> bool:
return isinstance(email, str) and len(email) <= 320 and SYNTAX.match(email) is not None
is_valid_syntax("jane@example.com") # True
is_valid_syntax("not-an-email") # False
is_valid_syntax("a@@b.com") # False
Das email.utils.parseaddr der Standardbibliothek parst zwar eine Adresse, aber es
ist bewusst nachsichtig – parseaddr("nonsense") liefert ('', 'nonsense') zurück,
ohne einen Fehler auszulösen – es ist also ein Parser, kein Validator. Eine kurze Regex
ist für ein Ja/Nein-Syntax-Gate klarer.
Schicht 2: Kann die Domain überhaupt Mail empfangen?#
Eine Domain ohne MX-Records kann für niemanden Mail annehmen. dnspython löst sie in
ein paar Zeilen auf:
import dns.resolver # pip install dnspython
def has_mail_route(domain: str) -> bool:
try:
answers = dns.resolver.resolve(domain, "MX")
return len(answers) > 0
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
return False
has_mail_route("gmail.com") # True
has_mail_route("company-that-folded.com") # False
Dieser eine Lookup eliminiert tote Domains und erfundene TLDs ganz nebenbei. Wenn Sie
Domains berücksichtigen wollen, die Mail auf einem A-Record ohne MX annehmen, fallen Sie
auf das Auflösen von A/AAAA zurück, wenn die MX-Menge leer ist – aber eine MX-Prüfung
deckt die überwältigende Mehrheit der echten Adressen ab.
Schicht 3: Existiert das Postfach tatsächlich?#
Die Schichten 1 und 2 können eine Adresse nur ausschließen. Ein Postfach zu bestätigen
bedeutet das SMTP-Zustellgespräch – und während smtplib ein solches eröffnen kann, ist
es eine schlechte Idee, es aus Ihrer Anwendung heraus auszuführen: Port 25 ist weithin
blockiert, die Antwort hängt von der Reputation der IP ab, von der aus Sie sich verbinden,
und empfangende Server wenden Greylisting und Ratenbegrenzung auf Fremde an. Delegieren
Sie diese Schicht.
Qualisends POST /verify führt die vollständige Pipeline aus. Die lokalen
Prüfungen kehren sofort zurück, während die SMTP-Prüfung in die Warteschlange gestellt wird:
import os
import requests # pip install requests
BASE = "https://app.qualisend.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['QUALISEND_API_KEY']}"}
def verify(email: str) -> dict:
res = requests.post(f"{BASE}/verify", headers=HEADERS, json={"email": email}, timeout=10)
res.raise_for_status()
return res.json()
Für die Live-Validierung bei der Anmeldung reicht das sofortige result meist aus, um zu
handeln. Wenn Sie das SMTP-bestätigte Urteil brauchen, fragen Sie den Job ab, bis die
Prüfung abgeschlossen ist:
import time
def verify_and_wait(email: str, tries: int = 10, delay: float = 1.5):
job_id = verify(email)["job_id"]
for _ in range(tries):
res = requests.get(f"{BASE}/jobs/{job_id}",
params={"include": "results"}, headers=HEADERS, timeout=10)
job = res.json()
if job["status"] == "completed":
return job["results"][0] # {"status": ..., "score": ..., "reason": ...}
time.sleep(delay)
return None # still processing — treat as unknown, retry later
Eine Liste im Stapel verifizieren#
Lassen Sie verify() nicht Adresse für Adresse über eine CSV laufen – das ist ein
HTTP-Roundtrip und ein Job pro Zeile. Schicken Sie die gesamte Liste in einer einzigen
Anfrage an /verify, erhalten Sie eine job_id zurück und fragen Sie einmal für den
gesamten Stapel ab. Das results-Array kommt in der Reihenfolge der Übermittlung zurück:
def verify_bulk(emails: list[str], tries: int = 60, delay: float = 2.0):
res = requests.post(f"{BASE}/verify", headers=HEADERS,
json={"emails": emails}, timeout=30)
res.raise_for_status()
job_id = res.json()["job_id"]
for _ in range(tries):
job = requests.get(f"{BASE}/jobs/{job_id}",
params={"include": "results"}, headers=HEADERS,
timeout=30).json()
if job["status"] == "completed":
return dict(zip(emails, job["results"]))
time.sleep(delay) # back off; large lists take longer than a single probe
return None
Ein Stapel von Tausenden kann Minuten dauern, weil jede SMTP-Prüfung ein separates
Netzwerkgespräch ist. Weiten Sie also tries/delay für größere Listen aus, statt den
Poll-Endpunkt zu überlasten. Führen Sie zuerst die beiden lokalen Schichten aus, um
offensichtlichen Müll auszusortieren, bevor Sie Credits ausgeben – filtern Sie mit
is_valid_syntax und has_mail_route und senden Sie dann nur die Überlebenden.
Hinweise zu Async und pydantic#
requests ist blockierend. Umschließen Sie den Aufruf in einer asyncio-App daher mit
asyncio.to_thread(verify, email) oder wechseln Sie zu httpx.AsyncClient, um die
Verify-Aufrufe nebenläufig abzufeuern; dnspython bringt aus demselben Grund einen
asynchronen Resolver (dns.asyncresolver) mit. Wenn Sie bereits pydantic verwenden,
ist dessen EmailStr-Typ ein sauberer Ersatz für Schicht 1 – er validiert die Syntax,
wenn ein Modell erstellt wird –, aber dort endet es: Er berührt weder DNS noch das
Postfach, halten Sie die MX-Prüfung und die Verifizierungs-API also nachgelagert.
Die Schichten zusammensetzen#
Die günstigste zuerst, aufhören, sobald Sie eine Antwort haben:
def validate_email(email: str) -> dict:
if not is_valid_syntax(email):
return {"status": "undeliverable", "reason": "invalid_email"}
domain = email.rsplit("@", 1)[1]
if not has_mail_route(domain):
return {"status": "undeliverable", "reason": "invalid_domain"}
return verify(email)["result"] # deliverable | risky | undeliverable | unknown
Die beiden lokalen Schichten kosten nichts und fangen den meisten Müll sofort ab; die API läuft nur auf Adressen, die den Roundtrip wert sind. Siehe wie E-Mail-Verifizierung funktioniert dafür, warum die Stufen in dieser Reihenfolge stehen.
Häufig gestellte Fragen#
Kann ich eine E-Mail in Python nur mit der Standardbibliothek verifizieren?#
Bis zu einem gewissen Punkt. re übernimmt die Syntax und mit socket können Sie einen
DNS-Versuch unternehmen – aber es gibt keinen eingebauten MX-Resolver (daher dnspython),
und um ein Postfach zu bestätigen, braucht es eine SMTP-Prüfung, die Sie nicht aus Ihrer App
heraus ausführen sollten. Die Standardbibliothek liefert Ihnen Syntax und eine grobe
Domain-Prüfung; die Postfach-Schicht braucht dnspython plus einen Verifizierungsdienst.
Warum nicht smtplib verwenden, um zu prüfen, ob ein Postfach existiert?#
smtplib kann das SMTP-Gespräch eröffnen, aber es von Ihrem Anwendungsserver aus
auszuführen, ist unzuverlässig und riskant: Die meisten Hoster blockieren den ausgehenden
Port 25, die Antwort hängt von der Reputation Ihrer sendenden IP ab, und Prüfungen in
jeglichem Umfang führen dazu, dass Sie ratenbegrenzt oder auf Sperrlisten gesetzt werden.
Eine Verifizierungs-API führt die Prüfung von einer dafür gebauten, reputationsverwalteten
Infrastruktur aus durch.
Ist dnspython die richtige Bibliothek für MX-Lookups?#
Ja – es ist das standardmäßige, gut gepflegte DNS-Toolkit für Python und löst MX-Records
sauber auf. Installieren Sie es mit pip install dnspython und fangen Sie NXDOMAIN,
NoAnswer und NoNameservers ab, um eine fehlende Mail-Route als nicht zustellbar zu
behandeln.
Validiert das EmailStr von pydantic eine E-Mail-Adresse vollständig?#
Nein – EmailStr (basierend auf dem Paket email-validator) prüft nur, dass eine
Zeichenkette beim Erstellen Ihres Modells syntaktisch eine gültige Adresse ist. Es bestätigt
nicht, dass die Domain MX-Records hat oder dass das Postfach existiert, es entspricht also
genau Schicht 1. Verwenden Sie es als Syntax-Gate in Ihren pydantic-Modellen und führen Sie
dann den DNS-Lookup und die Verifizierungs-API auf den Adressen aus, die durchkommen. Es
normalisiert die Adresse außerdem (schreibt die Domain klein), was praktisch ist, bevor Sie
eine Liste deduplizieren.
Bereit, die SMTP-Schicht hinzuzufügen? Der kostenlose Tarif enthält 100 Credits,
die die vollständige Pipeline ausführen, und die API-Referenz enthält die
kompletten /verify- und /jobs-Endpunkte mit Copy-and-paste-Beispielen in sieben Sprachen.