Validar una dirección de correo en Laravel invita a tratarlo como algo de una
sola línea: añades email a un array de validación y a otra cosa. Esa regla es
un buen punto de partida, pero solo responde a la primera de las tres preguntas
que plantea una validación de verdad. ¿La dirección tiene la forma correcta?
¿Puede su dominio recibir correo? ¿Existe realmente el buzón? Laravel es
peculiar en que su regla email integrada puede responder a las dos primeras
en una sola línea; la tercera es un problema de red que conviene delegar. Esta
guía construye cada capa sobre el propio validador de Laravel y muestra
exactamente dónde se detiene el framework. Es una extensión de la
guía de validación de correo en PHP: Laravel
envuelve los mismos ladrillos de PHP —y filter_var está a una palabra de
distancia— en una API mucho más agradable.
La respuesta corta#
Usa la regla email:rfc,dns de Laravel para la sintaxis y la consulta MX en una
sola pasada, y luego una API de verificación para la comprobación SMTP del buzón:
lo más barato primero, cortocircuitando con bail en cuanto una capa sea
concluyente. No intentes abrir una sesión SMTP desde tu aplicación para sondear
buzones tú mismo: el puerto 25 de salida está bloqueado en la mayoría de los
alojamientos, y la respuesta depende de la reputación de la IP de envío y del
greylisting que no quieres reimplementar. Cada capa descarta direcciones más
barato que la anterior; solo la última puede dar por buena una dirección.
Capas 1 y 2: sintaxis y DNS en una sola regla#
La regla email se apoya en el paquete egulias/email-validator, y admite
estilos que deciden cuán estricta es. Por defecto aplica el estilo rfc
(gramática RFC 5321/5322). El que importa para la validación es dns, que añade
una consulta en vivo y confirma que el dominio resuelve una ruta de correo —un
registro MX, con un registro A como alternativa—. Pide ambos y Laravel hace la
sintaxis y la comprobación del dominio en una única regla:
public function store(Request $request)
{
$validated = $request->validate([
'email' => ['required', 'email:rfc,dns'],
]);
// $validated['email'] is well-formed AND its domain can receive mail.
}
Los demás estilos conviene conocerlos. email:filter ejecuta el
filter_var($email, FILTER_VALIDATE_EMAIL) de PHP —la misma comprobación de la
guía de PHP— y filter_unicode admite algunas
partes locales en Unicode. strict rechaza las advertencias de puntos finales y
consecutivos que el parser del RFC tolera. Y spoof protege frente a caracteres
homógrafos engañosos, así que para un formulario de registro público
email:rfc,dns,spoof es un valor por defecto sensato. En un form request queda
así:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubscriberRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email:rfc,dns,spoof'],
];
}
}
Por qué email:rfc,dns sigue sin ser luz verde#
Un aprobado en email:rfc,dns significa que la cadena está bien formada y que
el dominio puede recibir correo. No dice nada sobre si el buzón concreto existe.
noreply-9f2x@gmail.com pasa. Y también definitely-fake@gmail.com. Ambas son
no entregables, porque el registro MX es un hecho sobre el dominio y el buzón
es un hecho que solo puedes averiguar preguntándole al servidor de correo. Es el
mismo muro que hace que
la validación de correo por regex fracase:
ninguna cantidad de coincidencia de patrones, ni ninguna consulta DNS, cruza de
«el dominio acepta correo» a «esta dirección lo hace».
Si quieres ver la brecha por ti mismo, pega una dirección sintácticamente
perfecta en el comprobador de correo gratuito y observa
cómo una cadena aprobada por la regla vuelve como undeliverable.
Capa 3: ¿existe realmente el buzón?#
Confirmar un buzón concreto implica la conversación de entrega SMTP: conectar con
el host de correo, emitir RCPT TO, leer la respuesta y desconectar antes de
enviar nada. En principio podrías guionizar esto desde PHP, pero no deberías
ejecutarlo desde tu servidor de aplicaciones: la mayoría de los proveedores en la
nube bloquean el puerto 25 de salida, la respuesta depende de la reputación de la
IP desde la que conectas, y los servidores receptores hacen greylisting y limitan
el ritmo a remitentes desconocidos. Y hay más: cómo funciona la verificación de
correo recorre todo el proceso, incluidos los
dominios catch-all, que aceptan cualquier dirección y derrotan a un sondeo ingenuo.
Esta es la capa que vale la pena delegar.
Llamar a Qualisend desde una regla de validación personalizada#
Laravel deja limpia la delegación: envuelve la llamada a la API en un objeto de regla y colócalo en el mismo array de reglas. Genera uno con Artisan:
php artisan make:rule DeliverableEmail
Mantén la clave fuera de tu código conectándola a través de
config/services.php, que la lee de tu .env:
// config/services.php
'qualisend' => [
'key' => env('QUALISEND_API_KEY'),
],
Entonces la regla llama al endpoint de verificación con la facade Http, lee el
sobre result y falla solo cuando el veredicto es undeliverable:
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
class DeliverableEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$response = Http::withToken(config('services.qualisend.key'))
->timeout(10)
->post('https://api.qualisend.com/v1/verify', ['email' => $value]);
// On our own outage or a rate limit, don't block a real signup.
if ($response->failed()) {
return;
}
$result = $response->json('result');
if (($result['status'] ?? null) === 'undeliverable') {
$fail('This email address appears to be undeliverable.');
}
}
}
Http::withToken() fija la cabecera de autenticación bearer, ->post() envía el
cuerpo JSON y $response->json('result') extrae el objeto result anidado con
acceso por puntos. El endpoint de arriba es un marcador de posición —consulta la
referencia de la API para la URL base y la forma exacta de la
petición—, pero la respuesta se lee así:
{
"result": {
"status": "deliverable",
"score": 95,
"reason": null,
"sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
}
}
status es uno de deliverable, risky, undeliverable o unknown. La regla
de arriba solo falla en firme ante undeliverable y deja pasar risky y
unknown, para que decidas más adelante qué hacer con ellos —filtrar por score
o ramificar según una entrada de sub_flags como disposable— en lugar de
convertir una dirección dudosa en un error de formulario. Toma el JSON de arriba
como la forma, no como el contrato; la lista completa de campos vive en la
documentación para desarrolladores.
Uniendo las capas para validar una dirección de correo en Laravel#
Compón las tres en un solo form request, lo más barato primero, y deja que bail
se detenga en el primer fallo, de modo que la regla de la API solo se ejecute
sobre direcciones que ya han superado la sintaxis y la comprobación MX:
namespace App\Http\Requests;
use App\Rules\DeliverableEmail;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubscriberRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => [
'bail',
'required',
'email:rfc,dns',
new DeliverableEmail(),
],
];
}
public function messages(): array
{
return [
'email.email' => 'Enter a valid email address on a real domain.',
];
}
}
bail es todo el truco. Una dirección mal formada o un dominio muerto falla en
local y nunca gasta un crédito de la API; solo las direcciones que merecen el
viaje de ida y vuelta por la red llegan a la capa SMTP. Indica el tipo del request
en tu controlador y Laravel ejecuta el flujo antes de que el cuerpo de tu método
llegue a ejecutarse:
public function store(StoreSubscriberRequest $request)
{
// Every layer has passed — safe to persist.
$subscriber = Subscriber::create($request->validated());
return redirect()->route('subscribers.index');
}
Es la estratificación, no el framework, lo que hace fiable la validación —la misma forma que la versión en PHP, con Laravel colapsando las dos primeras capas en una sola regla—. Ejecuta el flujo síncrono y rápido en el registro y reserva la pasada más lenta y confirmada por SMTP para el trabajo de trastienda: la guía de registro serverless muestra el patrón en tiempo real, y cómo limpiar una lista de correo cubre la parte por lotes.
Preguntas frecuentes#
¿La regla email de Laravel comprueba si el dominio existe?#
Solo si se lo pides. La regla email a secas valida la sintaxis con el paquete
egulias/email-validator (el estilo rfc). Añade el estilo dns —
email:rfc,dns— y Laravel también confirma que el dominio resuelve una ruta de
correo, de modo que un dominio inventado o muerto falla en la misma pasada. Ningún
estilo contacta con el buzón, así que un aprobado sigue significando «con pinta de
entregable», no «entregable».
¿Cuál es la diferencia entre email:rfc, email:filter y email:dns?#
rfc valida contra los RFC 5321/5322 mediante egulias y es el valor por defecto
de Laravel. filter ejecuta el filter_var($email, FILTER_VALIDATE_EMAIL) de
PHP —la misma comprobación de la guía de PHP— mientras que filter_unicode admite
algo de Unicode. dns añade una consulta MX en vivo, y spoof rechaza caracteres
homógrafos engañosos. Los combinas con comas, así que email:rfc,dns es la puerta
práctica de sintaxis más dominio.
¿Cómo verifico en Laravel que un buzón existe?#
No con una regla integrada: confirmar el buzón requiere una conversación SMTP que
no deberías ejecutar desde tu servidor de aplicaciones, porque el puerto 25 está
bloqueado en muchos sitios y el resultado depende de la reputación de tu IP de
envío. Envuelve una API de verificación en un objeto ValidationRule
personalizado, llámala con la facade Http, lee el sobre result y falla ante
undeliverable. Colócala después de email:rfc,dns con bail para que solo se
ejecute sobre direcciones que ya han superado las capas locales.
¿Ralentizará la regla dns el envío de mis formularios?#
Puede. El estilo dns realiza una consulta DNS en vivo en cada validación, así
que un resolvedor lento o inaccesible atasca la petición. Está bien para un único
formulario de registro, pero mantenlo fuera de las importaciones masivas y no lo
apiles con una llamada síncrona a una API a menos que hayas fijado un tiempo de
espera en el cliente Http, como hace la regla DeliverableEmail de arriba con
->timeout(10).
¿Listo para añadir la capa del buzón? Suelta una dirección en el
comprobador de correo gratuito para ver cómo una dirección
aprobada por la regla vuelve como no entregable, y luego conecta ese mismo
veredicto en tu ValidationRule con los ejemplos listos para copiar y pegar de la
referencia de la API.