Die Validierung einer E-Mail-Adresse in PHP beginnt und endet üblicherweise mit
einer Zeile: filter_var($email, FILTER_VALIDATE_EMAIL). Sie ist integriert,
sie ist schnell und sie ist wirklich nützlich – doch sie beantwortet nur die
erste der drei Fragen, die echte Validierung stellt. Ist die Adresse korrekt
geformt? Kann ihre Domain Mail empfangen? Existiert das Postfach tatsächlich?
Die Standardbibliothek von PHP beantwortet die ersten beiden von Haus aus; die
dritte ist ein Netzwerkproblem, das man besser auslagert. Dieser Leitfaden baut
die PHP-E-Mail-Validierung in Ebenen auf, mit funktionierendem Code für jede,
und zeigt genau, wo filter_var aufhört.
Die kurze Antwort#
Verwenden Sie filter_var() mit FILTER_VALIDATE_EMAIL für die Syntax,
checkdnsrr() oder getmxrr() für die MX-Abfrage und eine Verifizierungs-API
für die SMTP-Postfachprüfung – die günstigste zuerst, mit Abbruch, sobald eine
davon entscheidend ist. Versuchen Sie nicht, aus PHP heraus eine SMTP-Sitzung zu
öffnen, um Postfächer selbst zu prüfen: Der ausgehende Port 25 ist auf den
meisten Hosts gesperrt, und die Antwort hängt von der Reputation der sendenden IP
und vom Greylisting ab, das Sie nicht selbst
nachbauen wollen. Jede Ebene sortiert Adressen günstiger aus als die
vorherige; nur die letzte kann eine Adresse einschließen.
Ebene 1: Syntax mit filter_var#
Für PHP ist filter_var das richtige Werkzeug für Ebene eins – es erspart Ihnen
das Basteln einer eigenen Regex und ist besser getestet als alles, was Sie von
Stack Overflow einfügen würden. FILTER_VALIDATE_EMAIL gibt bei Erfolg die
Adresse zurück und bei Misserfolg false, vergleichen Sie also strikt:
function is_valid_syntax(string $email): bool
{
return strlen($email) <= 320
&& filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
is_valid_syntax('jane@example.com'); // true
is_valid_syntax('not-an-email'); // false
is_valid_syntax('a@@b.com'); // false
Das strikte !== false ist entscheidend. filter_var gibt bei Erfolg die
gefilterte Zeichenkette zurück, nicht true, ein lockeres
if (filter_var(...)) funktioniert also nur, weil eine nicht leere Zeichenkette
truthy ist – der Vergleich gegen false sagt, was Sie meinen, und übersteht
Refactorings. Der Längen-Guard ist doppelt abgesichert: Eine Adresse mit mehr
als 320 Zeichen kann keine echte sein, und es ist günstiger, sie früh abzulehnen,
als eine riesige Zeichenkette an irgendetwas nachgelagertes weiterzureichen.
Wissen Sie, was dieser Filter tut und was nicht. Standardmäßig validiert er
gegen eine RFC-abgeleitete Grammatik und lehnt internationalisierte
(Unicode-)Local-Parts ab – 用户@example.com scheitert also, sofern Sie nicht ab
PHP 7.1 das Flag FILTER_FLAG_EMAIL_UNICODE hinzufügen. Es ist ein
Syntax-Filter, Punkt: Er berührt niemals DNS, öffnet niemals einen Socket und
hat keine Ahnung, ob example.com existiert. Beachten Sie außerdem, dass
FILTER_SANITIZE_EMAIL ein anderes Kaliber ist – es entfernt unzulässige
Zeichen und gibt eine verstümmelte Zeichenkette zurück statt einer Ja/Nein-
Antwort, greifen Sie also nicht dazu, wenn Sie validieren wollen.
Warum ein bestandener filter_var-Test kein grünes Licht ist#
Ein bestandener filter_var-Test bedeutet, dass die Zeichenkette wie eine
E-Mail-Adresse geformt ist. Er sagt nichts darüber aus, ob Mail ankommen wird.
definitely-fake@gmail.com besteht. info@company-that-folded.com besteht.
typo@gmial.com besteht. Alle drei sind unzustellbar, und kein noch so
ausgefeilter Mustervergleich – weder filter_var noch die aufwendigste Regex,
die Sie finden – wird Ihnen das je verraten, denn Syntax und Zustellbarkeit sind
verschiedene Fragen. Die eine ist eine Tatsache über die Zeichenkette; die
andere eine Tatsache über das Internet. Das ist dieselbe Falle, die
Regex-E-Mail-Validierung scheitern lässt,
und filter_var steht auf genau derselben Seite der Linie.
Wenn Sie die Lücke selbst sehen möchten, fügen Sie eine syntaktisch perfekte
Adresse in den kostenlosen E-Mail-Checker ein und
beobachten Sie, wie eine von filter_var gebilligte Zeichenkette als
undeliverable zurückkommt.
Ebene 2: Kann die Domain Mail empfangen?#
Hier verdient sich PHP seinen Lohn ganz ohne Abhängigkeiten. Eine Domain ohne
Mailroute kann für niemanden Mail annehmen, diese eine Abfrage eliminiert also
tote Domains, falsch geschriebene Firmennamen und erfundene TLDs. Die schnellste
Prüfung ist checkdnsrr(), das einen Boolean dafür zurückgibt, ob ein bestimmter
Eintragstyp existiert:
function has_mail_route(string $domain): bool
{
// checkdnsrr() returns true if the domain publishes at least one MX record.
// Fall back to A for domains that accept mail on an implicit MX.
return checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
}
has_mail_route('gmail.com'); // true
has_mail_route('company-that-folded.com'); // false
Trennen Sie die Domain mit strrpos von der Adresse ab, damit Sie immer am
letzten @ schneiden:
$domain = substr($email, strrpos($email, '@') + 1);
Wenn Sie die tatsächlichen Mail-Hosts statt eines Ja/Nein benötigen – etwa um
sie zu protokollieren oder die Prioritäten zu prüfen –, greifen Sie zu
getmxrr(). Es füllt per Referenz ein Hosts-Array und ein passendes
Gewichtungs-Array und gibt false zurück, wenn überhaupt kein MX-Eintrag
vorhanden ist:
function mail_hosts(string $domain): array
{
$hosts = [];
$weights = [];
if (!getmxrr($domain, $hosts, $weights)) {
return [];
}
array_multisort($weights, $hosts); // lowest weight = highest priority
return $hosts; // e.g. ['gmail-smtp-in.l.google.com', 'alt1.gmail-smtp-in.l.google.com', ...]
}
Ebene 3: Existiert das Postfach tatsächlich?#
Ebene eins und zwei können eine Adresse nur aussortieren. Eine Domain kann
perfekte MX-Einträge veröffentlichen und trotzdem kein Postfach unter der Adresse
haben, die Sie in Händen halten – noreply-9f2x@gmail.com ist syntaktisch gültig
auf einer Domain mit aktiver Mailroute und ist dennoch ein Postfach, das nie
angelegt wurde. Zu bestätigen, dass ein bestimmtes Postfach existiert, bedeutet
die SMTP-Zustellkonversation: eine Verbindung zum Mail-Host aufbauen, RCPT TO
absetzen, die Antwort lesen und die Verbindung trennen, bevor irgendetwas
gesendet wird. Es steckt mehr dahinter –
wie E-Mail-Verifizierung funktioniert
durchläuft die gesamte Pipeline, einschließlich
Catch-All-Domains, die jede Adresse annehmen
und eine naive Prüfung aushebeln.
Im Prinzip können Sie das in PHP mit fsockopen() und rohen SMTP-Befehlen
skripten. In der Praxis sollten Sie es nicht von Ihrem Anwendungsserver aus
ausführen: Die meisten Cloud-Anbieter sperren den ausgehenden Port 25, die
Antwort hängt von der Reputation der IP ab, von der aus Sie verbinden, und
empfangende Server betreiben Greylisting und Rate-Limiting bei unbekannten
Absendern – eine Prüfung, die im lokalen Test funktioniert, scheitert also in der
Produktion still oder bringt Sie auf eine Blockliste. Das ist die Ebene, die sich
zu delegieren lohnt.
Die vollständige Prüfung mit einer API#
Qualisends POST /verify durchläuft die gesamte Pipeline –
Syntax, DNS und die SMTP-Postfachprüfung – von einer reputationsverwalteten,
dafür gebauten Infrastruktur aus und gibt ein Urteil zurück. Ganz ohne Framework
deckt ein kleiner cURL-Helfer jeden Aufruf ab, den Sie machen werden:
const QUALISEND_BASE = 'https://app.qualisend.com/api/v1';
function qualisend(string $method, string $path, ?array $body = null): array
{
$ch = curl_init(QUALISEND_BASE . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('QUALISEND_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body !== null ? json_encode($body) : null,
CURLOPT_TIMEOUT => 10,
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($raw === false || $status >= 400) {
throw new RuntimeException("Qualisend responded {$status}");
}
return json_decode($raw, true);
}
function verify(string $email): array
{
return qualisend('POST', '/verify', ['email' => $email]);
}
Wenn Sie bereits Guzzle verwenden, ist dieselbe Anfrage ein paar Zeilen kürzer und übernimmt JSON und Fehlerstatuscodes für Sie:
use GuzzleHttp\Client;
$http = new Client(['base_uri' => 'https://app.qualisend.com/api/v1/']);
$response = $http->post('verify', [
'headers' => ['Authorization' => 'Bearer ' . getenv('QUALISEND_API_KEY')],
'json' => ['email' => 'jane@example.com'],
'timeout' => 10,
]);
$result = json_decode((string) $response->getBody(), true);
Die lokalen Prüfungen kommen sofort zurück, während die SMTP-Prüfung in die Warteschlange gestellt wird:
{
"job_id": "6f1c2e0a-9b3d-4a1e-8c77-1a2b3c4d5e6f",
"probe_queued": true,
"result": {
"email": "jane@example.com",
"status": "deliverable",
"score": 95,
"sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false },
"did_you_mean": null,
"smtp": "pending",
"reason": null
}
}
Für die Live-Validierung bei der Registrierung reicht das unmittelbare result
in der Regel als Handlungsgrundlage aus – undeliverable ablehnen, die
did_you_mean-Korrektur anbieten, disposable markieren. Wenn Sie das per SMTP
bestätigte Urteil benötigen, fragen Sie den Job ab, bis die Prüfung
abgeschlossen ist:
function verify_and_wait(string $email, int $tries = 10, float $delay = 1.5): ?array
{
$jobId = verify($email)['job_id'];
for ($i = 0; $i < $tries; $i++) {
$job = qualisend('GET', "/jobs/{$jobId}?include=results");
if ($job['status'] === 'completed') {
return $job['results'][0]; // ['status' => ..., 'score' => ..., 'reason' => ...]
}
usleep((int) ($delay * 1_000_000));
}
return null; // still processing — treat as unknown, retry later
}
Die Ebenen zusammenfügen#
Die günstigste zuerst, Stopp, sobald Sie eine Antwort haben:
function validate_email(string $email): array
{
if (!is_valid_syntax($email)) {
return ['status' => 'undeliverable', 'reason' => 'invalid_email'];
}
$domain = substr($email, strrpos($email, '@') + 1);
if (!has_mail_route($domain)) {
return ['status' => 'undeliverable', 'reason' => 'invalid_domain'];
}
return verify($email)['result']; // deliverable | risky | undeliverable | unknown
}
Die beiden lokalen Ebenen kosten nichts und fangen den meisten Müll sofort ab; die API-Ebene läuft nur bei Adressen, die den Netzwerk-Roundtrip wert sind. Diese Reihenfolge ist der ganze Trick – dieselbe Form finden Sie auch in der Node.js- und der Python-Version dieses Leitfadens, denn die Schichtung, nicht die Sprache, ist es, die Validierung funktionieren lässt.
Häufig gestellte Fragen#
Reicht filter_var aus, um eine E-Mail-Adresse in PHP zu validieren?#
Für die Syntax: ja – filter_var($email, FILTER_VALIDATE_EMAIL) ist die richtige
Prüfung auf Ebene eins und die bessere Wahl als eine selbst geschriebene Regex.
Doch es validiert die Form, nicht die Zustellbarkeit: Es löst niemals DNS auf
und kontaktiert keinen Mailserver, ein bestandener Test bedeutet also „sieht aus
wie eine E-Mail“, nicht „wird zugestellt“. Kombinieren Sie es mit einer
MX-Abfrage und einer SMTP-Postfachprüfung, bevor Sie der Adresse vertrauen.
Was ist der Unterschied zwischen checkdnsrr() und getmxrr()?#
checkdnsrr() beantwortet eine Ja/Nein-Frage – veröffentlicht die Domain einen
Eintrag eines bestimmten Typs? – und gibt einen Boolean zurück, was alles ist,
was Sie brauchen, um zu bestätigen, dass eine Mailroute existiert. getmxrr()
geht weiter: Es füllt Arrays per Referenz mit den tatsächlichen MX-Hostnamen und
deren Gewichtungen, verwenden Sie es also, wenn Sie die Mailserver untersuchen
oder sortieren möchten, statt nur zu bestätigen, dass sie vorhanden sind. Beide
geben false zurück, wenn kein passender Eintrag existiert.
Kann ich ein Postfach in PHP ohne externen Dienst verifizieren?#
Teilweise. checkdnsrr() und getmxrr() bestätigen, dass die Domain Mail
annimmt, was tote Domains kostenlos aussortiert und nichts über die
Standardbibliothek hinaus benötigt. Das Postfach zu bestätigen bedeutet eine
SMTP-Konversation, die Sie mit fsockopen() versuchen können, aber nicht von
Ihrem App-Server aus ausführen sollten – Port 25 ist weithin gesperrt und das
Ergebnis hängt von der Reputation Ihrer IP ab. Das ist die Ebene, für deren
Bewältigung ein Verifizierungsdienst existiert.
Sollte ich diese Prüfungen bei der Registrierung oder beim Bereinigen einer Liste durchführen?#
Beides, in unterschiedlicher Tiefe. Führen Sie die Syntax- und die MX-Abfrage synchron bei der Registrierung aus – sie sind schnell genug, um die Anfrage zu blockieren und sofortiges Feedback zu geben – und handeln Sie auch dort auf Basis des unmittelbaren API-Urteils. Reservieren Sie das vollständige, per SMTP bestätigte Ergebnis für die Listenbereinigung und langsamere Back-Office-Arbeit; der Serverless-Registrierungsleitfaden zeigt das Muster von Anfang bis Ende.
Bereit, die SMTP-Ebene hinzuzufügen? Der kostenlose Tarif umfasst 100
Credits, die die vollständige Pipeline durchlaufen, und die
API-Referenz enthält die vollständigen /verify- und
/jobs-Endpunkte mit Copy-and-paste-Beispielen in sieben Sprachen.