Eine E-Mail-Adresse in Go zu validieren sind drei Aufgaben, die einen Namen
tragen. Die Tutorials, die Ihnen eine Regex zeigen und dann aufhören, haben
genau eine davon gelöst – und dazu die am wenigsten nützliche. Echte Validierung
ist mehrschichtig: eine günstige Syntaxprüfung, eine DNS-Abfrage und eine
SMTP-Postfachprüfung, das Günstigste zuerst. Gos Standardbibliothek deckt die
ersten beiden sauber mit net/mail und net ab, ganz ohne Abhängigkeiten;
die dritte ist ein Netzwerkproblem, das man besser abgibt. Dieser Leitfaden baut
jede Ebene mit funktionierendem, idiomatischem Go auf und zeigt genau, wo eine
Verifizierungs-API übernimmt.
Die kurze Antwort#
Verwenden Sie net/mail.ParseAddress für die Syntax, net.LookupMX für die
MX-Abfrage und eine Verifizierungs-API für die SMTP-Postfachprüfung – in dieser
Reihenfolge, das Günstigste zuerst, mit Kurzschluss, sobald eine Ebene eine
Entscheidung liefert. Greifen Sie nicht zu net/smtp, um Postfächer von Ihrem
eigenen Server aus zu prüfen: Der ausgehende Port 25 ist auf den meisten Hosts
blockiert, und selbst dort, wo er es nicht ist, hängt die Antwort von der
Reputation der sendenden IP und vom Greylisting-Verhalten ab, das Sie nicht
nachbauen wollen. Jede Ebene sortiert Adressen günstiger aus als die
vorherige; nur die letzte kann eine Adresse als gültig bestätigen.
Ebene 1: Syntax mit net/mail#
Go braucht hier keine selbst gebaute Regex. Das net/mail-Paket der
Standardbibliothek parst Adressen nach RFC 5322, und ParseAddress gibt bei
allem Fehlerhaften einen Fehler ungleich nil zurück – genau das
Ja/Nein-Signal, das ein Syntax-Gate braucht:
package main
import "net/mail"
func isValidSyntax(email string) bool {
if len(email) > 320 {
return false
}
addr, err := mail.ParseAddress(email)
return err == nil && addr.Address == email
}
Der Vergleich addr.Address == email ist der Teil, den die meisten Beispiele
übersehen. ParseAddress ist darauf ausgelegt, eine vollständige Mailbox-Zeile
zu lesen, und akzeptiert daher bereitwillig einen Anzeigenamen:
mail.ParseAddress("Jane Doe <jane@example.com>") gibt keinen Fehler zurück,
wobei addr.Address auf jane@example.com gesetzt ist. Für ein Anmeldefeld
wollen Sie die reine Adresse und nichts sonst, also vergleichen Sie die
geparste Address mit der Eingabe und weisen Sie alles zurück, was einen Namen,
spitze Klammern oder nachgestellte Leerzeichen mitbrachte:
isValidSyntax("jane@example.com") // true
isValidSyntax("Jane Doe <jane@example.com>") // false — not a bare address
isValidSyntax("not-an-email") // false — ParseAddress errors
isValidSyntax("a@@b.com") // false
Ein Bestehen bedeutet hier „prüfenswert“, nicht „gültig“. Es sagt Ihnen, dass
die Zeichenkette wie eine Adresse geformt ist; über die Frage, ob Mail
ankommt, sagt es nichts aus. Das ist dieselbe Wand, gegen die jede
Regex-basierte E-Mail-Prüfung
läuft, und net/mail steht auf
derselben Seite davon – es ist ein Parser, kein Zustellbarkeitsorakel. Jede
Ebene darunter setzt voraus, dass die Syntax bereits in Ordnung ist.
Ebene 2: Kann die Domain überhaupt Mail empfangen?#
Hier macht sich die Standardbibliothek ohne jede Abhängigkeit bezahlt. Eine
Domain, die keine MX-Einträge veröffentlicht, hat keinen Ort, an dem Mail landen
kann, sodass eine einzige Abfrage tote Domains, falsch geschriebene
Firmennamen und erfundene TLDs eliminiert. net.LookupMX löst sie auf:
package main
import "net"
func hasMailRoute(domain string) bool {
mxs, err := net.LookupMX(domain)
if err != nil {
return false
}
return len(mxs) > 0
}
hasMailRoute("gmail.com") // true
hasMailRoute("company-that-folded.com") // false
LookupMX gibt einen Slice von *net.MX zurück, nach Präferenz sortiert, oder
einen *net.DNSError, wenn die Domain nicht aufgelöst werden kann. Jeden Fehler
– oder einen leeren Slice – als „keine Mailroute“ zu behandeln, ist die sichere
Lesart für einen Validator. Wenn Sie Domains berücksichtigen wollen, die Mail
über einen A-Eintrag ohne expliziten MX annehmen (den sogenannten impliziten
MX), greifen Sie auf net.LookupHost(domain) zurück, wenn der MX-Slice leer
zurückkommt. Für die überwältigende Mehrheit echter Adressen ist eine MX-Prüfung
jedoch der richtige Filter.
Eine betriebliche Anmerkung: LookupMX blockiert auf Ihrem DNS-Resolver, sodass
ein langsamer oder nicht erreichbarer Nameserver die Goroutine anhält.
Verwenden Sie in einem Request-Pfad den kontextbewussten Resolver –
(&net.Resolver{}).LookupMX(ctx, domain) – mit einer Deadline, und behandeln
Sie einen Timeout als „später prüfen“, nicht als harte Ablehnung, damit ein
vorübergehender DNS-Aussetzer nie einen echten Kunden abweist.
Ebene 3: Existiert das Postfach tatsächlich?#
Die Ebenen 1 und 2 können eine Adresse nur aussortieren. Eine Domain kann
perfekte MX-Einträge veröffentlichen und dennoch kein Postfach unter der
Adresse haben, die Sie in Händen halten – noreply-9f2x@gmail.com ist
syntaktisch gültig auf einer aktiven Mailroute und war nie ein echtes Postfach.
Ein bestimmtes Postfach zu bestätigen bedeutet die SMTP-Zustellkonversation:
zum Mailhost verbinden, RCPT TO absetzen, die Antwort lesen und trennen, bevor
irgendetwas gesendet wird.
Go liefert die Werkzeuge, es zu versuchen. net/smtp gibt Ihnen smtp.Dial
und einen Client mit den Methoden Mail, Rcpt und Close, und Sie könnten
diesen Handshake im Prinzip selbst steuern. In der Praxis sollten Sie ihn nicht
von Ihrem Anwendungsserver aus ausführen. Die meisten Cloud-Anbieter blockieren
den ausgehenden Port 25 vollständig, sodass der Verbindungsaufbau in der
Produktion einfach abläuft, selbst wenn er auf Ihrem Laptop funktioniert hat.
Wo der Port offen ist, hängt die Antwort von der Reputation der IP ab, von der
aus Sie sich verbinden, und empfangende Server wenden Greylisting und
Rate-Limiting auf unbekannte Absender an – sodass eine naive Prüfung
zurückgestellt, gedrosselt oder auf Sperrlisten gesetzt wird. Es steckt auch
mehr dahinter als ein einziger Roundtrip: Catch-all-Domains akzeptieren jede
Adresse und laufen einem einzelnen RCPT TO zuwider.
Wie E-Mail-Verifizierung funktioniert
geht die gesamte Pipeline durch. Das ist die Ebene, die es wert ist, delegiert
zu werden.
Die vollständige Prüfung mit einer API durchführen#
Die Echtzeit-API von Qualisend durchläuft die gesamte Pipeline – Syntax, DNS
und die SMTP-Postfachprüfung – auf einer reputationsverwalteten Infrastruktur,
die dafür gebaut ist, und liefert ein Urteil zurück. Es ist ein einfacher
JSON-POST, sodass net/http und encoding/json aus der Standardbibliothek
alles sind, was Sie brauchen. Dekodieren Sie den result-Umschlag direkt in
eine Struktur:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
)
type VerifyResult struct {
Status string `json:"status"` // deliverable | risky | undeliverable | unknown
Score int `json:"score"`
Reason string `json:"reason"`
SubFlags map[string]bool `json:"sub_flags"`
SMTP string `json:"smtp"` // pending | cached | skipped
JobID string `json:"-"` // from the top-level job_id
}
type verifyResponse struct {
JobID string `json:"job_id"`
Result VerifyResult `json:"result"`
}
func verify(ctx context.Context, email string) (VerifyResult, error) {
body, err := json.Marshal(map[string]string{"email": email})
if err != nil {
return VerifyResult{}, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://app.qualisend.com/api/v1/verify", bytes.NewReader(body))
if err != nil {
return VerifyResult{}, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("QUALISEND_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return VerifyResult{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return VerifyResult{}, fmt.Errorf("qualisend: unexpected status %s", res.Status)
}
var payload verifyResponse
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
return VerifyResult{}, fmt.Errorf("qualisend: decode response: %w", err)
}
payload.Result.JobID = payload.JobID
return payload.Result, nil
}
Lesen Sie Ihren
gescopten Schlüssel aus der Umgebungsvariablen QUALISEND_API_KEY, anstatt ihn
fest einzucodieren, und prüfen Sie die
API-Referenz auf der Entwicklerseite für das genaue Format von
Request und Response. Das result-Objekt trägt einen status von
deliverable, risky, undeliverable oder unknown, einen numerischen
score, einen reason und eine sub_flags-Map (Rollenadresse, Wegwerfadresse,
Freemail-Anbieter, Catch-all und dergleichen) – die Felder, auf die Sie
tatsächlich verzweigen.
Die Antwort wartet nicht auf die Postfachprüfung, und SMTP zeigt Ihnen, welche
Art von Antwort Sie haben. cached oder skipped bedeutet, dass das Ergebnis
bereits endgültig ist. pending bedeutet, dass status die lokalen Prüfungen
widerspiegelt, reason noch leer ist und das SMTP-Urteil im Job landet:
GET https://app.qualisend.com/api/v1/jobs/{job_id}?include=results, die ID
steht in JobID. Fragen Sie den Job alle paar Sekunden aus einem
Hintergrund-Worker ab, nie innerhalb des Anmelde-Requests, bis er completed,
canceled oder failed ist, und rufen Sie /verify nicht erneut auf, um
dieselbe Adresse zu aktualisieren.
Da verify einen context.Context entgegennimmt, besitzt der Aufrufer das
Timeout und die Cancellation. Übergeben Sie eine Deadline, damit eine langsame
Verifizierung nie einen Anmelde-Request aufhängt:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := verify(ctx, "jane@example.com")
if err != nil {
log.Printf("verify failed: %v", err)
}
Alles zusammenfügen, um eine E-Mail-Adresse in Go zu validieren#
Das Günstigste zuerst, aufhören, sobald Sie eine Antwort haben. Die beiden lokalen Ebenen kosten nichts und weisen den meisten Unrat sofort ab; der API-Aufruf läuft nur auf Adressen, die sie überstanden haben und den Netzwerk- Roundtrip wert sind:
package main
import (
"context"
"strings"
)
func validateEmail(ctx context.Context, email string) (VerifyResult, error) {
if !isValidSyntax(email) {
return VerifyResult{Status: "undeliverable", Reason: "invalid_email"}, nil
}
domain := email[strings.LastIndex(email, "@")+1:]
if !hasMailRoute(domain) {
return VerifyResult{Status: "undeliverable", Reason: "invalid_domain"}, nil
}
return verify(ctx, email)
}
Verzweigen Sie dann auf den Status, so wie Ihr Produkt es braucht – die Unzustellbaren abweisen, die Zustellbaren durchlassen und pro Feature entscheiden, was mit den Urteilen in der Grauzone geschehen soll:
result, err := validateEmail(ctx, "jane@example.com")
if err != nil {
// network/API failure — fail open or retry, don't block a real user
}
switch result.Status {
case "deliverable":
// accept
case "undeliverable":
// reject at the form
default:
// risky | unknown — flag for review, or allow with a soft warning
}
Diese Reihenfolge ist der ganze Trick, und es ist dieselbe Form, die Sie in den Node.js- und Python-Versionen dieses Leitfadens finden – die Schichtung, nicht die Sprache, macht die Validierung zuverlässig. Siehe wie E-Mail-Verifizierung funktioniert dafür, warum jede Stufe dort sitzt, wo sie sitzt.
Häufig gestellte Fragen
01Reicht net/mail.ParseAddress aus, um eine E-Mail-Adresse in Go zu validieren?
Für die Syntax: ja – net/mail.ParseAddress ist die richtige Prüfung auf Ebene eins und eine weitaus bessere Wahl als eine selbst gebaute Regex, da es gegen RFC 5322 parst und bei fehlerhafter Eingabe einen Fehler zurückgibt. Aber es validiert die Form, nicht die Zustellbarkeit: Es löst niemals DNS auf und kontaktiert keinen Mailserver, sodass ein nil-Fehler „sieht aus wie eine Adresse“ bedeutet, nicht „wird zugestellt“. Vergleichen Sie addr.Address mit Ihrer Eingabe, um Anzeigenamen abzuweisen, und kombinieren Sie die Prüfung dann mit einer MX-Abfrage und einer SMTP-Postfachprüfung.
02Wie frage ich MX-Einträge in Go ab?
Verwenden Sie net.LookupMX(domain) aus der Standardbibliothek. Es gibt einen Slice von *net.MX-Einträgen zurück, nach Präferenz sortiert, oder einen *net.DNSError, wenn die Domain nicht aufgelöst werden kann. Behandeln Sie einen Fehler oder einen leeren Slice als „keine Mailroute“, und verwenden Sie in einem Request-Pfad das kontextbewusste (*net.Resolver).LookupMX(ctx, domain), damit ein langsamer Nameserver die Goroutine nicht über Ihre Deadline hinaus blockieren kann.
03Kann ich ein Postfach in Go ohne externen Dienst verifizieren?
Teilweise. net.LookupMX bestätigt, dass die Domain Mail annimmt, was tote Domains kostenlos aussortiert und nichts über die Standardbibliothek hinaus benötigt. Das Postfach selbst zu bestätigen erfordert eine SMTP-Konversation, die Sie mit net/smtp skripten können, aber nicht von Ihrem App-Server aus ausführen sollten – Port 25 ist weithin blockiert und das Ergebnis hängt von der Reputation Ihrer sendenden IP ab. Genau diese Ebene übernimmt ein Verifizierungsdienst; den Unterschied sehen Sie bei jeder Adresse mit dem kostenlosen E-Mail-Checker.
04Sollte ich E-Mails bei der Anmeldung oder beim Bereinigen einer Liste validieren?
Beides, in unterschiedlicher Tiefe. Führen Sie die Syntaxprüfung und die MX-Abfrage bei der Anmeldung synchron aus – sie sind schnell genug, um den Request zu blockieren und sofortiges Feedback zu geben – und handeln Sie auch dort nach dem unmittelbaren API-Urteil. Reservieren Sie die tiefere, SMTP-bestätigte Arbeit für die Listenbereinigung und Backoffice-Jobs; der Leitfaden zur serverlosen Anmeldung zeigt das Echtzeitmuster von Anfang bis Ende.
Bereit, die SMTP-Ebene zu Ihrem Go-Dienst hinzuzufügen? Die
Qualisend-API-Referenz enthält den /verify-Endpunkt mit
gescopten Schlüsseln und Copy-and-paste-Beispielen, und der
kostenlose E-Mail-Checker lässt Sie zusehen, wie eine
syntaktisch perfekte Adresse als undeliverable zurückkommt, bevor Sie eine
Zeile Code schreiben.



