Por qué no llegan los correos del formulario de contacto y cómo solucionarlo

· Actualizado el: · · Entrega de correo electrónico, SPF, DKIM, DMARC, Mejora del embudo de contacto, B2B

1. Resumen ejecutivo

Cuando el correo de notificación del formulario de contacto «se envía correctamente pero no llega», la causa suele no estar en el código de envío, sino en el diseño del remitente. Las conclusiones son tres:

  1. Fije From: al dominio de su propio sitio y coloque la dirección del usuario del formulario en Reply-To:. El objetivo es que el remitente visible y el dominio realmente autenticado coincidan.
  2. Configure tanto SPF como DKIM. El reenvío tiende a romper el SPF, y si depende solo de SPF, incluso los correos legítimos pueden caer.
  3. Empiece DMARC con la observación en p=none y suba por etapas a quarantine / reject. Pasar directamente a reject puede llegar a bloquear incluso los correos legítimos de su propia empresa.

Las razones y los fundamentos se explican a partir del capítulo 2. Si lo que necesita ahora es aislar un problema de no entrega, también puede empezar directamente por el procedimiento de diagnóstico del capítulo 4.

Público objetivo y entorno de referencia

Este artículo está dirigido a quienes tienen el problema de que no llegan los correos de notificación del formulario de contacto de su propio sitio.

Elemento Contenido
Público objetivo Responsables del sitio web, desarrolladores que implementaron el formulario, personal de sistemas que gestiona el correo dentro de la empresa
Parte independiente del entorno Capítulos 2 a 5 (concepto de remitente, escenarios de fallo, lectura de cabeceras, diseño de From). Aplica igual sea cual sea la infraestructura de envío
Parte que depende del entorno Los ejemplos de configuración del capítulo 6. Toman como referencia PHP y una infraestructura de envío basada en Linux (Postfix/Exim/OpenDKIM), además de SendGrid, SES y Mailgun
Qué no se trata Las pantallas de configuración de CMS o plugins de formularios concretos, el flujo de correo interno de Exchange Online/Microsoft 365, ni el diseño de envío de email marketing

Lo relativo a los registros DNS (SPF/DKIM/DMARC) y al diseño de cabeceras es común tanto si el servidor web es Windows como Linux, y tanto si el envío se hace con un MTA propio como con un servicio externo. Aunque cambie el lenguaje de implementación, lo único que hay que adaptar son dos cosas: «dónde se construye la cabecera» y «dónde se especifica el remitente de sobre».

Términos utilizados en este artículo

Antes de continuar, se resumen los términos que aparecen en el cuerpo del artículo sin explicación adicional.

Término Significado
MTA (Mail Transfer Agent) Software de servidor que entrega el correo. Además de Postfix o Exim, servicios de envío como SendGrid o SES también cumplen este papel
MUA (Mail User Agent) El software de correo que usa el usuario, como la interfaz de Outlook o Gmail
Sobre (envelope) El remitente y el destinatario de entrega que se pasan en el intercambio SMTP (MAIL FROM/RCPT TO). Es algo distinto de las cabeceras From:/To: del cuerpo del mensaje; la relación es como la de un sobre y la carta que contiene
Alineación (alignment) En el juicio de DMARC, que el dominio de la cabecera From: coincida con el dominio autenticado por SPF o DKIM
Reputación La valoración que el lado receptor hace de la IP o el dominio de envío. Si se deteriora, puede provocar que el correo se marque como spam o sea rechazado
PTR Registro DNS de resolución inversa que obtiene el nombre de host a partir de una dirección IP
DSN (Delivery Status Notification) Notificación del resultado de la entrega. Es el formato legible por máquina de los llamados correos de rebote, definido por el RFC 3464
milter Mecanismo para insertar procesamiento de filtrado en el MTA. OpenDKIM, que añade la firma DKIM, también funciona mediante este mecanismo

Estructura de este artículo

  1. Resumen ejecutivo (este capítulo)
  2. El papel de SPF, DKIM, DMARC y la cabecera From — comprensión del mecanismo
  3. Escenarios de fallo habituales en los formularios de contacto
  4. Procedimiento de diagnóstico y comandos — cabeceras, DNS, SMTP, envío real
  5. Patrones de diseño recomendados para From
  6. Guía de configuración según el entorno (SMTP externo / hosting compartido / PHP)
  7. Lista de verificación para la resolución de problemas

2. El papel de SPF, DKIM, DMARC y la cabecera From

En la entrega de correo hay que pensar por separado en el sobre (envelope) y la cabecera. Lo que SPF verifica principalmente es el MAIL FROM de la sesión SMTP, que es el remitente a efectos de entrega. En la entrega final, esa ruta inversa debe quedar registrada como un único Return-Path, y el sistema SMTP emisor no debería generar desde el principio un mensaje que ya lleve la cabecera Return-Path. Por otro lado, From: indica, en el lado de la cabecera del cuerpo del mensaje, «de quién parece venir el correo»; Reply-To: es la dirección de respuesta, y Sender: representa al sujeto que realmente lo envió.

El RFC 5322 define From: como el autor del mensaje y Sender: como el sujeto que realmente lo envía. Si el autor y el sujeto emisor son el mismo, no debería usarse Sender:, y cuando se quiere que la dirección de respuesta sea distinta del autor, lo correcto es usar Reply-To:. Además, el RFC 5322 establece explícitamente que en From: no debería colocarse una dirección que no sea la del autor. Como la notificación de un formulario de contacto normalmente no la envía el propio usuario desde su MUA, sino que la genera el sistema del sitio, un diseño que coloca la dirección del usuario en From: tiende a desviarse también del sentido de la especificación.

DKIM añade una firma a parte de las cabeceras del mensaje y al cuerpo, y el lado receptor la verifica con la clave pública que hay en el DNS. El dominio de firma se expresa con el d= de DKIM-Signature, y la clave pública se consulta mediante un selector, como en selector._domainkey.example.com. DKIM se usa como una autenticación que «sobrevive relativamente bien incluso al reenvío», pero si el cuerpo o las cabeceras firmadas se modifican en el camino, falla el hash del cuerpo bh= o la verificación de la firma.

DMARC no solo mira si SPF o DKIM han pasado, sino si esos dominios ya autenticados son coherentes con el dominio de From:. Este es el punto clave. Aunque SPF pase, si es MAIL FROM=bounces.vendor.net con From: contact@example.com, la alineación de SPF falla. Si DKIM pasa con d=example.com, DMARC puede aprobarse, pero si tampoco hay DKIM, DMARC falla. DMARC define los modos de alineación strict/relaxed mediante adkim/aspf, la política p=none|quarantine|reject y los destinos de informe rua/ruf.

Las directrices recientes de Gmail trasladan directamente esta filosofía de diseño a requisitos operativos. Google especifica que «no se debe suplantar la cabecera From:» y que «en el correo directo, el dominio dentro de la cabecera From: debe coincidir con el dominio SPF o el dominio DKIM». La notificación de un formulario de contacto no es correo de marketing, pero la lógica básica de los filtros del lado receptor es la misma, así que ignorar esta idea reduce la tasa de entrega.

Flujo de autenticación del correo de notificación del formulario de contactoDiagrama que muestra cómo la aplicación web determina las cabeceras From, Reply-To y Sender, cómo el MTA añade la firma DKIM y cómo el MX receptor verifica SPF, DKIM y DMARC consultando el DNS antes de decidir si el correo se recibe, se marca como spam, se rechaza o rebota.MX receptorDNSMTA de envío / servicio SMTPAplicación webUsuario del formularioMX receptorDNSMTA de envío / servicio SMTPAplicación webUsuario del formularioEnvío del formularioDetermina From / Reply-To / SenderSolicitud de envío SMTPAñade la firma DKIMMAIL FROM / RCPT TO / DATAConsulta SPF (MAIL FROM)Consulta la clave pública DKIM (selector._domainkey)Consulta DMARC (_dmarc + dominio From)Evaluación de alineación (Alignment)Recepción, spam, rechazo o rebote

Lo importante de este flujo es que el criterio de juicio de DMARC gira, de principio a fin, en torno al dominio de From:. Como From: se maneja en la aplicación, MAIL FROM en el SMTP y SPF/DKIM/DMARC en el DNS, cada uno por separado, corregir solo uno de ellos no resuelve la no entrega.

3. Escenarios de fallo habituales en los formularios de contacto

El caso más frecuente es colocar la dirección del usuario del formulario en From:. Por ejemplo, si se envía desde el SMTP del sitio pero se pone From: taro@gmail.com, lo que normalmente pasa SPF o DKIM es el dominio del sitio, no el dominio de Gmail. Como resultado, se produce una discrepancia en la que From: es Gmail pero el dominio autenticado es example.com, y la alineación de DMARC falla. El propio Google exige evitar la suplantación de From: y que From: coincida con el dominio de SPF/DKIM.

El siguiente caso más frecuente es que el reenvío rompe el SPF. En el reenvío de correo, la IP de origen vista por el destinatario final tiende a convertirse en «un servidor intermediario que no figura en el SPF del dominio de envío original», por lo que el SPF falla incluso en correos legítimos. Google también indica que «el correo reenviado tiende a fallar en SPF, así que siempre debe usarse DKIM». Además, si el intermediario modifica el cuerpo, añade un prefijo al asunto o agrega un pie de página, incluso el DKIM se rompe.

Otro caso típico es una configuración inicial insuficiente al usar un servicio SMTP externo. En SendGrid hay casos en los que no se puede enviar si no se configura Domain Authentication; al activar Automated Security se generan registros de autenticación basados en CNAME, y según se necesite se puede configurar un Custom Return Path o un Custom DKIM Selector. En SES, por defecto se usa el MAIL FROM de amazonses.com y el SPF se cumple de forma implícita, pero si se quiere lograr la alineación de SPF con el dominio del sitio, hay que configurar un MAIL FROM personalizado. En Mailgun, si el SPF/DKIM del dominio de envío y el MX necesario no están configurados, tampoco se establece una firma de envío correcta.

El MTA local de un hosting compartido es una trampa fácil de pasar por alto. Aunque el propio sitio esté autenticado, si la IP que realmente sale al exterior es una IP compartida con mala reputación, no tiene PTR, o el hosting no ofrece DKIM, la tasa de entrega baja. Google exige el PTR de la IP de origen como requisito, e indica que una mala reputación de una IP compartida también puede causar errores del tipo 5.7.1.

El uso de mail() de PHP o de librerías de envío también es propenso a fallos. El manual de PHP explica que mail() necesita la cabecera From, y que con el parámetro adicional se puede especificar el remitente de sobre mediante sendmail -f. Es decir, lo correcto no es construir uno mismo la cabecera Return-Path:, sino pasar el remitente de sobre al MTA. Si se malinterpreta este punto, el remitente usado para el juicio de SPF y el remitente que la aplicación asume quedan desalineados.

Por último, hay casos en los que se modifica Sender o From en situaciones donde debería usarse Reply-To. Si el único objetivo es dirigir la respuesta hacia el usuario, Reply-To es suficiente. Sender es una cabecera que se usa cuando se quiere dejar explícito que «el autor y el sujeto que realmente envía son distintos», y no es algo que deba usarse habitualmente en un formulario de contacto. Separar si el objetivo del diseño es «que la respuesta vuelva al usuario» o «indicar el sujeto responsable de la notificación» reduce los errores.

4. Procedimiento de diagnóstico y comandos

Lo primero que hay que hacer es mirar la cabecera cruda del correo antes que el código. En Gmail, desde «Mostrar original», y en Outlook, desde «Detalles del mensaje» o «Encabezados de Internet», se pueden consultar Authentication-Results, Return-Path, From, Reply-To, DKIM-Signature y Received. Revisando esto se puede distinguir con bastante precisión si el problema es que «no se está enviando» o que «la autenticación y la coherencia están rotas».

Puntos que revisar primero en la cabecera

Lo prioritario son estos 5 puntos:

  1. El dominio de From:
  2. El dominio de Return-Path:
  3. Si Authentication-Results: muestra spf=pass / dkim=pass / dmarc=pass
  4. Cuando aparece dkim=pass, qué dominio tiene header.i= o d=
  5. Cuando aparece dmarc=fail, si el motivo es un fallo de autenticación o un alignment failure

La guía de resolución de problemas de DMARC de Google también deja claro que aunque el mensaje pase otras autenticaciones, si las cabeceras no están alineadas, DMARC fallará.

Comandos para verificar el DNS

dig es la herramienta estándar para la resolución de problemas de DNS, y el manual de BIND también lo describe como una herramienta de consulta de DNS flexible y con una salida clara. nslookup es un comando de verificación más ligero, que también puede usarse en modo no interactivo. Para verificar la autenticación de un formulario de contacto, hay que consultar como mínimo los tres registros: SPF, DKIM y DMARC.

# SPF
dig +short TXT example.com

# DKIM
dig +short TXT form2026._domainkey.example.com

# DMARC
dig +short TXT _dmarc.example.com

# En Windows
nslookup -type=txt example.com
nslookup -type=txt _dmarc.example.com
nslookup -type=txt form2026._domainkey.example.com

Un ejemplo de la salida esperada tiene este aspecto:

"v=spf1 include:sendgrid.net include:mailgun.org ip4:203.0.113.10 -all"
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
"v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com; ruf=mailto:dmarc-afrf@example.com; adkim=r; aspf=r; fo=1"

Lo que hay que comprobar aquí es si el registro SPF está unificado en uno solo, si se puede obtener la clave pública de DKIM y si DMARC tiene p=. El RFC 7208 establece que SPF debe limitarse a un total de 10 mecanismos que generen consultas DNS, y superar ese límite provoca un permerror.

Verificación de la conexión SMTP y TLS

openssl s_client es el cliente SSL/TLS de propósito general de OpenSSL, útil para verificar el STARTTLS de un servidor SMTP y la cadena de certificados. Con -starttls smtp se inicia el STARTTLS de SMTP, y con -showcerts se puede consultar la lista de certificados que devuelve el servidor.

printf 'QUIT\r\n' | openssl s_client \
  -connect smtp.example.com:587 \
  -starttls smtp \
  -servername smtp.example.com \
  -showcerts \
  -brief

Un ejemplo de salida:

CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Verification: OK
250 CHUNKING

Con esto se puede ver al menos si es posible conectar con el servidor SMTP, si STARTTLS está activo y si no hay anomalías evidentes en la verificación del certificado. Google exige TLS a los remitentes por encima de cierto volumen, y no usarlo puede ser causa del error 5.7.29.

Prueba de envío real para reproducir el problema

swaks es una herramienta práctica dedicada a pruebas SMTP, capaz de reproducir con flexibilidad pruebas de envío que incluyen TLS, autenticación y extensiones SMTP. En la investigación de correos no entregados desde un formulario de contacto, es útil reproducir el problema enviando un único correo «sin pasar por la aplicación, con el mismo SMTP, el mismo From, el mismo Reply-To y el mismo destinatario».

swaks \
  --server smtp.example.com \
  --port 587 \
  --tls \
  --auth LOGIN \
  --auth-user contact@example.com \
  --auth-password '********' \
  --from bounce@example.com \
  --to yourtest@gmail.com \
  --h-From "Notificación del sitio <contact@example.com>" \
  --h-Reply-To "Taro Yamada <visitor@gmail.com>" \
  --header "Subject: swaks test" \
  --body "This is a test"

Un ejemplo típico de envío exitoso es el siguiente:

=== Trying smtp.example.com:587...
=== Connected to smtp.example.com.
<-  250-STARTTLS
<-  250-AUTH LOGIN PLAIN
->  STARTTLS
<-  220 Ready to start TLS
...
<-  250 2.0.0 Ok: queued as ABC123DEF

Si falla a través de la aplicación pero funciona con swaks, es probable que la causa esté más del lado de la construcción de cabeceras de la librería o de la configuración del remitente de sobre. Por el contrario, si también falla igual con swaks, se puede acotar el problema al DNS, al SMTP o a la política del lado receptor.

Diagnóstico externo con mail-tester

mail-tester es un servicio que hace enviar un correo a una dirección de prueba generada aleatoriamente y devuelve un informe detallado tras analizar ese mensaje, el servidor de envío y la IP de envío. Es adecuado como diagnóstico de primer nivel para casos de «de alguna manera no llega» en un MTA local o un servidor compartido.

El uso es sencillo:

  1. Obtener la dirección de prueba emitida por mail-tester
  2. Enviar un correo por la misma ruta que usa el formulario de contacto
  3. Revisar la puntuación y las observaciones sobre SPF/DKIM/DMARC, resolución inversa, listas negras y estructura del cuerpo

No se debe juzgar el envío en producción solo por la puntuación de mail-tester, pero al menos permite detectar rápidamente problemas como «el SPF ni siquiera es visible», «no se puede obtener la clave pública de DKIM» o «el cuerpo o la estructura del remitente son poco naturales».

Ejemplo de análisis de cabecera

Un ejemplo de fallo:

Return-Path: <bounce-123@vendor.example.net>
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of bounce-123@vendor.example.net designates 198.51.100.10 as permitted sender) smtp.mailfrom=vendor.example.net;
       dkim=none;
       dmarc=fail (p=quarantine sp=quarantine dis=none) header.from=gmail.com
From: Taro Yamada <visitor@gmail.com>
Reply-To: Taro Yamada <visitor@gmail.com>
Subject: Consulta

En este correo, aunque el SPF en sí pasa, como From: es gmail.com, DMARC falla. Es la forma de rotura típica cuando se coloca la dirección del usuario en From: en un formulario de contacto.

Un ejemplo de éxito es el siguiente:

Return-Path: <bounce@example.com>
Authentication-Results: mx.google.com;
       spf=pass smtp.mailfrom=example.com;
       dkim=pass header.i=@example.com header.s=form2026;
       dmarc=pass header.from=example.com
From: Example Site <contact@example.com>
Reply-To: Taro Yamada <visitor@gmail.com>
Subject: Notificación de consulta

Con este formato, se mantiene la facilidad de responder al usuario mediante Reply-To:, y como tanto el remitente visible como el remitente autenticado coinciden en example.com, la tasa de entrega se estabiliza notablemente.

5. Patrones de diseño recomendados para From

El principio invariable para un formulario de contacto es hacer coincidir «el dominio usado para la autenticación» con «el dominio From que se muestra al destinatario». A partir de ahí, se deriva únicamente la dirección de respuesta hacia Reply-To:. Separar el diseño en tres capas —usar Sender: solo cuando sea necesario y configurar Return-Path en el sobre— hace que el diseño sea estable.

Patrón Ejemplo de cabecera Caso adecuado Ventajas Puntos a tener en cuenta
Patrón recomendado From: contact@example.com
Reply-To: visitor@gmail.com
Return-Path: bounce@example.com
Casi cualquier formulario de contacto Fácil de alinear con DMARC / fácil de responder / implementación sencilla Si se olvida Reply-To, la respuesta va al propio sitio
Separación en subdominio From: contact@form.example.com
Reply-To: visitor@gmail.com
Return-Path: bounce.form.example.com
Se quiere separar la notificación del formulario del correo principal Fácil de separar la reputación / fácil de gestionar Hay que configurar SPF/DKIM/DMARC también en el subdominio
Tipo con Sender explícito From: contact@example.com
Sender: mailer@example.com
Reply-To: visitor@gmail.com
Requisito especial en el que se quiere dejar explícito el sujeto emisor Permite mostrar el sujeto responsable de la operación Normalmente innecesario. Es redundante si el autor y el sujeto emisor son el mismo
Patrón no recomendado From: visitor@gmail.com
Reply-To: visitor@gmail.com
Implementación que solo busca resaltar la dirección de respuesta Solo la apariencia resulta natural Tiende a causar fallos de DMARC. Debe evitarse en notificaciones de contacto

El fundamento de esta tabla es la definición de From/Sender/Reply-To del RFC 5322, el tratamiento de Return-Path del RFC 5321, y la especificación de que DMARC juzga la coherencia tomando From: como referencia. Como valor por defecto para la notificación de un formulario, basta con la fila 1, el «patrón recomendado». Incluso en situaciones donde se tienta a poner la dirección del usuario en From:, el objetivo se cumple igualmente colocando la dirección de respuesta en Reply-To:.

Lo que conviene recordar especialmente es que Return-Path no es «una cabecera que se edita», sino «el resultado del remitente de sobre usado en la entrega». La implementación correcta es manejarlo con -f en el caso de mail() de PHP, o mediante elementos de configuración como Custom MAIL FROM / Return Path / bounce domain en el caso de un servicio SMTP.

6. Guía de configuración según el entorno

A partir de aquí se organiza el criterio de configuración para los tres patrones más frecuentes en la práctica. Como premisa, para los valores exactos que se introducen en el DNS, dé prioridad siempre a los valores que muestra el panel de administración de cada servicio. Los siguientes ejemplos de registros son representativos, pensados para entender la estructura.

Cuando el sitio usa un SMTP externo

El punto más importante de un SMTP externo es completar primero la autenticación del propio dominio. Google también indica que, al usar un proveedor de servicios de correo, hay que confirmar que ese servicio autentica el SPF y el DKIM del propio dominio.

Configuración recomendada

  • From: es contact@example.com o contact@form.example.com
  • Reply-To: es la dirección del usuario del formulario
  • Return-Path/MAIL FROM es un subdominio de rebote que usted mismo administra, como bounce.example.com
  • DKIM firma con example.com o con el subdominio de envío
  • DMARC se coloca en el dominio de From: visible

Configuración típica de SendGrid

En SendGrid, Domain Authentication es un requisito previo; al activar Automated Security se generan 3 registros CNAME. Si está desactivado, se generan 1 registro MX y 2 registros TXT, y también se puede configurar un Custom Return Path o un Custom DKIM Selector.

; Ejemplo: SendGrid (use los valores reales generados por el panel de administración)
em123.example.com.           CNAME   u123456.wl.sendgrid.net.
s1._domainkey.example.com.   CNAME   s1.domainkey.u123456.wl.sendgrid.net.
s2._domainkey.example.com.   CNAME   s2.domainkey.u123456.wl.sendgrid.net.

_dmarc.example.com.          TXT     "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com"

Un error frecuente en SendGrid es el estado de «haber completado solo la autenticación SMTP, sin configurar la autenticación de dominio». En ese estado, el envío en sí es posible, pero desde el punto de vista del receptor, la relación entre From: y el dominio autenticado queda débil. Lo básico es completar Domain Authentication y, a partir de ahí, configurar Custom Return Path según se necesite.

Configuración típica de SES

SES usa por defecto el MAIL FROM del subdominio amazonses.com, por lo que el SPF en sí se cumple de forma implícita. Sin embargo, si se quiere lograr la alineación de SPF con el dominio del sitio, se usa un MAIL FROM personalizado. En ese caso, SES exige un TXT de SPF y un MX en el dominio del MAIL FROM personalizado, y el MX debe ser exactamente uno. Además, con Easy DKIM se añaden 3 registros CNAME al DNS.

; Ejemplo: SES Easy DKIM
abcde12345._domainkey.example.com.   CNAME   abcde12345.dkim.amazonses.com.
fghij67890._domainkey.example.com.   CNAME   fghij67890.dkim.amazonses.com.
klmno54321._domainkey.example.com.   CNAME   klmno54321.dkim.amazonses.com.

; Ejemplo: SES custom MAIL FROM
bounce.example.com.                  MX      10 feedback-smtp.ap-northeast-1.amazonses.com.
bounce.example.com.                  TXT     "v=spf1 include:amazonses.com -all"

_dmarc.example.com.                  TXT     "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com"

Lo importante en el diseño de SES es que el dominio para MAIL FROM no sea el propio dominio de From: de envío, sino un subdominio dedicado exclusivamente a los rebotes. AWS también recomienda que el MAIL FROM sea un subdominio distinto del dominio desde el que realmente se envía el correo.

Configuración típica de Mailgun

En Mailgun, al verificar el dominio de envío se necesitan un TXT para SPF y un TXT para DKIM, y además se añaden 2 registros MX. Si ya existe un SPF, en lugar de añadir un nuevo registro SPF, se inserta include:mailgun.org en el registro existente. Puede que aparezcan varias claves DKIM, pero el envío es posible siempre que la clave que se usa actualmente esté correctamente publicada en el DNS.

; Ejemplo: uso de Mailgun con un subdominio
mg.example.com.                      TXT     "v=spf1 include:mailgun.org ~all"
mx._domainkey.mg.example.com.        TXT     "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
mg.example.com.                      MX      10 mxa.mailgun.org.
mg.example.com.                      MX      10 mxb.mailgun.org.
email.mg.example.com.                CNAME   mailgun.org.

_dmarc.example.com.                  TXT     "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com"

Mailgun se lleva bien con la operación por subdominios, así que crear un subdominio de envío dedicado como mg.example.com facilita la gestión de las notificaciones de formularios y las notificaciones transaccionales.

Cuando el sitio envía desde el MTA local de un hosting compartido

En un hosting compartido, lo primero que hay que mirar no es la propia aplicación, sino la calidad de la infraestructura de envío del hosting. Si el PTR de la IP de envío, el soporte de DKIM, la reputación de la IP compartida o la visibilidad de los registros de envío son deficientes, esta configuración ya parte en desventaja por sí sola. Google también da mucha importancia al PTR de la IP de envío, e indica que una mala reputación de una IP compartida puede ser causa de bloqueo.

En la práctica, es más seguro avanzar en este orden:

  1. Confirmar si la empresa de hosting permite configurar SPF/DKIM/PTR desde el panel de administración o mediante soporte
  2. Fijar siempre From: al propio dominio
  3. Incluir en el SPF la IP de origen del hosting o el dominio de envío autorizado
  4. Activar DKIM con la función del hosting; si no está disponible, cambiar a un SMTP externo
  5. Si es posible, separar la dirección de rebote para MAIL FROM, como bounce.example.com

En un servidor compartido, consulte primero a su proveedor

En un servidor de alquiler compartido, el margen para tocar usted mismo la infraestructura de envío es limitado. Antes de ponerse manos a la obra, confirme lo siguiente en el panel de administración, el manual o la atención al cliente:

Qué confirmar Qué significa si no se puede confirmar o no está disponible
Si existe una función para firmar el correo saliente con DKIM del propio dominio Sin DKIM, en el momento en que el reenvío hace fallar el SPF, DMARC también falla
Si puede editar usted mismo el registro SPF (incluido el caso de gestionar el DNS con otra empresa) No podrá añadir el origen al SPF, y no podrá corregir spf=fail
Cómo está configurada la IP usada para el envío y su PTR (resolución inversa) Una resolución inversa deficiente puede ser motivo de rechazo en el lado receptor
Si la IP es compartida o dedicada Una IP compartida se ve afectada por la calidad de envío de otros usuarios que la comparten
Si se puede consultar el registro de entrega de correo No podrá distinguir si «se envió» o «fue rechazado»
Si se puede especificar el remitente de sobre (equivalente a -f) No podrá controlar el destino de rebote ni el remitente usado para el juicio de SPF

De todo esto, la posibilidad de firma DKIM y la posibilidad de consultar registros de entrega son lo que marca la diferencia entre «si se puede investigar y mejorar en este entorno». Si ambas cosas son «no se puede», el camino más rápido es derivar solo la notificación del formulario a un SMTP externo. Como la notificación del formulario tiene poco volumen de envío y el alcance del impacto del cambio es limitado, es un tipo de tarea fácil de tratar como primer objeto de migración.

Como ejemplo típico de creación de DKIM por cuenta propia en un hosting compartido, en la familia OpenDKIM se puede usar opendkim-genkey para generar la clave privada y el registro TXT para el DNS. La estructura de colocar la clave pública de DKIM en selector._domainkey.example.com, con su selector, coincide con el RFC 6376.

mkdir -p /etc/opendkim/keys/example.com
opendkim-genkey -D /etc/opendkim/keys/example.com -d example.com -s form2026
chown opendkim:opendkim /etc/opendkim/keys/example.com/form2026.private
chmod 600 /etc/opendkim/keys/example.com/form2026.private

El aspecto tras la generación es el siguiente:

form2026._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
# /etc/opendkim/KeyTable
form2026._domainkey.example.com example.com:form2026:/etc/opendkim/keys/example.com/form2026.private

# /etc/opendkim/SigningTable
*@example.com form2026._domainkey.example.com

No obstante, si en el hosting compartido no puede controlar usted mismo el PTR o el outbound relay, mudarse a un SMTP externo es el camino más rápido. Incluso para un envío de bajo volumen como la notificación de un formulario de contacto, un MTA local con autenticación débil juega en contra frente a Gmail o el correo corporativo.

Cuando el sitio usa PHP mail() o una librería SMTP

mail() de PHP es cómodo, pero desde el punto de vista de la autenticación y la tasa de entrega, depende de «qué MTA hay detrás». El manual de PHP explica que el correo necesita la cabecera From, y que en el envío a través de sendmail_path se puede especificar el remitente de sobre mediante un parámetro adicional. Dicho de otro modo, usar mail() no hace que SPF/DKIM/DMARC se configuren automáticamente.

Como mínimo, diseñe así:

  • From: es contact@example.com
  • Reply-To: es el usuario del formulario
  • El remitente de sobre es bounce@example.com
  • En el cuerpo del mensaje se indica también la dirección del usuario
  • Si es posible, use SMTP autenticado en lugar de mail()

Ejemplo mínimo de configuración con mail()

Es peligroso introducir directamente la entrada del usuario en la cabecera. Si $name o $email contienen CR/LF, un atacante puede insertar cabeceras adicionales como Bcc:, y el formulario se convierte en un relé de spam. El manual de PHP también indica que la entrada externa usada en cabeceras debe validarse y normalizarse siempre. En el siguiente ejemplo, incluido el argumento del remitente de sobre (additional_params), los valores que se insertan en la cabecera se sanitizan de antemano.

<?php

// Devuelve únicamente valores permitidos para la cabecera. Rechaza si contiene CR/LF/NUL.
function sanitize_header_value(string $value): string {
    if (preg_match('/[\r\n\0]/', $value)) {
        throw new InvalidArgumentException('Invalid characters in header value');
    }
    return trim($value);
}

// Valida la dirección de correo conforme al RFC.
function sanitize_email(string $email): string {
    $clean = sanitize_header_value($email);
    if (!filter_var($clean, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Invalid email address');
    }
    return $clean;
}

$to = 'ops@example.com';
$subject = sanitize_header_value('Notificación de consulta');

// $name / $email / $message son la entrada del formulario. $message es para el cuerpo, así que admite CR/LF,
// pero $name / $email, usados en la cabecera, siempre deben rechazar CR/LF.
$safeName  = sanitize_header_value($name);
$safeEmail = sanitize_email($email);

$body = <<<TEXT
Name: {$safeName}
Email: {$safeEmail}

{$message}
TEXT;

$headers = [
    'From' => 'Example Site <contact@example.com>',
    'Reply-To' => sprintf('%s <%s>', $safeName, $safeEmail),
    'Content-Type' => 'text/plain; charset=UTF-8',
];

// additional_params también se pasa al shell, así que use solo valores fijos y no mezcle entrada dinámica.
mail($to, $subject, $body, $headers, '-fbounce@example.com');

Este ejemplo tiene dos puntos clave. Uno es que no se escribe Return-Path: como cabecera, sino que el remitente de sobre se pasa mediante el quinto argumento, -f. El otro es que la entrada del usuario que se inserta en cabeceras como Reply-To: pasa por una función de saneamiento que rechaza CR/LF. Si se construye la cabecera sin sanitizar, un atacante puede inyectar una cadena como \r\nBcc: victim@example.com para insertar cabeceras adicionales, por lo que el manual de PHP también considera imprescindible validar la entrada externa cuando se usa en cabeceras. Como additional_params también acaba pasando al shell, no mezcle entrada del usuario y páselo siempre con un valor fijo.

Ejemplo con una librería SMTP

<?php
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->SMTPSecure = 'tls';
$mail->Username = getenv('SMTP_USER');
$mail->Password = getenv('SMTP_PASS');

$mail->setFrom('contact@example.com', 'Example Site');
$mail->addAddress('ops@example.com');
$mail->addReplyTo($email, $name);

// Según la librería, Sender / return-path pueden configurarse por separado
$mail->Sender = 'bounce@example.com';

$mail->Subject = 'Notificación de consulta';
$mail->Body = $body;

$mail->send();

La ventaja de una librería SMTP es que facilita controlar por separado el remitente de cabecera y el remitente de sobre. Para el uso en un formulario de contacto, es lo más adecuado para un diseño en el que From: queda fijo al dominio del sitio y solo la dirección de respuesta se coloca en Reply-To:.

Ejemplos concretos de SPF, DKIM y DMARC

Ejemplo básico de SPF

example.com. TXT "v=spf1 ip4:203.0.113.10 include:sendgrid.net -all"

En SPF hay que incluir todos los orígenes de envío que realmente se usan. Si se emplea un remitente de terceros, Google también exige comprobar si ese remitente está autenticado con SPF y DKIM. Además, como SPF tiene un límite en el número de consultas DNS, hay que tener cuidado de no acumular demasiados include.

Ejemplo básico de DKIM

form2026._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."

El selector se usa para la rotación de claves, y permite que convivan varias claves públicas en el mismo dominio. En la operación, en lugar de default, resulta más fácil de revisar después usar un nombre que se pueda distinguir por uso o por fecha.

Ejemplo de implementación de DMARC

Primero, el modo de observación:

_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com; ruf=mailto:dmarc-afrf@example.com; adkim=r; aspf=r; fo=1"

A continuación, cuando se quiere poner en cuarentena una parte:

_dmarc.example.com. TXT "v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-agg@example.com; adkim=r; aspf=r"

Por último, la operación estricta:

_dmarc.example.com. TXT "v=DMARC1; p=reject; sp=reject; rua=mailto:dmarc-agg@example.com; adkim=s; aspf=s"

p=none significa solo monitorización, quarantine recomienda poner en cuarentena, y reject recomienda rechazar durante la sesión SMTP. adkim y aspf alternan entre strict/relaxed. En lugar de pasar directamente a reject, es más seguro observar primero el volumen y las fuentes de envío legítimas con none, y luego subir por etapas.

Además, si se envían rua/ruf a un servicio de agregación externo a la empresa, el RFC 7489 exige un registro DNS adicional en el lado del tercero. Por ejemplo, si se envían los informes de example.com a thirdparty.example.net, el lado receptor debe publicar example.com._report._dmarc.thirdparty.example.net TXT "v=DMARC1".

7. Lista de verificación para la resolución de problemas

Por último, se resume un orden de verificación que se puede usar directamente en la práctica. Para la no entrega de correos de un formulario de contacto, el camino más rápido es ir resolviéndolos en orden, de arriba hacia abajo.

Qué verificar primero

  • ¿From: es el propio dominio?
  • ¿Se ha puesto la dirección del usuario en Reply-To:?
  • ¿Authentication-Results muestra spf=pass o dkim=pass y, además, dmarc=pass?
  • Si aparece dmarc=fail, ¿es un fallo de autenticación o un alignment failure?
  • ¿El SPF está unificado en un solo registro?
  • ¿El número de consultas de SPF no es excesivo?
  • ¿Se puede obtener la clave pública de DKIM?
  • ¿DMARC tiene p=?
  • ¿El PTR y la resolución inversa de la IP de origen son correctos?
  • ¿No se está usando una IP compartida, o no se ha deteriorado su reputación?

Qué revisar en los rebotes y los registros

Si llegan correos de rebote, lo importante son Final-Recipient, Status, Action y Diagnostic-Code dentro del DSN en formato message/delivery-status. El RFC 3464 define esta información de fallo de entrega legible por máquina. Por ejemplo, si aparece una línea como Diagnostic-Code: smtp; 550 relay not permitted, es un rechazo del lado SMTP, no de la capa de aplicación.

En el lado del servidor, revise al menos el registro de entrega del MTA. En Postfix aparecen el éxito o fallo de la entrega, la acumulación en cola, el rechazo de relé, el fallo de resolución DNS y las advertencias del milter de DKIM. En Exim ocurre algo similar. A continuación se indican comandos de verificación representativos.

# Ejemplo: Postfix
journalctl -u postfix -n 200 --no-pager
postqueue -p

# Ejemplo: Exim
exim -bp

Según el hosting, la ruta de los registros y los permisos de los comandos varían, así que confirme primero si se puede consultar «el registro de entrega de correo» y no solo «el registro de la aplicación». En un entorno donde esto no es visible, resulta más fácil analizar el problema si se deriva a un SMTP externo.

Cómo verlo en Gmail y Outlook

En Gmail, desde «Mostrar original», y en Outlook de Microsoft, desde «Detalles del mensaje» o «Encabezados de Internet», se puede consultar la cabecera cruda. Para investigar la no entrega en un formulario de contacto, es más eficaz guardar y comparar el texto completo de la cabecera que una captura de pantalla.

Cómo distinguir los errores del lado receptor

Los errores de tipo Gmail son fáciles de interpretar a partir del código.

Ejemplo de error Significado Solución principal
5.7.27 SPF no aprobado Añadir el origen al registro SPF
5.7.30 DKIM no aprobado Corregir la clave DKIM y la configuración de firma
4.7.32 Discrepancia entre From: y el dominio organizativo de SPF/DKIM Revisar el diseño de From:
5.7.25 PTR / resolución inversa deficiente Configurar correctamente la resolución inversa de la IP de envío

Las preguntas frecuentes de Google también especifican estos errores y su tratamiento. En los formularios de contacto, lo más frecuente es la falta de alineación del error 4.7.32.

Criterio final de decisión

Si se cumplen simultáneamente las siguientes 3 condiciones, se puede decir que el diseño de la notificación del formulario de contacto es sólido.

  1. From: está dentro de example.com
  2. Reply-To: es la dirección del usuario del formulario
  3. Authentication-Results muestra dmarc=pass

Si estas tres condiciones se cumplen a la vez, el diseño es coherente sin importar si se usa SendGrid, SES, Mailgun, hosting compartido o una librería SMTP. Por el contrario, si falta aunque sea una, el camino más corto es sospechar primero del diseño de From:.

Referencias

Fuentes primarias de las especificaciones y de cada servicio. Para los valores reales que se introducen en el DNS, dé siempre prioridad a los valores que indique el servicio que esté usando.

Especificaciones (RFC)

Directrices del lado receptor

Servicios de envío e implementación

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Desarrollo de sitios web

Porque el diseño del formulario de contacto, la gestión del remitente de los correos de notificación y el diseño de Reply-To se pueden abordar junto con el desarrollo del sitio web como parte de la mejora del embudo de contacto.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Cuál es la causa más frecuente de que no lleguen los correos del formulario de contacto?
No suele ser la propia conexión SMTP, sino que el remitente visible (From:) y el remitente realmente autenticado (el MAIL FROM de SPF o el d= de DKIM) no coinciden. El caso más frecuente es usar directamente la dirección de Gmail del usuario del formulario como From:. Si se envía desde el SMTP del sitio pero se pone From: taro@gmail.com, lo que se autentica es el dominio del sitio, mientras que el From: es Gmail; esa discrepancia hace que falle la alineación de DMARC.
¿Cómo debería configurarse el From de los correos de notificación del formulario de contacto?
Lo básico es fijar From: al dominio del propio sitio y colocar la dirección del usuario del formulario en Reply-To:. Así se mantiene la facilidad para responder, y como el remitente visible y el remitente autenticado coinciden en el dominio, la tasa de entrega se estabiliza. Sender: se usa solo cuando el autor y el sujeto que realmente envía son distintos, y Return-Path no debe escribirse a mano como cabecera, sino configurarse como remitente de sobre (envelope) en el MTA o en el servicio de correo.
¿Qué debería hacerse primero al investigar la no entrega de correos?
Antes de mirar el código, revise la cabecera cruda del correo recibido. En Gmail, desde «Mostrar original», puede consultar Authentication-Results, Return-Path, From y DKIM-Signature. Lo prioritario es comprobar el dominio de From:, el dominio de Return-Path: y si spf/dkim/dmarc han dado pass. Si aparece dmarc=fail, hay que distinguir si es un fallo de autenticación o un fallo de alineación (alignment failure). En el lado del DNS, use dig o nslookup para consultar los tres registros de SPF, DKIM y DMARC.
¿Se puede configurar DMARC directamente en reject?
Es más seguro no pasar directamente a reject: primero confirme el volumen y las fuentes de envío legítimas en el modo de observación p=none, y después vaya subiendo por etapas a quarantine y reject. p=none significa solo monitorización, quarantine recomienda poner en cuarentena, y reject recomienda rechazar durante la sesión SMTP. Además, el reenvío tiende a romper el SPF, y si el reenvío reescribe el cuerpo o las cabeceras también rompe el DKIM, por lo que es importante no depender solo de SPF y activar siempre DKIM.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog