Por qué tus reportes DMARC nunca llegaron

Si tu rua apunta a otro dominio sin registro _report._dmarc, Google y Microsoft descartan tus reportes DMARC sin avisar. Cómo auditarlo con dig.

Pablo7 min de lectura

Publicaste rua=mailto:dmarc@reportes.example.net, esperaste una semana y el tablero sigue vacío. O algo peor: el tablero muestra datos de un par de receptores pequeños, asumes que la cobertura es completa, y Google y Microsoft (que probablemente mueven la mayor parte de tu correo) no te han enviado un solo XML. Ningún correo rebotó. Ningún validador marcó error. El registro está sintácticamente perfecto.

La causa casi nunca está en tu zona DNS. Está en la zona del destinatario de los reportes. La RFC 7489, en su sección 7.1 (Verifying External Destinations), define un paso de verificación que la mayoría de las guías en español omite. Si ese paso falla, el receptor descarta el destino sin avisarle a nadie.

Tu registro DMARC es una solicitud, no una orden

Cualquiera puede publicar un registro DMARC en su dominio y escribir en rua= la dirección que quiera. Sin ningún control, un atacante podría publicar registros en miles de dominios desechables apuntando rua al buzón de una víctima, y cada gran proveedor de correo le enviaría un XML diario por cada uno de esos dominios. Sería un vector de inundación gratuito, firmado por Google y Microsoft.

Por eso la especificación obliga al receptor a pedir consentimiento al dominio destino antes de enviarle reportes de un dominio ajeno. Ese consentimiento es un registro TXT publicado en la zona de quien recibe los reportes, no en la tuya.

Cómo funciona la verificación

Cuando un receptor conforme termina su ciclo de agregación (normalmente 24 horas) y tiene que enviar el reporte de example.com, hace lo siguiente con cada URI de rua (y de ruf):

  1. Extrae el dominio de la dirección. En dmarc@reportes.example.net es reportes.example.net.
  2. Compara el dominio organizacional del destino con el del dominio que publicó la política. Si coinciden (example.com reportando a dmarc@corp.example.com), no hay verificación: el reporte sale.
  3. Si difieren, construye este nombre: <dominio-de-la-política>._report._dmarc.<dominio-del-destino>.
  4. Consulta el TXT de ese nombre. Si no existe, o si no empieza por v=DMARC1, ese destino se descarta. Los demás destinos del mismo rua se evalúan por separado.

Para el ejemplo, el registro que tiene que existir es este:

example.com._report._dmarc.reportes.example.net.  TXT  "v=DMARC1"

Hay tres matices que casi nadie menciona.

El prefijo es el dominio donde se encontró la política. Si ventas.example.com publica su propio _dmarc con un rua externo, el nombre a autorizar es ventas.example.com._report._dmarc.reportes.example.net. Si el subdominio no tiene registro propio y hereda el del dominio organizacional, el prefijo es example.com.

El destino puede usar un comodín (*._report._dmarc.reportes.example.net) para aceptar reportes de cualquier dominio. Es cómodo para un SaaS, pero reabre exactamente el vector de abuso que el mecanismo intenta cerrar. La alternativa es un registro explícito por cada dominio cliente.

El registro de autorización puede llevar su propio rua=. Si lo trae, el receptor usa esa dirección en lugar de la que tú publicaste. Es la forma en que un proveedor redirige reportes a otro buzón sin que toques tu zona.

El flujo que ejecuta el receptor

Flujo de verificación de destino externo DMARC: si el registro example.com._report._dmarc del dominio destino no existe o no empieza por v=DMARC1, el receptor descarta el destino en silencio

Fíjate en la rama del descarte. No existe canal de error. El receptor no puede notificarte que rechazó el envío, porque la notificación sería precisamente el correo que decidió no mandar.

Auditoría con dig, paso a paso

Paso 1. Lee tu registro tal como lo ve el mundo.

dig +short TXT _dmarc.example.com
"v=DMARC1; p=none; rua=mailto:dmarc@reportes.example.net,mailto:ti@example.com"

Paso 2. Separa los destinos. Aquí hay dos. ti@example.com comparte dominio organizacional: no requiere verificación. dmarc@reportes.example.net es externo: requiere autorización.

Paso 3. Consulta la autorización con el nombre completo.

dig TXT example.com._report._dmarc.reportes.example.net +noall +comments +answer

Hay cuatro resultados posibles, y solo uno es bueno.

El correcto: status: NOERROR con una respuesta que empieza por v=DMARC1.

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41022
example.com._report._dmarc.reportes.example.net. 300 IN TXT "v=DMARC1"

El fallo más común: status: NXDOMAIN. El nombre no existe. Tus reportes hacia ese destino se están descartando.

;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 7731

El fallo engañoso: status: NOERROR sin sección de respuesta (NODATA). El nombre existe como nodo intermedio, o tiene otro tipo de registro, pero no hay TXT. Para el receptor es exactamente igual que NXDOMAIN.

El fallo sutil: hay un TXT, pero no empieza por v=DMARC1 (un error tipográfico, un espacio inicial, un v=spf1 pegado por error). Se descarta igual.

Puedes ver el caso correcto contra una zona real, la nuestra. secureready.io envía sus reportes a sendersec.com, y la autorización está publicada con un registro explícito, sin comodín:

dig +short TXT secureready.io._report._dmarc.sendersec.com
"v=DMARC1"

El error de consulta que da falsos negativos

Mucha gente, al enterarse de que existe este mecanismo, ejecuta esto:

dig TXT _report._dmarc.reportes.example.net

Esa consulta no comprueba nada sobre ti. Le falta el prefijo con tu dominio, que es justamente lo que el receptor pregunta. Además, un comodín *._report._dmarc.reportes.example.net cubre los nombres debajo de ese nodo, no el nodo en sí: la consulta sin prefijo puede devolver vacío aunque el proveedor lo tenga todo bien configurado. La consulta útil siempre lleva tu dominio delante.

Auditar varios destinos de una vez

Si gestionas varios dominios o tu rua tiene varias direcciones, este script revisa rua y ruf contra la regla anterior:

#!/usr/bin/env bash
# uso: ./edv-check.sh example.com
d="${1,,}"
rec=$(dig +short TXT "_dmarc.$d" | tr -d '"\n ')
grep -oE 'ru[af]=[^;]+' <<<"$rec" | cut -d= -f2 | tr ',' '\n' |
sed -E 's/^mailto://; s/!.*$//' | while read -r addr; do
  dest="${addr#*@}"; dest="${dest,,}"
  if [[ "$dest" == "$d" || "$dest" == *".$d" ]]; then
    echo "INTERNO  $addr"; continue
  fi
  ans=$(dig +short TXT "$d._report._dmarc.$dest" | tr -d '"')
  [[ "$ans" == v=DMARC1* ]] && echo "OK       $addr" \
                             || echo "FALTA    $addr  ->  $d._report._dmarc.$dest"
done

Una limitación honesta: la comparación de dominio organizacional está simplificada (sufijo textual, sin Public Suffix List). Para example.com contra example.com.mx marcará el destino como externo, que es correcto: son dominios organizacionales distintos y sí necesitan autorización.

Qué hacer según lo que encuentres

El destino es tu proveedor de reportes. No puedes arreglarlo tú: el registro vive en su zona. Abre un ticket con el nombre exacto que falta (example.com._report._dmarc.reportes.example.net). Antes de hacerlo, comprueba que la dirección de tu rua sea la vigente. Un caso frecuente es un rua que apunta al proveedor anterior, que dejó de autorizar tu dominio el día que cancelaste.

El destino es otro dominio tuyo. Esto es muy común en LATAM con dominios hermanos: empresa.com.gt y empresa.com.mx reportando a un buzón de empresa.com. Son dominios organizacionales distintos, así que el receptor exige autorización aunque todo sea de la misma empresa. Controlas ambas zonas, así que publica tú el registro:

empresa.com.gt._report._dmarc.empresa.com.  TXT  "v=DMARC1"
empresa.com.mx._report._dmarc.empresa.com.  TXT  "v=DMARC1"

Si tu panel DNS completa el sufijo de zona automáticamente (Cloudflare lo hace), en el campo de nombre va solo empresa.com.gt._report._dmarc. Escribir el nombre completo produce ...empresa.com.empresa.com, que no autoriza nada.

Sobre los tiempos. No hay recuperación retroactiva. Los reportes descartados no se reenvían. Tras publicar la autorización, el primer XML llega con el siguiente ciclo de agregación de cada receptor, normalmente entre 24 y 72 horas.

Por qué nadie te avisó

Porque ninguna pieza de la cadena mira las dos zonas a la vez. Los validadores de sintaxis revisan tu _dmarc y lo encuentran correcto, porque lo es. Tu proveedor de reportes muestra un tablero en cero con el mensaje de que está esperando datos. El receptor descarta sin registro visible. Y tu política sigue en p=none, así que nadie nota ningún impacto en la entrega. El resultado es una organización que cree estar observando su dominio y en realidad no está observando nada, a veces mientras paga por hacerlo.

Si prefieres no repetir esta consulta a mano por cada dominio y cada destino, el escáner público de SenderSec en sendersec.com/scan hace esta misma verificación sobre cada rua y ruf externo, junto con el resto de SPF, DKIM y DMARC. No pide cuenta ni correo.