Valider une adresse e-mail en Go, ce sont trois tâches sous un seul nom. Les
tutoriels qui vous montrent une regex et s'arrêtent là n'en ont résolu qu'une
seule — et la moins utile qui soit. La vraie validation se fait en couches : un
contrôle de syntaxe peu coûteux, une recherche DNS et une sonde SMTP de la boîte
aux lettres, du moins cher au plus cher. La bibliothèque standard de Go couvre
proprement les deux premières avec net/mail et net, sans aucune dépendance ;
la troisième est un problème réseau qu'il vaut mieux déléguer. Ce guide construit
chaque couche avec du Go fonctionnel et idiomatique, et montre précisément où une
API de vérification prend le relais.
La réponse courte#
Utilisez net/mail.ParseAddress pour la syntaxe, net.LookupMX pour la
recherche MX et une API de vérification pour le contrôle SMTP de la boîte aux
lettres — dans cet ordre, du moins cher au plus cher, en court-circuitant dès
qu'une couche est décisive. Ne vous tournez pas vers net/smtp pour sonder les
boîtes aux lettres depuis votre propre serveur : le port 25 sortant est bloqué
sur la plupart des hébergeurs et, même là où il ne l'est pas, la réponse dépend
de la réputation de l'IP d'envoi et d'un comportement de greylisting que vous ne
voulez pas réimplémenter. Chaque couche écarte des adresses à moindre coût que la
précédente ; seule la dernière peut valider une adresse.
Couche 1 : la syntaxe avec net/mail#
Go n'a aucun besoin d'une regex maison ici. Le paquet net/mail de la
bibliothèque standard analyse les adresses RFC 5322, et ParseAddress renvoie
une erreur non nil sur tout ce qui est malformé — ce qui est exactement le signal
oui/non dont a besoin un filtre de syntaxe :
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
}
La comparaison addr.Address == email est la partie que la plupart des exemples
oublient. ParseAddress est conçu pour lire une ligne de boîte aux lettres
complète, il accepte donc volontiers un nom affiché :
mail.ParseAddress("Jane Doe <jane@example.com>") ne renvoie aucune erreur, avec
addr.Address défini à jane@example.com. Pour un champ d'inscription, vous
voulez l'adresse nue et rien d'autre : comparez donc l'Address analysée à
l'entrée et rejetez tout ce qui portait un nom, des chevrons ou des espaces en
fin de chaîne :
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
Un succès ici signifie « vaut la peine d'être vérifiée », pas « valide ». Il vous
indique que la chaîne a la forme d'une adresse ; il ne dit rien sur la question
de savoir si le courrier arrivera. C'est le même mur contre lequel se heurte
toute vérification d'e-mail par
regex, et net/mail se trouve du même
côté — c'est un analyseur, pas un oracle de délivrabilité. Toutes les couches
ci-dessous supposent que la syntaxe est déjà saine.
Couche 2 : le domaine peut-il recevoir du courrier ?#
C'est ici que la bibliothèque standard fait ses preuves, sans aucune dépendance.
Un domaine qui ne publie aucun enregistrement MX n'a nulle part où le courrier
puisse atterrir, si bien qu'une seule recherche élimine les domaines morts, les
noms d'entreprise mal orthographiés et les TLD inventés. net.LookupMX les
résout :
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 renvoie une tranche de *net.MX triée par préférence, ou une
*net.DNSError lorsque le domaine ne se résout pas. Traiter toute erreur — ou
une tranche vide — comme « aucune route de messagerie » est la lecture prudente
pour un validateur. Si vous voulez tenir compte des domaines qui acceptent le
courrier sur un enregistrement A sans MX explicite (ce que l'on appelle le MX
implicite), repliez-vous sur net.LookupHost(domain) lorsque la tranche MX
revient vide. Pour l'écrasante majorité des adresses réelles, cependant, un
contrôle MX est le bon filtre.
Une note opérationnelle : LookupMX se bloque sur votre résolveur DNS, si bien
qu'un serveur de noms lent ou injoignable fige la goroutine. Dans un chemin de
requête, utilisez le résolveur tenant compte du contexte —
(&net.Resolver{}).LookupMX(ctx, domain) — avec une échéance, et traitez un
dépassement de délai comme « à revérifier plus tard », pas comme un rejet ferme,
afin qu'un incident DNS passager ne fasse jamais fuir un vrai client.
Couche 3 : la boîte aux lettres existe-t-elle vraiment ?#
Les couches 1 et 2 ne peuvent qu'écarter une adresse. Un domaine peut publier des
enregistrements MX parfaits et n'avoir tout de même aucune boîte aux lettres à
l'adresse que vous détenez — noreply-9f2x@gmail.com a une syntaxe valide sur une
route de messagerie active et n'a jamais été une véritable boîte aux lettres.
Confirmer une boîte aux lettres précise implique la conversation de livraison
SMTP : se connecter au serveur de messagerie, émettre RCPT TO, lire la réponse
et se déconnecter avant d'envoyer quoi que ce soit.
Go fournit les outils pour essayer. net/smtp vous donne smtp.Dial et un
Client doté des méthodes Mail, Rcpt et Close, et vous pourriez en principe
conduire vous-même cette poignée de main. En pratique, vous ne devriez pas
l'exécuter depuis votre serveur applicatif. La plupart des fournisseurs cloud
bloquent purement et simplement le port 25 sortant, si bien que la connexion
expire tout simplement en production, même si elle fonctionnait sur votre
ordinateur portable. Là où le port est ouvert, la réponse dépend de la réputation
de l'IP depuis laquelle vous vous connectez, et les serveurs destinataires font
du greylisting et limitent le débit des expéditeurs inconnus — si bien qu'une
sonde naïve se voit différée, étranglée ou mise sur liste de blocage. Il y a plus
qu'un seul aller-retour, aussi : les domaines catch-all acceptent toutes les
adresses et déjouent un unique RCPT TO.
Comment fonctionne la vérification d'e-mails
parcourt tout le pipeline. C'est la couche qu'il vaut la peine de déléguer.
Effectuer le contrôle complet avec une API#
L'API en temps réel de Qualisend exécute tout le pipeline — syntaxe, DNS et la
sonde SMTP de la boîte aux lettres — depuis une infrastructure à réputation gérée
conçue pour cela, et renvoie un verdict. C'est un simple POST JSON, donc net/http
et encoding/json de la bibliothèque standard sont tout ce dont vous avez besoin.
Décodez l'enveloppe result directement dans une structure :
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"`
}
type verifyResponse struct {
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://api.qualisend.com/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)
}
return payload.Result, nil
}
Le point de terminaison et la clé ci-dessus sont des valeurs d'exemple — lisez
votre clé à portée limitée depuis la variable d'environnement
QUALISEND_API_KEY plutôt que de la coder en dur, et consultez la
référence de l'API sur la page développeurs pour connaître la forme
exacte de la requête et de la réponse. L'objet result porte un status valant
deliverable, risky, undeliverable ou unknown, un score numérique, un
reason et une carte sub_flags (adresse de rôle, jetable, fournisseur gratuit,
catch-all, et ainsi de suite) — les champs sur lesquels vous vous appuyez
réellement pour brancher votre logique.
Comme verify prend un context.Context, l'appelant est maître du délai et de
l'annulation. Passez une échéance afin qu'une vérification lente ne fasse jamais
patienter une requête d'inscription :
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)
}
Tout assembler pour valider une adresse e-mail en Go#
Du moins cher au plus cher, arrêtez-vous dès que vous avez une réponse. Les deux couches locales ne coûtent rien et rejettent instantanément la plupart des déchets ; l'appel à l'API ne s'exécute que sur les adresses qui leur ont survécu et qui valent l'aller-retour réseau :
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)
}
Ensuite, branchez votre logique sur le statut selon les besoins de votre produit — rejetez les adresses non délivrables, laissez passer les adresses délivrables et décidez fonctionnalité par fonctionnalité quoi faire des verdicts plus gris :
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
}
Cet ordonnancement est toute l'astuce, et c'est la même structure que vous trouverez dans les versions Node.js et Python de ce guide — c'est le découpage en couches, pas le langage, qui rend la validation fiable. Voyez comment fonctionne la vérification d'e-mails pour comprendre pourquoi chaque étape se situe là où elle est.
Foire aux questions#
net/mail.ParseAddress suffit-il pour valider une adresse e-mail en Go ?#
Pour la syntaxe, oui — net/mail.ParseAddress est le bon contrôle de premier
niveau et un bien meilleur choix qu'une regex maison, car il analyse selon la
RFC 5322 et renvoie une erreur sur une entrée malformée. Mais il valide la
forme, pas la délivrabilité : il ne résout jamais le DNS et ne contacte aucun
serveur de messagerie, si bien qu'une erreur nil signifie « ressemble à une
adresse », pas « sera délivrée ». Comparez addr.Address à votre entrée pour
rejeter les noms affichés, puis associez ce contrôle à une recherche MX et à une
sonde SMTP de la boîte aux lettres.
Comment rechercher les enregistrements MX en Go ?#
Utilisez net.LookupMX(domain) de la bibliothèque standard. Elle renvoie une
tranche d'enregistrements *net.MX triée par préférence, ou une *net.DNSError
lorsque le domaine ne se résout pas. Traitez une erreur ou une tranche vide comme
« aucune route de messagerie », et dans un chemin de requête utilisez la version
tenant compte du contexte (*net.Resolver).LookupMX(ctx, domain) afin qu'un
serveur de noms lent ne puisse pas bloquer la goroutine au-delà de votre échéance.
Puis-je vérifier une boîte aux lettres en Go sans service externe ?#
En partie. net.LookupMX confirme que le domaine accepte le courrier, ce qui
écarte gratuitement les domaines morts et ne nécessite rien de plus que la
bibliothèque standard. Confirmer la boîte aux lettres elle-même implique une
conversation SMTP, que vous pouvez scripter avec net/smtp mais que vous ne
devriez pas exécuter depuis votre serveur applicatif — le port 25 est largement
bloqué et le résultat dépend de la réputation de votre IP d'envoi. C'est la couche
qu'un service de vérification existe pour prendre en charge ; vous pouvez
constater la différence sur n'importe quelle adresse avec le
vérificateur d'e-mails gratuit.
Faut-il valider les e-mails à l'inscription ou lors du nettoyage d'une liste ?#
Les deux, à des profondeurs différentes. Exécutez la syntaxe et la recherche MX de façon synchrone à l'inscription — elles sont assez rapides pour bloquer la requête et donner un retour instantané — et agissez aussi sur le verdict immédiat de l'API. Réservez le travail plus poussé, confirmé par SMTP, au nettoyage des listes et aux tâches de back-office ; le guide d'inscription serverless montre le schéma en temps réel de bout en bout.
Prêt à ajouter la couche SMTP à votre service Go ? La
référence de l'API Qualisend propose le point de terminaison
/verify avec des clés à portée limitée et des exemples à copier-coller, et le
vérificateur d'e-mails gratuit vous laisse observer une
adresse syntaxiquement parfaite revenir undeliverable avant même que vous
n'écriviez une ligne de code.