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

E-Mail-Adressen in Next.js validieren

7 minutes read

Qualisend team
Ein Code-Editor-Fenster mit dem Titel route.ts, das drei sich verengende Next.js-Validierungsebenen zeigt — ein zod-E-Mail-Schema, eine MX-Abfrage und einen Aufruf der Verify-API — und mit einem grünen Deliverable-Badge endet.

Um eine E-Mail-Adresse in Next.js zu validieren, müssen Sie drei Fragen beantworten, und der App Router bietet Ihnen einen sauberen Ort, um jede einzelne zu beantworten. Ist die Adresse korrekt geformt? Kann ihre Domain Mail empfangen? Existiert das Postfach tatsächlich? Die erste ist ein zod-Schema, das Sie zwischen einer Client-Komponente und dem Server teilen; die zweite ist eine DNS-Abfrage; die dritte ist ein Netzwerkproblem, das Sie an eine Verifizierungs-API übergeben. Die Falle besteht darin, bei der ersten Frage stehenzubleiben — ein bestandenes z.string().email() im Browser ist eine UX-Nettigkeit, keine Validierung, und es erreicht Ihre Datenbank nie intakt, denn jeder kann daran vorbei posten.

Die kurze Antwort#

Validieren Sie mit einem gemeinsam genutzten zod-Schema für die Syntax sowohl auf dem Client als auch auf dem Server, mit node:dns/promises für die MX-Abfrage innerhalb eines Route Handlers und mit einer Verifizierungs-API für die SMTP-Postfachprüfung — günstigste zuerst, mit Kurzschluss, sobald eine Ebene entscheidend ist. Halten Sie jede Prüfung, die zählt, auf dem Server: Die Client-Kopie dient dem sofortigen Feedback, und der API-Schlüssel, der die Postfachprüfung authentifiziert, darf niemals an den Browser ausgeliefert werden. Versuchen Sie nicht, aus einer Next.js-Funktion selbst SMTP-Verbindungen zu öffnen, um Postfächer zu prüfen — der ausgehende Port 25 ist auf den meisten Hosts blockiert (und in der Edge-Runtime gänzlich nicht verfügbar), und die Antwort hängt von der Reputation der sendenden IP und von Greylisting ab, das Sie nicht nachbauen wollen.

Ebene 1: Format-Validierung mit einem gemeinsam genutzten zod-Schema#

Next.js hat kein eigenes E-Mail-Primitiv, aber die idiomatische Formatprüfung ist ein zod-Schema, und der Grund, warum zod hier so gut passt, ist, dass dasselbe Schema im Browser und auf dem Server läuft. Definieren Sie es einmal:

// lib/email.ts
import { z } from "zod";

export const signupSchema = z.object({
  email: z.string().email("Enter a valid email address."),
});

Parsen Sie in einer Client-Komponente beim Absenden dagegen, damit der Nutzer sofort einen Fehler erhält, ohne einen Roundtrip. safeParse gibt ein diskriminiertes Ergebnis zurück, das Sie ohne try/catch auslesen können:

"use client";

import { useState } from "react";
import { signupSchema } from "@/lib/email";

export function SignupForm() {
  const [error, setError] = useState<string | null>(null);

  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const email = String(new FormData(e.currentTarget).get("email"));

    // Instant feedback only — this is not a security boundary.
    const parsed = signupSchema.safeParse({ email });
    if (!parsed.success) {
      setError(parsed.error.issues[0].message);
      return;
    }

    const res = await fetch("/api/signup", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    });
    setError(res.ok ? null : (await res.json()).error);
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="email" type="email" autoComplete="email" required />
      {error && <p role="alert">{error}</p>}
      <button type="submit">Sign up</button>
    </form>
  );
}

Die Client-Prüfung ist es wert, vorhanden zu sein — sie spart bei offensichtlichen Tippfehlern eine Anfrage —, aber sie ist das Gegenteil von vertrauenswürdig. Ein Nutzer mit geöffnetem Netzwerk-Tab kann direkt an /api/signup posten und sie vollständig überspringen, sodass der Server exakt dasselbe Schema erneut ausführen muss, bevor er irgendetwas glaubt.

Ebene 2: Kann die Domain Mail empfangen?#

Dies ist die erste Prüfung, die der Browser nicht durchführen kann und zod nicht durchführen wird: Eine Domain ohne MX-Einträge kann für niemanden Mail annehmen, sodass eine einzige DNS-Abfrage tote Domains, falsch geschriebene Firmennamen und erfundene TLDs ausschließt. Ein Next.js Route Handler läuft standardmäßig auf Node, sodass Ihnen node:dns/promises ohne jede Abhängigkeit zur Verfügung steht:

// lib/email.ts (continued)
import { resolveMx } from "node:dns/promises";

export async function hasMailRoute(domain: string): Promise<boolean> {
  try {
    const mx = await resolveMx(domain);
    return mx.length > 0;
  } catch {
    // ENOTFOUND / ENODATA → the domain doesn't exist or publishes no MX.
    return false;
  }
}

Ein Bestehen bedeutet hier „die Domain nimmt Mail an“, nicht „dieses Postfach existiert“ — aber ein Scheitern ist entscheidend und kostenlos, genau das, was Sie von einer günstigen Ebene wollen.

Ebene 3: Existiert das Postfach tatsächlich?#

Die Ebenen 1 und 2 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 der Hand halten — noreply-9f2x@gmail.com ist gültige Syntax auf einer aktiven Mail-Route und dennoch 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.

Sie könnten das aus einer Node-Runtime heraus skripten, aber Sie sollten es nicht aus einer Next.js-Funktion heraus 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 lokal funktioniert, in der Produktion still und leise scheitert oder Sie auf eine Blockliste bringt. Wie E-Mail-Verifizierung funktioniert durchläuft die vollständige Pipeline, Catch-all-Domains und alles. Dies ist die Ebene, die es sich zu delegieren lohnt.

Der Verify-Endpunkt von Qualisend führt die gesamte Pipeline auf reputationsverwalteter Infrastruktur aus und gibt ein Urteil zurück. Rufen Sie ihn serverseitig mit fetch auf und lesen Sie den Schlüssel aus einer Umgebungsvariable, sodass er die Maschine nie verlässt:

// lib/email.ts (continued)
export async function verifyMailbox(email: string): Promise<string> {
  const res = await fetch("https://api.qualisend.com/v1/verify", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.QUALISEND_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email }),
  });

  // Don't block a real signup on our outage — treat a failure as "unknown".
  if (!res.ok) return "unknown";

  const { result } = await res.json();
  return result.status; // "deliverable" | "risky" | "undeliverable" | "unknown"
}

Die Antwort ist ein { "result": { ... } }-Umschlag. Sie lesen result.status neben einem score, einem reason und sub_flags für Merkmale wie Rollen- oder Wegwerf-Adressen:

{
  "result": {
    "status": "deliverable",
    "score": 95,
    "reason": null,
    "sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
  }
}

Schauen Sie in die API-Referenz für die genauen Feldformen; der obige Helfer braucht nur status, um eine Adresse abzulehnen, die nicht zugestellt wird. Beachten Sie, dass die Umgebungsvariable schlicht QUALISEND_API_KEY heißt, nicht mit NEXT_PUBLIC_ präfixiert — dieses Präfix ist es, was einen Wert in das Client-Bundle einbettet, und ein Verifizierungsschlüssel im Browser ist ein Schlüssel, den jeder lesen und verbrauchen kann.

Die Ebenen zusammensetzen, um eine E-Mail-Adresse in Next.js zu validieren#

Nähen Sie nun die drei Helfer in einer einzigen route.ts zusammen, günstigste zuerst, sodass das API-Guthaben nur für eine Adresse ausgegeben wird, die Syntax und MX-Prüfung bereits bestanden hat:

// app/api/signup/route.ts
import { NextResponse } from "next/server";
import { signupSchema, hasMailRoute, verifyMailbox } from "@/lib/email";

export async function POST(request: Request) {
  // Layer 1 — re-validate on the server; the client check is not a boundary.
  const parsed = signupSchema.safeParse(await request.json());
  if (!parsed.success) {
    return NextResponse.json({ error: "Enter a valid email address." }, { status: 400 });
  }

  const { email } = parsed.data;
  const domain = email.slice(email.lastIndexOf("@") + 1);

  // Layer 2 — cheap MX gate, rules out dead domains before any API call.
  if (!(await hasMailRoute(domain))) {
    return NextResponse.json({ error: "That domain can't receive email." }, { status: 400 });
  }

  // Layer 3 — the SMTP mailbox probe, delegated to Qualisend.
  if ((await verifyMailbox(email)) === "undeliverable") {
    return NextResponse.json(
      { error: "We couldn't confirm a mailbox at this address." },
      { status: 400 },
    );
  }

  // Every layer passed (or the probe was inconclusive) — create the account.
  // await createUser(email);
  return NextResponse.json({ ok: true });
}

Diese Reihenfolge ist der ganze Trick: Das Schema kurzschließt Müll, die lokale DNS-Prüfung schließt tote Domains kostenlos aus, und die API wird nur für Adressen getroffen, die beide durchlaufen haben. Der Handler lässt nur undeliverable hart scheitern und lässt risky und unknown durch, sodass eine Grenzfall-Adresse oder ein vorübergehendes API-Scheitern niemals einen echten Kunden abweist — Sie können nachgelagert anhand von score oder eines sub_flags-Eintrags verzweigen, statt die Registrierung zu blockieren.

Wenn Sie das Formular lieber absenden möchten, ohne Client-Fetch-Code zu schreiben, funktioniert derselbe Rumpf in einer Server Action. Geben Sie { error } statt NextResponse zurück und konsumieren Sie es mit useActionState:

"use server";

import { signupSchema, hasMailRoute, verifyMailbox } from "@/lib/email";

export async function signup(_prev: unknown, formData: FormData) {
  const parsed = signupSchema.safeParse({ email: formData.get("email") });
  if (!parsed.success) return { error: "Enter a valid email address." };

  const { email } = parsed.data;
  const domain = email.slice(email.lastIndexOf("@") + 1);
  if (!(await hasMailRoute(domain))) return { error: "That domain can't receive email." };

  if ((await verifyMailbox(email)) === "undeliverable") {
    return { error: "We couldn't confirm a mailbox at this address." };
  }
  return { ok: true };
}

Dies ist dieselbe Drei-Ebenen-Form, die Sie auch im Node.js-Leitfaden finden — die Schichtung, nicht das Framework, ist es, was die Validierung zuverlässig macht. Führen Sie diese schnelle Pipeline beim Signup synchron aus und reservieren Sie tiefere Stapelprüfungen für die Listenbereinigung; der Leitfaden für Serverless-Signups zeigt das Echtzeitmuster von Anfang bis Ende, und der API-Vergleich stellt die Dienste gegenüber, die Ebene drei stützen können.

Häufig gestellte Fragen#

Reicht z.string().email() aus, um eine E-Mail-Adresse in Next.js zu validieren?#

Für die Syntax ja — z.string().email() ist die richtige Prüfung auf Ebene eins und weit besser als eine selbstgebaute Regex. Aber sie validiert die Form: Sie löst nie DNS auf und kontaktiert nie einen Mailserver, daher bedeutet ein Bestehen „sieht aus wie eine E-Mail“, nicht „wird zugestellt“. Und eine reine Browser-Prüfung lässt sich trivial umgehen, indem man direkt an Ihre Route postet. Führen Sie das Schema erneut auf dem Server aus und ergänzen Sie dann eine MX-Abfrage und eine SMTP-Postfachprüfung, bevor Sie der Adresse vertrauen.

Wo lege ich den Qualisend-API-Schlüssel in einer Next.js-App ab?#

In einer serverseitigen Umgebungsvariable namens QUALISEND_API_KEY — niemals in einer mit dem Präfix NEXT_PUBLIC_. Das Präfix NEXT_PUBLIC_ bettet einen Wert in das Client-Bundle ein, sodass ein Verifizierungsschlüssel mit diesem Präfix für jeden lesbar ist, der Ihre Website öffnet. Lesen Sie process.env.QUALISEND_API_KEY innerhalb eines Route Handlers oder einer Server Action, wo er auf dem Server bleibt, und lassen Sie den Browser Ihren eigenen Endpunkt aufrufen statt Qualisend direkt.

Sollte ich eine E-Mail in Next.js auf dem Client oder auf dem Server validieren?#

Beides, aber nur der Server zählt. Führen Sie das gemeinsam genutzte zod-Schema in der Client-Komponente aus, um sofortiges Feedback bei Tippfehlern zu geben, und führen Sie exakt dasselbe Schema erneut im Route Handler oder in der Server Action aus, denn die Client-Prüfung lässt sich vollständig überspringen. Die DNS- und SMTP-Ebenen sind naturgemäß serverseitig — sie benötigen Node-APIs und einen geheimen Schlüssel — sodass der Browser sie nie zu Gesicht bekommt.

Kann ich die DNS/MX-Prüfung in der Edge-Runtime ausführen?#

Nein. node:dns/promises benötigt die Node.js-Runtime, die Route Handler standardmäßig verwenden; eine Route mit export const runtime = "edge" hat kein dns-Modul. Wenn Sie Edge brauchen, lassen Sie das lokale MX-Gate weg und lassen Sie Qualisend DNS als Teil des Verify-Aufrufs auflösen — seine Pipeline führt bereits die MX-Abfrage und die SMTP-Prüfung durch, sodass der einzelne API-Aufruf die Ebenen zwei und drei abdeckt.


Bereit, die Postfach-Ebene hinzuzufügen? Werfen Sie eine syntaktisch perfekte Adresse in den kostenlosen E-Mail-Checker, um zuzusehen, wie eine zod-genehmigte Zeichenkette als undeliverable zurückkommt, und verdrahten Sie dann dasselbe Urteil in Ihren Route Handler mit den Copy-Paste-Beispielen in der API-Referenz.

Your reputation, protected.

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

Get started