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

E-Mail-Adressen in Flask validieren – so geht's richtig

8 minutes read

Qualisend team
Codefenster mit dem Titel forms.py, das drei sich verengende Flask-Validierungsschichten zeigt – Email(), einen check_deliverability-MX-Lookup und einen POST an die verify-API – abgeschlossen mit einem grünen Deliverable-Badge.

Um eine E-Mail-Adresse in Flask zu validieren, führen Sie drei Prüfungen durch, nicht eine – und Flask selbst liefert keine davon, denn Flask hat keine eigene Formularschicht. Was es hat, ist ein Ökosystem: WTForms (üblicherweise über Flask-WTF) für die Formularverarbeitung und das Paket email-validator, auf das sich WTForms stützt. Zusammen beantworten sie die ersten beiden der drei Validierungsfragen – ist die Adresse korrekt geformt und kann ihre Domain überhaupt Mails empfangen –, aber niemals die dritte: existiert das Postfach tatsächlich. Echte Validierung ist geschichtet: Syntax, dann ein DNS-Lookup, dann eine SMTP-Postfachprüfung. Diese Anleitung baut jede Schicht als WTForms-Validator auf, den Sie in ein Flask-WTF-Formular oder eine schlichte JSON-Route einsetzen können, und zeigt, wo eine Verifizierungs-API übernimmt. Sie knüpft dort an, wo die Python-Anleitung aufhört, sodass die dortige DNS- und SMTP-Argumentation unverändert weitergilt.

Die kurze Antwort#

Verwenden Sie den Email()-Validator von WTForms für die Syntax, seine check_deliverability=True-Option (oder dnspython) für den MX-Lookup und eine Verifizierungs-API für die SMTP-Postfachprüfung – so verdrahtet, dass die günstigste Prüfung zuerst läuft und jede Schicht abbricht, bevor die nächste startet. Öffnen Sie keine SMTP-Verbindungen aus Ihrem Flask-Prozess, um Postfächer zu prüfen: Der ausgehende Port 25 ist auf den meisten Hosts blockiert, und die Antwort hängt von der Reputation der sendenden IP und von Greylisting ab, das Sie nicht neu implementieren wollen. Jede Schicht schließt Adressen aus, und zwar günstiger als die vorige; nur die API kann eine Adresse einschließen. Falls Ihnen die Pipeline neu ist, erklärt was E-Mail-Verifizierung ist zuerst die Begriffe.

Schicht 1: Formatvalidierung mit dem Email-Validator von WTForms#

WTForms besitzt Schicht eins und schreibt sie nicht selbst zusammen. wtforms.validators.Email delegiert an das Paket email-validator, sodass die Deklaration eines Email()-Validators an einem Feld Ihnen eine echte, aus dem RFC abgeleitete Syntaxprüfung liefert – keine Regex – und das kostenlos:

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired, Email

class SignupForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired(), Email()])

Der Email()-Validator benötigt das installierte Paket (pip install email-validator, oder pip install wtforms[email]); ohne es löst WTForms zum Validierungszeitpunkt einen Fehler aus, der Ihnen das mitteilt. Unter der Haube ruft er email_validator.validate_email auf, das Sie auch eigenständig verwenden können – außerhalb eines Formulars oder in einer Service-Schicht –, um dasselbe Urteil als Boolean zu erhalten:

from email_validator import validate_email, EmailNotValidError

def is_valid_syntax(email: str) -> bool:
    try:
        validate_email(email, check_deliverability=False)
        return True
    except EmailNotValidError:
        return False

Machen Sie sich genau klar, was Ihnen das bringt. Mit check_deliverability=False liest die Prüfung nur die Form – sie löst nie DNS auf, öffnet nie einen Socket und hat keine Ahnung, ob example.com existiert. definitely-fake@gmail.com besteht. info@company-that-folded.com besteht. typo@gmial.com besteht. Alle drei sind unzustellbar, und kein Validator, der nur den String liest, wird Ihnen das jemals sagen – aus demselben Grund, aus dem E-Mail-Validierung per Regex scheitert: Syntax und Zustellbarkeit sind unterschiedliche Fragen. Die eine ist eine Tatsache über den String, die andere eine Tatsache über das Internet.

Schicht 2: Nimmt die Domain Mails an?#

Eine Domain ohne MX-Einträge kann für niemanden Mails annehmen, also eliminiert ein einziger DNS-Lookup tote Domains, falsch geschriebene Firmennamen und erfundene TLDs. Hier tut Flasks Stack etwas, das Djangos Stack von Haus aus nicht tut: email-validator kann den Lookup für Sie durchführen. Schalten Sie check_deliverability ein, und es löst die MX-Einträge der Domain auf (mit Rückfall auf A/AAAA) über dnspython und löst EmailUndeliverableError aus – eine Unterklasse von EmailNotValidError –, wenn die Domain keine Mails empfangen kann. In einem Formular ist das eine Option am Validator, den Sie bereits haben:

email = StringField("Email", validators=[DataRequired(), Email(check_deliverability=True)])

Dieses eine Flag fasst die Schichten eins und zwei zu einem Validator zusammen. Eigenständig gibt derselbe Aufruf ein Objekt mit normalisierter Adresse zurück, wenn die Domain in Ordnung ist:

from email_validator import validate_email, EmailNotValidError

def has_mail_route(email: str) -> bool:
    try:
        validate_email(email, check_deliverability=True)  # syntax + live MX lookup
        return True
    except EmailNotValidError:
        return False

Wenn Sie die DNS-Prüfung lieber selbst durchführen möchten – um Ergebnisse zu cachen, auf Ihren eigenen Resolver zu zeigen oder ein knappes Timeout zu setzen –, greifen Sie direkt zu dnspython:

import dns.resolver  # pip install dnspython

def domain_has_mx(domain: str) -> bool:
    try:
        return len(dns.resolver.resolve(domain, "MX")) > 0
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
        return False

Schicht 3: Existiert das Postfach tatsächlich?#

Die Schichten eins und zwei können eine Adresse nur ausschließen. Eine Domain kann perfekte MX-Einträge veröffentlichen und trotzdem kein Postfach an der Adresse haben, die Sie in Händen halten – noreply-9f2x@gmail.com hat gültige Syntax auf einer aktiven Mail-Route und ist trotzdem ein Postfach, das nie angelegt wurde. Ein bestimmtes Postfach zu bestätigen bedeutet die SMTP-Zustellkonversation: sich mit dem Mail-Host verbinden, RCPT TO absetzen, die Antwort lesen und die Verbindung trennen, bevor irgendetwas gesendet wird.

Im Prinzip könnten Sie das aus Flask heraus mit smtplib skripten. In der Praxis sollten Sie es nicht von Ihrem Anwendungsserver aus ausführen: Die meisten Cloud-Hosts blockieren den ausgehenden Port 25, die Antwort hängt von der Reputation der IP ab, von der aus Sie sich verbinden, und empfangende Server greylisten und drosseln unbekannte Absender – sodass eine Prüfung, die im lokalen Test funktioniert, in der Produktion klammheimlich scheitert oder Sie auf eine Blockliste bringt. Wie E-Mail-Verifizierung funktioniert geht die vollständige Pipeline durch, Catch-all-Domains und alles Übrige. Das ist die Schicht, die es sich zu delegieren lohnt.

Der verify-Endpunkt von Qualisend durchläuft die gesamte Pipeline – Syntax, DNS und die SMTP-Postfachprüfung – von reputationsverwalteter Infrastruktur aus und liefert ein Urteil zurück. Rufen Sie ihn mit requests auf und lesen Sie Ihren Schlüssel aus der Umgebung:

import os
import requests  # pip install requests

QUALISEND_VERIFY_URL = "https://api.qualisend.com/v1/verify"

def verify(email: str) -> dict:
    res = requests.post(
        QUALISEND_VERIFY_URL,
        headers={"Authorization": f"Bearer {os.environ['QUALISEND_API_KEY']}"},
        json={"email": email},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()["result"]  # {"status", "score", "reason", "sub_flags"}

Die Antwort ist ein { "result": { ... } }-Umschlag. Lesen Sie result["status"]deliverable, risky, undeliverable oder unknown – zusammen mit einem score, einem reason und sub_flags für Merkmale wie Rollen- oder Wegwerf-Adressen. Prüfen Sie in der API-Referenz die genauen Feldformen; hier brauchen Sie nur den Status zur Entscheidung. undeliverable abzulehnen ist die sichere Voreinstellung; Sie könnten auch risky ablehnen oder den score speichern und die Adresse durchlassen – das ist eine Policy-Entscheidung, und die Anleitung zur Serverless-Anmeldung behandelt, wie streng man am Erhebungspunkt sein sollte.

Die Schichten zusammenfügen, um eine E-Mail-Adresse in Flask zu validieren#

Der idiomatische Ort, um in Flask-WTF eine Prüfung pro Feld hinzuzufügen, ist eine Inline-Methode validate_<fieldname> am Formular. WTForms hängt diese Methode an die Validatorkette des Felds an und führt sie nach den Validatoren in der Liste aus, sodass bis zum Zeitpunkt ihres Feuerns Email(check_deliverability=True) Syntax und MX-Lookup bereits geklärt hat. Schützen Sie mit field.errors, damit die API-Rundreise nur für Adressen stattfindet, die beide günstigeren Schichten bestanden haben:

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired, Email, ValidationError

class SignupForm(FlaskForm):
    email = StringField("Email", validators=[
        DataRequired(),
        Email(check_deliverability=True),   # layers 1 + 2
    ])

    def validate_email(self, field):        # layer 3 — runs after the list above
        if field.errors:                    # syntax or MX already failed
            return                          # don't spend an API credit
        if verify(field.data)["status"] == "undeliverable":
            raise ValidationError("We couldn't confirm a mailbox at this address.")

Dieser field.errors-Schutz ist der ganze Trick. Eine fehlerhafte Adresse oder eine tote Domain trägt einen Fehler in die Kette ein, bevor die Inline-Methode läuft, sodass der Schutz den verify-Aufruf für Adressen stoppt, die nie eine Chance hatten – eine Validierung in falscher Reihenfolge oder das Auslassen des Schutzes verbraucht ein Guthaben bei jedem Tippfehler. In einer View führt validate_on_submit die gesamte Pipeline aus, bevor der Rumpf Ihres Handlers ausgeführt wird:

@app.route("/signup", methods=["POST"])
def signup():
    form = SignupForm()
    if form.validate_on_submit():
        # every layer passed — safe to persist
        create_account(form.email.data)
        return redirect(url_for("welcome"))
    return render_template("signup.html", form=form), 400

Eine Sache müssen Sie vorab entscheiden: was passiert, wenn der API-Aufruf selbst scheitert. Ein Netzwerk-Timeout oder eine requests-Exception innerhalb von validate_email sollte den Request nicht mit einem 500 abbrechen oder einer echten Kundin bzw. einem echten Kunden einen Fehler bescheren, den sie nicht beheben können. Fangen Sie requests.RequestException getrennt von ValidationError ab und behandeln Sie einen nicht erreichbaren Dienst als unknown statt als undeliverable – lassen Sie die Anmeldung durch und verifizieren Sie die Adresse später erneut, statt die Registrierung an einem vorübergehenden Ausfall zu blockieren. Die Syntax- und MX-Schichten sind bereits gelaufen, Sie mildern also nur die Schicht ab, die vom Netzwerk abhängt.

Nicht jede Flask-App verwendet WTForms. Für eine JSON-API lesen sich dieselben drei Schichten direkt in einer Route und geben bei einer schlechten Adresse einen 400 zurück, statt ValidationError auszulösen:

from flask import Flask, request, jsonify
from email_validator import validate_email, EmailNotValidError

app = Flask(__name__)

@app.post("/api/signup")
def api_signup():
    email = (request.get_json(silent=True) or {}).get("email", "")
    try:
        info = validate_email(email, check_deliverability=True)  # layers 1 + 2
    except EmailNotValidError as exc:
        return jsonify(error=str(exc)), 400

    if verify(info.normalized)["status"] == "undeliverable":     # layer 3
        return jsonify(error="Email address appears undeliverable."), 400

    # ... create the account
    return jsonify(ok=True), 201

info.normalized ist die kanonisierte Adresse, die email-validator zurückgibt und die Sie speichern sollten. Das ist dieselbe dreischichtige Form, die Sie auch in den Versionen dieser Anleitung für Node.js und Python finden – es ist die Schichtung, nicht das Framework, die Validierung funktionieren lässt. Wenn Sie auswählen, welcher Verifizierungsdienst Schicht drei absichert, stellt der API-Vergleich die Optionen gegenüber.

Häufig gestellte Fragen#

Reicht der Email-Validator von WTForms aus, um eine E-Mail-Adresse zu validieren?#

Für die Syntax ja – Email() delegiert an das Paket email-validator und ist eine bessere Prüfung auf der ersten Schicht als jede selbstgeschriebene Regex. Standardmäßig validiert er jedoch die Form, nicht die Zustellbarkeit. Mit check_deliverability=True führt er zusätzlich einen Live-MX-Lookup durch, der tote Domains ausschließt; selbst dann kontaktiert er das Postfach aber nie, sodass ein bestandener Test bedeutet: „die Domain nimmt Mails an“, nicht „diese Adresse existiert“. Kombinieren Sie ihn mit einer SMTP-Postfachprüfung, bevor Sie der Adresse vertrauen.

Wie füge ich in Flask-WTF einen eigenen E-Mail-Validator hinzu?#

Zwei idiomatische Möglichkeiten. Für eine einmalige Prüfung fügen Sie dem Formular eine Inline-Methode mit dem Namen validate_<fieldname> hinzu – z. B. validate_email(self, field) – und lösen wtforms.validators.ValidationError aus, wenn der Wert scheitert; WTForms führt sie nach den im Feld aufgeführten Validatoren aus. Für eine wiederverwendbare Regel schreiben Sie ein Callable, das (form, field) entgegennimmt und denselben Fehler auslöst, und übergeben es dann in der validators-Liste des Felds. Beides ist der richtige Ort für den Qualisend-verify-Aufruf.

Kann ich in Flask ohne API prüfen, ob ein Postfach existiert?#

Teilweise. email-validator mit check_deliverability=True (oder dnspython direkt) bestätigt, dass die Domain Mails annimmt, schließt so tote Domains kostenlos aus und benötigt nichts außer einer kleinen Abhängigkeit. Das Postfach zu bestätigen bedeutet eine SMTP-Konversation, die Sie mit smtplib versuchen können, aber nicht von Ihrem App-Server aus ausführen sollten – Port 25 ist weitgehend blockiert und das Ergebnis hängt von der Reputation Ihrer IP ab. Genau diese Schicht übernimmt ein Verifizierungsdienst.

Sollte ich E-Mails bei der Anmeldung oder beim Bereinigen einer Liste validieren?#

Beides, in unterschiedlicher Tiefe. Führen Sie die Syntax- und MX-Schichten synchron im Formular oder in der Route aus – sie sind schnell genug, um den Request zu blockieren und sofortiges Feedback zu geben – und reagieren Sie dort auch auf das API-Urteil. Reservieren Sie die aufwendigere Batch-Verifizierung für die Listenbereinigung und Back-Office-Arbeit, wo Latenz keine Rolle spielt und Sie Adressen in großen Mengen verarbeiten können statt eine Formularübermittlung nach der anderen.


Bereit, die SMTP-Schicht hinzuzufügen? Werfen Sie eine von Email() bestätigte Adresse in den kostenlosen E-Mail-Checker, um zuzusehen, wie ein WTForms-gültiger String als undeliverable zurückkommt, und verdrahten Sie dann dasselbe Urteil mit der API-Referenz in Ihr Formular – Copy-and-paste-Beispiele inklusive.

Your reputation, protected.

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

Get started