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.