Valider une adresse e-mail en PHP commence et se termine généralement par une
seule ligne : filter_var($email, FILTER_VALIDATE_EMAIL). C'est intégré, c'est
rapide et c'est réellement utile — mais cela ne répond qu'à la première des trois
questions que pose une vraie validation. L'adresse a-t-elle la bonne forme ? Son
domaine peut-il recevoir du courrier ? La boîte aux lettres existe-t-elle
vraiment ? La bibliothèque standard de PHP répond aux deux premières d'emblée ; la
troisième est un problème réseau qu'il vaut la peine de déléguer. Ce guide
construit la validation d'e-mail en PHP par couches, avec du code fonctionnel pour
chacune, et montre exactement où filter_var s'arrête.
La réponse courte#
Utilisez filter_var() avec FILTER_VALIDATE_EMAIL pour la syntaxe,
checkdnsrr() ou getmxrr() pour la recherche MX, et une API de vérification pour
le contrôle SMTP de la boîte aux lettres — la moins coûteuse d'abord, en
court-circuitant dès que l'une est décisive. N'essayez pas d'ouvrir une session
SMTP depuis PHP pour sonder les boîtes aux lettres vous-même : le port 25 sortant
est bloqué sur la plupart des hébergements, et la réponse dépend de la réputation
de l'IP émettrice et du 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 filter_var#
Pour PHP, filter_var est le bon outil pour la première couche — il vous évite
d'écrire une expression régulière à la main, et il est mieux testé que tout ce que
vous colleriez depuis Stack Overflow. FILTER_VALIDATE_EMAIL renvoie l'adresse en
cas de succès et false en cas d'échec, alors comparez de façon stricte :
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
Le !== false strict a son importance. filter_var renvoie la chaîne filtrée
en cas de succès, pas true, si bien qu'un if (filter_var(...)) souple ne
fonctionne que parce qu'une chaîne non vide est « truthy » — comparer à false
exprime ce que vous voulez dire et survit aux refactorisations. Le garde-fou sur
la longueur relève de la ceinture et des bretelles : une adresse de plus de 320
caractères ne peut pas être réelle, et il est moins coûteux de la rejeter tôt que
de transmettre une chaîne géante à quoi que ce soit en aval.
Sachez ce que ce filtre fait et ne fait pas. Par défaut, il valide selon une
grammaire dérivée des RFC et rejette les parties locales internationalisées
(Unicode) — ainsi 用户@example.com échoue sauf si vous ajoutez le drapeau
FILTER_FLAG_EMAIL_UNICODE sur PHP 7.1 et supérieur. C'est un filtre de syntaxe,
point final : il ne touche jamais au DNS, n'ouvre jamais de socket, et n'a aucune
idée de savoir si example.com existe. Notez aussi que FILTER_SANITIZE_EMAIL
est une tout autre bête — il supprime les caractères non autorisés et renvoie une
chaîne modifiée plutôt qu'une réponse par oui ou par non, alors ne le sortez pas
quand vous voulez valider.
Pourquoi une réussite de filter_var n'est pas un feu vert#
Une réussite de filter_var signifie que la chaîne a la forme d'une adresse
e-mail. Elle ne dit rien sur le fait que le courrier arrivera.
definitely-fake@gmail.com passe. info@company-that-folded.com passe.
typo@gmial.com passe. Toutes les trois sont non délivrables, et aucune
correspondance de motif — filter_var ou l'expression régulière la plus élaborée
que vous puissiez trouver — ne vous le dira jamais, parce que la syntaxe et la
délivrabilité sont des questions différentes. L'une est un fait au sujet de la
chaîne ; l'autre est un fait au sujet d'Internet. C'est le même piège qui fait
échouer la validation d'e-mail par regex,
et filter_var se trouve exactement du même côté de la ligne.
Si vous voulez constater l'écart par vous-même, collez une adresse
syntaxiquement parfaite dans le vérificateur d'e-mail gratuit
et regardez une chaîne approuvée par filter_var revenir undeliverable.
Couche 2 : le domaine peut-il recevoir du courrier ?#
C'est là que PHP se rend utile sans aucune dépendance. Un domaine sans route de
messagerie ne peut accepter de courrier pour personne, si bien que cette seule
recherche élimine les domaines morts, les noms d'entreprise mal orthographiés et
les TLD inventés. Le contrôle le plus rapide est checkdnsrr(), qui renvoie un
booléen indiquant si un type d'enregistrement donné existe :
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
Séparez le domaine de l'adresse avec strrpos afin de toujours couper au
dernier @ :
$domain = substr($email, strrpos($email, '@') + 1);
Lorsque vous avez besoin des hôtes de messagerie réels plutôt que d'un oui/non —
par exemple pour les journaliser ou en inspecter les priorités — tournez-vous
vers getmxrr(). Il remplit un tableau d'hôtes et un tableau de poids
correspondant par référence, et renvoie false lorsqu'il n'y a aucun
enregistrement MX du tout :
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', ...]
}
Couche 3 : la boîte aux lettres existe-t-elle vraiment ?#
Les couches un et deux ne peuvent qu'écarter une adresse. Un domaine peut publier
des enregistrements MX parfaits et n'avoir malgré tout aucune boîte aux lettres à
l'adresse que vous détenez — noreply-9f2x@gmail.com est une syntaxe valide sur
un domaine doté d'une route de messagerie active, et c'est pourtant une boîte aux
lettres qui n'a jamais été créée. Confirmer qu'une boîte aux lettres précise
existe implique la conversation de remise SMTP : se connecter à l'hôte de
messagerie, émettre RCPT TO, lire la réponse et se déconnecter avant d'envoyer
quoi que ce soit. Il y a plus à dire —
comment fonctionne la vérification d'e-mail
parcourt tout le pipeline, y compris les
domaines catch-all qui acceptent toutes les
adresses et déjouent une sonde naïve.
En principe, vous pouvez scripter cela en PHP avec fsockopen() et des commandes
SMTP brutes. En pratique, vous ne devriez pas l'exécuter depuis votre serveur
applicatif : la plupart des fournisseurs cloud bloquent le port 25 sortant, la
réponse dépend de la réputation de l'IP depuis laquelle vous vous connectez, et
les serveurs récepteurs appliquent du greylisting et limitent le débit des
expéditeurs inconnus — si bien qu'une sonde qui fonctionne dans un test local
échoue silencieusement, ou vous fait mettre sur liste de blocage, en production.
C'est la couche qu'il vaut la peine de déléguer.
Effectuer le contrôle complet avec une API#
Le POST /verify 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. Sans aucun framework,
un petit assistant cURL couvre tous les appels que vous ferez :
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]);
}
Si vous utilisez déjà Guzzle, la même requête tient en quelques lignes de moins et gère le JSON et les codes de statut d'erreur à votre place :
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);
Les contrôles locaux reviennent immédiatement, la sonde SMTP étant mise en file d'attente :
{
"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
}
}
Pour la validation d'inscription en direct, le result immédiat suffit
généralement pour agir — rejetez undeliverable, proposez la correction
did_you_mean, signalez disposable. Lorsque vous avez besoin du verdict
confirmé par SMTP, interrogez le job jusqu'à ce que la sonde se termine :
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
}
Assembler les couches#
La moins coûteuse d'abord, arrêtez-vous dès que vous avez une réponse :
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
}
Les deux couches locales ne coûtent rien et attrapent la plupart des déchets instantanément ; la couche API ne s'exécute que sur les adresses qui valent l'aller-retour réseau. Cet ordonnancement est toute l'astuce — le même schéma que vous retrouverez dans les versions Node.js et Python de ce guide, parce que c'est le découpage en couches, et non le langage, qui fait fonctionner la validation.
Foire aux questions#
filter_var suffit-il pour valider une adresse e-mail en PHP ?#
Pour la syntaxe, oui — filter_var($email, FILTER_VALIDATE_EMAIL) est le bon
contrôle de première couche et un meilleur choix qu'une expression régulière
écrite à la main. Mais il valide la forme, pas la délivrabilité : il ne résout
jamais le DNS ni ne contacte de serveur de messagerie, si bien qu'une réussite
signifie « ressemble à un e-mail », pas « sera délivré ». Associez-le à une
recherche MX et à un contrôle SMTP de la boîte aux lettres avant d'accorder votre
confiance à l'adresse.
Quelle est la différence entre checkdnsrr() et getmxrr() ?#
checkdnsrr() répond à une question par oui ou par non — le domaine publie-t-il
un enregistrement d'un type donné ? — et renvoie un booléen, ce qui suffit pour
confirmer qu'une route de messagerie existe. getmxrr() va plus loin : il remplit
par référence des tableaux avec les noms d'hôtes MX réels et leurs poids, alors
utilisez-le lorsque vous voulez inspecter ou trier les serveurs de messagerie
plutôt que simplement confirmer leur présence. Tous deux renvoient false
lorsqu'il n'y a aucun enregistrement correspondant.
Puis-je vérifier une boîte aux lettres en PHP sans service externe ?#
En partie. checkdnsrr() et getmxrr() confirment que le domaine accepte le
courrier, ce qui écarte gratuitement les domaines morts et ne requiert rien de
plus que la bibliothèque standard. Confirmer la boîte aux lettres implique une
conversation SMTP, que vous pouvez tenter avec fsockopen() mais 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. C'est la couche qu'un service de
vérification existe pour prendre en charge.
Dois-je effectuer ces contrôles à l'inscription ou lors du nettoyage d'une liste ?#
Les deux, à des profondeurs différentes. Effectuez 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 à cet endroit. Réservez le résultat complet confirmé par SMTP au nettoyage de listes et aux traitements d'arrière-guichet plus lents ; le guide d'inscription serverless présente le schéma de bout en bout.
Prêt à ajouter la couche SMTP ? Le forfait gratuit inclut 100 crédits
qui exécutent le pipeline complet, et la référence API présente les
endpoints /verify et /jobs complets avec des exemples prêts à copier-coller
dans sept langages.