Skip to content
Inizia con 100 crediti di verifica gratuiti
Qualisend
Tutti gli articoli
Ingegneria / 22 luglio 2026

Come validare un indirizzo email in PHP

7 min di lettura

Qualisend team
Come validare un indirizzo email in PHP

Validare un indirizzo email in PHP di solito inizia e finisce con una riga: filter_var($email, FILTER_VALIDATE_EMAIL). È una funzione nativa, è veloce ed è davvero utile, ma risponde solo alla prima delle tre domande che una verifica seria si pone. L'indirizzo ha la forma corretta? Il suo dominio può ricevere posta? La casella esiste davvero? La libreria standard di PHP risponde alle prime due senza bisogno di altro; la terza è un problema di rete che conviene delegare. Questa guida costruisce la validazione email in PHP a livelli, con codice funzionante per ciascuno, e mostra esattamente dove si ferma filter_var.

In breve#

Usa filter_var() con FILTER_VALIDATE_EMAIL per la sintassi, checkdnsrr() o getmxrr() per la ricerca MX e un'API di verifica per il controllo SMTP della casella, partendo dal più economico e fermandoti appena uno dà una risposta definitiva. Non provare ad aprire da PHP una sessione SMTP per testare le caselle per conto tuo: la porta 25 in uscita è bloccata dalla maggior parte dei provider di hosting, e la risposta dipende dalla reputazione dell'IP di invio e dal greylisting, che non vuoi certo reimplementare. Ogni livello esclude indirizzi a un costo minore dell'ultimo, e solo l'ultimo può confermarne uno.

Livello 1: la sintassi con filter_var#

In PHP, filter_var è lo strumento giusto per il primo livello: ti evita di scrivere a mano una regex ed è più collaudato di qualunque cosa potresti copiare da Stack Overflow. FILTER_VALIDATE_EMAIL restituisce l'indirizzo in caso di successo e false in caso di errore, quindi usa un confronto stretto:

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

Il !== false stretto è importante. filter_var restituisce la stringa filtrata in caso di successo, non true, quindi un if (filter_var(...)) non stretto funziona solo perché una stringa non vuota è truthy: confrontare con false dice esattamente cosa intendi e resiste ai refactoring. Il controllo sulla lunghezza è una doppia sicurezza: un indirizzo più lungo di 320 caratteri non può essere reale, ed è più economico scartarlo subito che passare una stringa enorme a tutto ciò che viene dopo.

Tieni presente cosa fa e cosa non fa questo filtro. Per impostazione predefinita valida secondo una grammatica derivata dalle RFC e rifiuta le parti locali internazionalizzate (Unicode): quindi 用户@example.com non passa, a meno che tu non aggiunga il flag FILTER_FLAG_EMAIL_UNICODE da PHP 7.1 in su. È un filtro di sintassi, punto: non tocca mai il DNS, non apre mai un socket e non ha idea se example.com esista. Nota anche che FILTER_SANITIZE_EMAIL è tutt'altra cosa: rimuove i caratteri non consentiti e restituisce una stringa alterata invece di una risposta sì/no, quindi non usarlo quando vuoi validare.

Perché superare filter_var non è un via libera#

Superare filter_var significa che la stringa ha la forma di un indirizzo email, ma non dice nulla sul fatto che la posta arriverà. definitely-fake@gmail.com passa. info@company-that-folded.com passa. typo@gmial.com passa. Tutti e tre sono non recapitabili, e nessun pattern matching (né filter_var né la regex più elaborata che riesci a trovare) potrà mai dirtelo, perché sintassi e deliverability sono domande diverse. Una riguarda la stringa; l'altra riguarda internet. È la stessa trappola che fa fallire la validazione email con le regex, e filter_var sta esattamente dalla stessa parte della linea.

Se vuoi vedere la differenza con i tuoi occhi, incolla un indirizzo sintatticamente perfetto nella verifica email gratuita e guarda una stringa approvata da filter_var tornare come non recapitabile (undeliverable).

Livello 2: il dominio può ricevere posta?#

Qui PHP si fa valere senza alcuna dipendenza. Un dominio senza un instradamento della posta non può ricevere messaggi per nessuno, quindi questa sola ricerca elimina i domini inesistenti, i nomi di aziende scritti male e i TLD inventati. Il controllo più rapido è checkdnsrr(), che restituisce un booleano che indica se esiste un dato tipo di record:

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

Separa il dominio dall'indirizzo con strrpos, così tagli sempre in corrispondenza dell'ultima @:

$domain = substr($email, strrpos($email, '@') + 1);

Quando ti servono gli host di posta effettivi invece di un sì/no (ad esempio per registrarli nei log o esaminarne le priorità), usa getmxrr(). Riempie per riferimento un array di host e un array corrispondente di pesi, e restituisce false quando non c'è alcun record MX:

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', ...]
}

Livello 3: la casella esiste davvero?#

I primi due livelli possono solo escludere un indirizzo. Un dominio può pubblicare record MX perfetti e non avere comunque alcuna casella all'indirizzo che hai in mano: noreply-9f2x@gmail.com ha una sintassi valida su un dominio con un instradamento di posta attivo, ma resta una casella mai creata. Confermare che una casella specifica esista richiede la conversazione SMTP di consegna: collegarsi all'host di posta, inviare RCPT TO, leggere la risposta e disconnettersi prima di inviare qualsiasi cosa. C'è dell'altro: come funziona la verifica email ripercorre l'intera pipeline, compresi i domini catch-all che accettano qualsiasi indirizzo e mandano a vuoto un test ingenuo.

In teoria puoi scriverlo in PHP con fsockopen() e comandi SMTP grezzi. In pratica non dovresti eseguirlo dal tuo server applicativo: la maggior parte dei cloud provider blocca la porta 25 in uscita, la risposta dipende dalla reputazione dell'IP da cui ti colleghi, e i server riceventi applicano il greylisting e limitano la frequenza dei mittenti sconosciuti. Così un test che funziona in locale fallisce in silenzio in produzione, o ti fa finire in blacklist. È il livello che conviene delegare.

Il controllo completo con un'API#

POST /verify di Qualisend esegue l'intera pipeline (sintassi, DNS e test SMTP della casella) da un'infrastruttura con reputazione gestita, costruita apposta, e restituisce un esito. Senza framework, un piccolo helper cURL copre ogni chiamata che farai:

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]);
}

Se usi già Guzzle, la stessa richiesta è qualche riga più corta e gestisce per te il JSON e i codici di stato di errore:

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);

I controlli locali tornano subito, con il test SMTP in coda:

{
  "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
  }
}

Per la verifica in tempo reale durante la registrazione, il result immediato di solito basta per decidere: rifiuta undeliverable, proponi la correzione did_you_mean, segnala disposable. Quando ti serve l'esito confermato via SMTP, interroga periodicamente l'elaborazione finché il test non è terminato:

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
}

Mettere insieme i livelli#

Parti dal più economico e fermati appena hai una risposta:

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
}

I due livelli locali non costano nulla e intercettano subito la maggior parte degli indirizzi spazzatura; il livello API viene eseguito solo sugli indirizzi che meritano la chiamata di rete. Tutto il trucco sta in quell'ordine: è la stessa struttura che trovi nelle versioni Node.js e Python di questa guida, perché a far funzionare la validazione è la struttura a livelli, non il linguaggio.

Domande frequenti#

filter_var basta per validare un indirizzo email in PHP?#

Per la sintassi sì: filter_var($email, FILTER_VALIDATE_EMAIL) è il controllo giusto per il primo livello e una scelta migliore di una regex scritta a mano. Ma valida la forma, non la deliverability: non risolve mai il DNS né contatta un server di posta, quindi superarlo significa "sembra un'email", non "verrà recapitata". Abbinalo a una ricerca MX e a un controllo SMTP della casella prima di fidarti dell'indirizzo.

Qual è la differenza tra checkdnsrr() e getmxrr()?#

checkdnsrr() risponde a una domanda sì/no (il dominio pubblica un record di un certo tipo?) e restituisce un booleano, che è tutto ciò che serve per confermare che esiste un instradamento della posta. getmxrr() va oltre: riempie per riferimento degli array con i nomi host MX effettivi e i relativi pesi, quindi usalo quando vuoi esaminare o ordinare i server di posta invece di confermare soltanto che ci sono. Entrambe restituiscono false quando non c'è alcun record corrispondente.

Posso verificare una casella in PHP senza un servizio esterno?#

In parte. checkdnsrr() e getmxrr() confermano che il dominio accetta posta, il che esclude gratis i domini inesistenti e non richiede nulla oltre alla libreria standard. Confermare la casella richiede una conversazione SMTP, che puoi tentare con fsockopen() ma che non dovresti eseguire dal tuo server applicativo: la porta 25 è bloccata quasi ovunque e il risultato dipende dalla reputazione del tuo IP. È proprio il livello di cui si occupa un servizio di verifica.

Conviene eseguire questi controlli alla registrazione o durante la pulizia di una lista?#

Entrambe le cose, con una profondità diversa. Esegui sintassi e ricerca MX in modo sincrono alla registrazione (sono abbastanza veloci da bloccare la richiesta e dare un riscontro immediato) e lì agisci anche sull'esito immediato dell'API. Riserva il risultato completo confermato via SMTP alla pulizia delle liste e al lavoro di back-office più lento; la guida alla verifica serverless in fase di registrazione mostra lo schema dall'inizio alla fine.


Vuoi aggiungere il livello SMTP? Il piano Free include 100 crediti che eseguono l'intera pipeline, e il riferimento API contiene gli endpoint completi /verify e /jobs con esempi da copiare e incollare in sette linguaggi.

La tua reputazione, protetta..

Pulisci la tua prima lista in pochi minuti. 100 crediti gratuiti, senza carta di credito.

Inizia