Skip to content
Starten Sie mit 100 kostenlosen Verifizierungs-Credits
Qualisend
Alle Artikel
Engineering / 17. Juni 2026

So validieren Sie eine E-Mail-Adresse in Python

4 minutes read

Qualisend team
Ein Python-Codefenster, das eine E-Mail über die Schichten Syntax, DNS und SMTP validiert

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.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started