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

E-Mail-Adressen in Node.js validieren

5 minutes read

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

Eine E-Mail-Adresse in Node.js zu validieren, sind drei Aufgaben unter einem Namen. Die meisten Tutorials zeigen Ihnen eine Regex und hören dann auf — das prüft die Schreibweise und nennt es Validierung. Echte Validierung ist geschichtet: eine günstige Syntaxprüfung, ein DNS-Lookup und eine SMTP-Postfachprüfung — und die ersten beiden bringt Node von Haus aus mit. Dieser Leitfaden baut jede Schicht mit lauffähigem Code auf und zeigt, wo eine Verifizierungs-API übernimmt.

Die kurze Antwort#

Verwenden Sie eine tolerante Regex für die Syntax, node:dns/promises für den MX-Lookup und eine Verifizierungs-API für die SMTP-Postfachprüfung — in dieser Reihenfolge, das Günstigste zuerst, und brechen Sie ab, sobald eine Schicht entscheidend ist. Versuchen Sie nicht, SMTP-Verbindungen von Ihrem App-Server aus zu öffnen: Port 25 ist auf den meisten Hosts gesperrt, und selbst wo er es nicht ist, hängt die Prüfung von der Reputation der sendenden IP und der Behandlung von Greylisting ab, die Sie nicht selbst bauen wollen.

Schicht 1: Syntax#

Regex gehört hierher und nirgendwo sonst. Halten Sie sie tolerant — das Ziel ist, Tippfehler bei der Eingabe abzufangen, nicht RFC 5322 nachzubauen (was ohnehin nicht hilft):

const SYNTAX = /^[^\s@"]+(?:\.[^\s@"]+)*@[^\s@.]+(?:\.[^\s@.]+)+$/;

export function isValidSyntax(email) {
  return typeof email === "string" && email.length <= 320 && SYNTAX.test(email);
}

Ein bestandener Test hier bedeutet „lohnt sich zu prüfen“, nicht „gültig“. Jede folgende Schicht setzt voraus, dass die Syntax bereits in Ordnung ist.

Schicht 2: Kann die Domain überhaupt Mail empfangen?#

Hier verdient Node sein Geld. Das integrierte Modul node:dns/promises löst MX-Einträge ohne Abhängigkeiten auf, und eine Domain ohne Mail-Route kann für niemanden Mail annehmen — dieser eine Lookup eliminiert also tote Domains, falsch geschriebene Firmennamen und erfundene TLDs:

import { resolveMx } from "node:dns/promises";

export async function hasMailRoute(domain) {
  try {
    const mx = await resolveMx(domain);
    return mx.length > 0;
  } catch {
    // ENOTFOUND / ENODATA → domain doesn't exist or publishes no MX
    return false;
  }
}
await hasMailRoute("gmail.com");            // true
await hasMailRoute("company-that-folded.com"); // false

Manche Domains nehmen Mail über einen A-Eintrag ohne MX an (implizites MX). Wenn Sie diesen Sonderfall berücksichtigen möchten, greifen Sie auf dns.resolve4 zurück, wenn resolveMx leer ist — aber für die überwiegende Mehrheit echter Adressen ist eine MX-Prüfung der richtige Filter.

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 den SMTP-Zustellungsdialog — RCPT TO, die Antwort lesen, trennen, bevor irgendetwas gesendet wird. Im Prinzip können Sie das mit dem net-Modul tun. In der Praxis sollten Sie es nicht: Die meisten Cloud-Anbieter sperren den ausgehenden Port 25, die Antwort hängt von der Reputation der IP ab, von der Sie sich verbinden, und Server greylisten und drosseln unbekannte Absender. Das ist die Schicht, die man delegieren sollte.

Qualisends POST /verify durchläuft die komplette Pipeline und liefert ein Urteil zurück. Die lokalen Prüfungen kommen sofort zurück, während die SMTP-Prüfung in die Warteschlange gestellt wird:

const BASE = "https://app.qualisend.com/api/v1";

export async function verify(email) {
  const res = await fetch(`${BASE}/verify`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.QUALISEND_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email }),
  });
  if (!res.ok) throw new Error(`Qualisend responded ${res.status}`);
  return res.json();
}
{
  "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 meist aus, um zu handeln — undeliverable ablehnen, die did_you_mean-Korrektur anbieten, disposable markieren. Wenn Sie das SMTP-bestätigte Urteil benötigen, fragen Sie den Job ab (Polling), bis die Prüfung abgeschlossen ist:

export async function verifyAndWait(email, { tries = 10, delayMs = 1500 } = {}) {
  const { job_id } = await verify(email);
  for (let i = 0; i < tries; i++) {
    const res = await fetch(`${BASE}/jobs/${job_id}?include=results`, {
      headers: { Authorization: `Bearer ${process.env.QUALISEND_API_KEY}` },
    });
    const job = await res.json();
    if (job.status === "completed") return job.results[0]; // { status, score, reason, ... }
    await new Promise((r) => setTimeout(r, delayMs));
  }
  return null; // still processing — treat as unknown, retry later
}

Zwei Dinge bringen das naive fetch oben zu Fall, sobald es auf echten Verkehr trifft. Erstens sollte ein langsames Upstream Ihren Node-Prozess nicht blockieren — umschließen Sie die Anfrage mit einem AbortController-Timeout, damit sie schnell fehlschlägt, statt die Event-Loop zu blockieren. Zweitens sind ein 429 (Rate Limit) oder ein 5xx vorübergehend und ein erneuter Versuch lohnt sich, während ein 4xx wie 422 bedeutet, dass die Nutzlast falsch ist und ein erneuter Versuch nicht hilft — trennen Sie also die beiden Fälle:

export async function verify(email, { timeoutMs = 4000 } = {}) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
  try {
    const res = await fetch(`${BASE}/verify`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.QUALISEND_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email }),
      signal: ctrl.signal,
    });
    if (res.status === 429 || res.status >= 500) {
      throw Object.assign(new Error(`retryable ${res.status}`), { retryable: true });
    }
    if (!res.ok) throw new Error(`Qualisend responded ${res.status}`);
    return res.json();
  } finally {
    clearTimeout(timer);
  }
}

Bewahren Sie den API-Schlüssel in process.env auf, niemals im Bundle — dieser Aufruf gehört auf die Serverseite Ihrer Node-App, nicht in etwas, das Sie an den Browser ausliefern.

Bei der Listenbereinigung verifizieren Sie Tausende von Adressen, nicht eine nach der anderen. Schleifen Sie nicht mit await (seriell und langsam) und feuern Sie sie auch nicht alle auf einmal ab (dann stolpern Sie über das Rate Limit). Senden Sie eine begrenzte Anzahl gleichzeitiger Anfragen mit Promise.allSettled, damit eine abgelehnte Adresse nicht den ganzen Batch versenkt:

export async function verifyMany(emails, { concurrency = 5 } = {}) {
  const out = [];
  for (let i = 0; i < emails.length; i += concurrency) {
    const slice = emails.slice(i, i + concurrency);
    const settled = await Promise.allSettled(slice.map((e) => verify(e)));
    out.push(...settled); // each: { status: "fulfilled", value } | { status: "rejected", reason }
  }
  return out;
}

Sammeln Sie die rejected-Einträge, warten Sie mit einem Backoff und wiederholen Sie nur diese — die fulfilled-Urteile sind bereits erledigt. Dasselbe gedrosselte Muster hält Sie ohne zusätzliche Abhängigkeit unter dem Rate Limit Ihres Tarifs.

Die Schichten zusammensetzen#

Das Günstigste zuerst, aufhören, sobald Sie eine Antwort haben:

export async function validateEmail(email) {
  if (!isValidSyntax(email)) return { status: "undeliverable", reason: "invalid_email" };
  const domain = email.slice(email.lastIndexOf("@") + 1);
  if (!(await hasMailRoute(domain))) return { status: "undeliverable", reason: "invalid_domain" };
  const { result } = await verify(email);
  return result; // deliverable | risky | undeliverable | unknown, with reason + sub_flags
}

Die beiden lokalen Schichten kosten nichts und fangen den meisten Müll sofort ab; die API-Schicht läuft nur bei Adressen, die den Netzwerk-Roundtrip wert sind. Diese Reihenfolge ist der ganze Trick — siehe wie E-Mail-Verifizierung funktioniert für den Grund, warum jede Stufe dort sitzt, wo sie sitzt.

Häufig gestellte Fragen#

Reicht eine Bibliothek wie validator.js oder email-validator aus?#

Diese Bibliotheken erledigen Schicht 1 — Syntax — gut und sind ein guter Ersatz für eine selbst gebaute Regex. Aber es sind Syntaxprüfer: Sie lösen keine MX-Einträge auf und bestätigen kein Postfach, sodass ein bestandenes validator.isEmail() weiterhin nur „sieht aus wie eine E-Mail“ bedeutet, nicht „wird zugestellt“. Kombinieren Sie sie mit den DNS- und SMTP-Schichten von oben.

Kann ich aus Node ohne API prüfen, ob eine E-Mail existiert?#

Teilweise. node:dns/promises bestätigt, dass die Domain Mail annimmt, wodurch sich tote Domains kostenlos ausschließen lassen. Das Postfach zu bestätigen bedeutet eine SMTP-Prüfung, die Sie mit dem net-Modul 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 Schicht, für die es einen Verifizierungsdienst gibt.

Sollte ich E-Mails bei der Registrierung synchron validieren?#

Führen Sie die sofortigen Schichten synchron aus — Syntax und MX sind schnell genug, um die Anfrage zu blockieren und dem Nutzer unmittelbares Feedback zu geben. Behandeln Sie das SMTP-Ergebnis als die langsamere Antwort: Handeln Sie bei der Registrierung nach dem sofortigen lokalen Urteil und nutzen Sie das vollständige, SMTP-bestätigte Ergebnis für die Listenbereinigung. Der Leitfaden zur serverlosen Registrierung zeigt das Muster von Anfang bis Ende.

Sollte ich Verifizierungsergebnisse zwischenspeichern, um dieselbe Adresse nicht erneut zu prüfen?#

Ja. Jeder Verifizierungsaufruf ist ein Netzwerk-Roundtrip und ein verbrauchtes Guthaben, wiederholen Sie ihn also nicht für eine Adresse, die Sie gerade geprüft haben. Verwenden Sie als Schlüssel für einen kurzlebigen Cache die normalisierte E-Mail — zuerst in Kleinbuchstaben umwandeln und trimmen — und verwenden Sie das Urteil für ein paar Minuten wieder. Ein Vorbehalt: Speichern Sie unknown- oder noch pending-Ergebnisse nicht zwischen, denn genau diese lohnt es sich erneut zu prüfen, sobald die SMTP-Prüfung abgeschlossen ist. Eine einfache Map genügt für einen einzelnen Node-Prozess; greifen Sie nur dann zu Redis, wenn Sie mehr als eine Instanz betreiben und diese sich den Cache teilen müssen.


Bereit, die SMTP-Schicht hinzuzufügen? Der kostenlose Tarif enthält 100 Guthaben, die die komplette Pipeline durchlaufen, und die API-Referenz enthält die vollständigen /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