Debugging de email sends con SQL
Cuando un Send salió pero los números no matchean lo esperado, el flujo de diagnóstico es siempre el mismo — funnel de audiencia, check de supresión, reconciliación de _Sent, breakdown de bounces/errores. Cinco queries que encuentran el bug rápido.
Algo se torció. Un Send tenía que llegar a 50.000 subscribers y solo lo vieron 32.000. O tenía que suprimir competidores y una dirección de competidor lo abrió. O el reporte muestra un conteo de delivered que no matchea el row count del audience DE. El flujo de diagnóstico es siempre el mismo: caminar el funnel desde la audiencia hasta delivered, encontrar dónde el conteo de filas se cae, y leer el DE que es dueño de esa caída.
Esta página es el playbook de cinco queries. Corrélas en orden. La que devuelve "off por 18.000" es la que es dueña del bug.
El funnel
[ Row count del Audience DE ]
↓ exclusiones en la Send Definition
[ Eligibles después de supresiones / unsubs / bounce-out ]
↓ ejecución de la Send Activity
[ Filas _Sent para el JobID ]
↓ delivery al MTA
[ _Sent menos _Bounce ]Cada flecha es un lugar donde las filas pueden desaparecer. Las queries de diagnóstico abajo chequean cada nivel.
Step 1 — Row count del Audience DE
Empezá en el origen. ¿Cuántas filas contiene realmente el audience DE en el momento que se disparó el Send?
SELECT
COUNT(*) AS TotalRows,
COUNT(DISTINCT SubscriberKey) AS UniqueSubs,
COUNT(CASE WHEN EmailAddress IS NULL OR EmailAddress = '' THEN 1 END) AS MissingEmail
FROM de_send_audience_<send_name>;Tres números, una query:
TotalRows: lo que la Send Activity ve como input.UniqueSubs: si esto es menor queTotalRows, la audiencia tiene duplicados y el setting "deduplicate by SubscriberKey" de la Send Definition decide si mandaste dos veces o una.MissingEmail: subscribers en la audiencia pero inalcanzables. Están "en la audiencia" para reportes pero nunca pasan del Send.
Si TotalRows es menor que lo esperado, el bug está aguas arriba del Send — en la SQL Activity de audience-build, no en el Send mismo. Pará acá y auditá la lógica de rebuild del audience DE.
Step 2 — Overlap de supresión
¿Cuántas de esas filas de audiencia hubieran sido excluidas por las exclusion lists de la Send Definition, los DEs de supresión, o el status de All Subscribers?
-- Reemplazá 'master_suppression' con el nombre real de tu DE de supresión.
-- Agregá LEFT JOINs adicionales por cada exclusion list configurada en
-- la Send Definition.
SELECT
COUNT(*) AS WouldSend,
COUNT(s.SubscriberKey) AS ExcludedBySuppression,
COUNT(*) - COUNT(s.SubscriberKey) AS NetEligible
FROM de_send_audience_<send_name> a
LEFT JOIN master_suppression s
ON a.SubscriberKey = s.SubscriberKey;Si ExcludedBySuppression saltó vs el send anterior (compará contra de_log_send_runs), una adición a la lista de supresión es la causa. Chequeá quién la editó y cuándo.
Step 3 — Row count de _Sent para el JobID
Compará lo que la Send Activity emitió realmente contra lo que el step 2 dijo era eligible.
-- Reemplazá 12345 con el JobID del run history de la Send Definition
SELECT
COUNT(*) AS SentRows,
COUNT(DISTINCT SubscriberKey) AS UniqueSubsSent,
MIN(EventDate) AS FirstEventAt,
MAX(EventDate) AS LastEventAt
FROM _Sent
WHERE JobID = 12345;Tres formas de falla acá:
SentRowses menor que NetEligible del step 2: el Send fue throttleado, pausado, o la reputación de IP disparó un hold de delivery. Cross-referenciá el rango de timestamp del run log de la Send Definition contraLastEventAt— siLastEventAtes horas después del fin grabado del Send, el throttling de IP es la causa.SentRowses mayor que NetEligible: una audience-build en modo Append re-corrió sin truncar, o la Send Definition está disparando contra una unión de audiencia vieja + nueva. Auditá la target action de la Activity del audience DE.SentRows = 0: el Send no ejecutó contra el JobID. Verificá que el JobID es correcto y que la Send Definition está habilitada.
Step 4 — Breakdown de bounce + error
De las filas _Sent, ¿cuántas realmente entregaron? _Sent registra el intento, no el delivery.
SELECT
s.JobID,
COUNT(*) AS Attempted,
COUNT(b.SubscriberKey) AS Bounced,
COUNT(*) - COUNT(b.SubscriberKey) AS Delivered,
CAST(COUNT(b.SubscriberKey) * 100.0 / COUNT(*) AS DECIMAL(5,2))
AS BouncePct
FROM _Sent s
LEFT JOIN _Bounce b
ON s.JobID = b.JobID
AND s.SubscriberKey = b.SubscriberKey
WHERE s.JobID = 12345
GROUP BY s.JobID;Un BouncePct arriba de ~3% es bandera roja de deliverability — investigá la frescura de la audiencia (subscribers de imports más viejos que 90 días son típicamente sucios) y el estado de IP warm-up.
Step 5 — Snapshot de reconciliación
Una vez encontrado el bug, escribí el snapshot del funnel a un de_log_send_diagnostics así la próxima vez que algo se vea raro tenés baselines históricos para comparar.
INSERT INTO de_log_send_diagnostics
SELECT
GETDATE() AS DiagnosticAt,
'<send_name>' AS SendName,
12345 AS JobID,
(SELECT COUNT(*) FROM de_send_audience_<send_name>) AS AudienceTotal,
(SELECT COUNT(*) FROM _Sent WHERE JobID = 12345) AS SentTotal,
(SELECT COUNT(*) FROM _Bounce WHERE JobID = 12345) AS BounceTotal,
(SELECT COUNT(DISTINCT SubscriberKey) FROM _Open WHERE JobID = 12345) AS UniqueOpens;Corré esto después de cada Send (manualmente o como parte de la Automation que es dueña del Send). Seis meses después cuando un stakeholder pregunte "este Send está under-performing", tenés el data para responder "comparado contra qué baseline".
Causas comunes rankeadas por frecuencia
| Causa | Cómo detectarla | Dónde |
|---|---|---|
| El audience DE no se reconstruyó esta corrida | Step 1 devuelve 0 o row count rancio | Auditá los logs de la Activity de audience-build |
| La lista de supresión creció | El ExcludedBySuppression del Step 2 saltó vs corridas previas | Comparar contra baseline de_log_send_runs |
| Send Definition deshabilitada / JobID equivocado | Step 3 devuelve 0 filas | Chequear el status de la Send Definition + output de la Activity |
| Throttling de reputación de IP | El LastEventAt del Step 3 corre horas pasado el Send window | Marketing Cloud Setup → Email Studio → Sender Authentication |
| Spike de bounce rate | El BouncePct del Step 4 > 3% | Auditá la frescura de la audiencia; chequear por imports recientes |
| La audiencia contenía emails NULL | El MissingEmail del Step 1 > 0 | Agregá WHERE EmailAddress IS NOT NULL a la SQL de audience-build |
| Activity en modo Update faltando PK | El audience DE tiene duplicados sin deduplicar | Verificá que el DE de destino tenga SubscriberKey como PK |
Relacionado
- FROM — por qué hacés snapshot de SDVs (
_Sent,_Bounce,_Open) antes de leerlas en producción - JOIN — patrón anti-join para la query de overlap de supresión
- Funciones agregadas — patrones de
COUNTusados en toda la página - INSERT INTO — escribir el snapshot de diagnóstico
- MC SQL gotchas — ver #6 (rotación de SDV), #9 (casteo de SubscriberKey si joineás data externa)
- Style Guide — el checklist de disciplina que evita que esta sesión de debug pase dos veces