Forum Discussion
Deliverability Issues?
Hi davidh_braze ,
Great question - this is a common point of confusion, and you’re right to flag the gap you’re seeing.
What’s going on with Deliveries vs Bounces
The Bounce metric shown in the campaign/canvas performance view represents hard bounces only. Soft bounces are not included in that number, which is why:
- Deliveries = Sends − Hard Bounces
- Soft bounces can be substantial, but they don’t reduce the Delivery count shown in the UI
This explains why your hard bounce rate looks very low, while the delivery rate still appears meaningfully lower than 100%.
How I validated this
To confirm, I tested this directly using segmentation:
- I created a segment with:
- Last received email with tag in last 2 days AND
- Hard bounced in last 2 days
- The resulting user count matched the hard bounce count shown on the campaign/canvas metrics exactly.
I then ran the same test using:
- Soft bounced more than 0 times in last 2 days AND
- Last received email with tag in last 2 days
That segment was significantly larger and did not match what’s shown in the dashboard — which aligns with soft bounces not being surfaced in campaign-level bounce metrics.
Where to find more detailed bounce data
If you’re looking for a deeper breakdown (hard vs soft bounces, domains, failure patterns, etc.), the best place is Analytics → Query Builder, using the shared Snowflake tables.
Below is an example query that pulls:
- Hard and soft bounces
- Sends and deliveries
- Opens, clicks, unsubscribes, and spam reports
- broken down by email domain over the last 30 days (you can adjust the date window as needed).
This should give you the level of visibility you’re looking for, including hard vs soft bounces and domain-level patterns.
-- Email bounces and sends per domain
WITH hard_bounces AS (
SELECT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_BOUNCE_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
soft_bounces AS (
SELECT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_SOFTBOUNCE_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
sends AS (
SELECT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_SEND_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
delivered AS (
SELECT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_DELIVERY_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
unique_opens AS (
SELECT DISTINCT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_OPEN_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
unique_clicks AS (
SELECT DISTINCT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_CLICK_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
unique_unsubscribes AS (
SELECT DISTINCT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_UNSUBSCRIBE_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
mark_as_spam AS (
SELECT DISTINCT email_address,
RIGHT(email_address, LENGTH(email_address) - charindex('@', email_address)) AS domain
FROM USERS_MESSAGES_EMAIL_MARKASSPAM_SHARED
WHERE date_trunc('day', to_timestamp_ntz(time)) >= dateadd('day', -30, date_trunc('day', CURRENT_DATE()))
),
total_bounces AS (
SELECT * FROM hard_bounces
UNION ALL
SELECT * FROM soft_bounces
),
hard_bounce_counts AS (
SELECT domain, COUNT(*) AS bounces
FROM hard_bounces
GROUP BY domain
),
soft_bounce_counts AS (
SELECT domain, COUNT(*) AS bounces
FROM soft_bounces
GROUP BY domain
),
total_bounce_counts AS (
SELECT domain, COUNT(*) AS bounces
FROM total_bounces
GROUP BY domain
),
send_counts AS (
SELECT domain, COUNT(*) AS sends
FROM sends
GROUP BY domain
),
delivered_counts AS (
SELECT domain, COUNT(*) AS delivered
FROM delivered
GROUP BY domain
),
unique_opens_counts AS (
SELECT domain, COUNT(*) AS unique_opens
FROM unique_opens
GROUP BY domain
),
unique_clicks_counts AS (
SELECT domain, COUNT(*) AS unique_clicks
FROM unique_clicks
GROUP BY domain
),
unique_unsubscribes_counts AS (
SELECT domain, COUNT(*) AS unique_unsubscribes
FROM unique_unsubscribes
GROUP BY domain
),
mark_as_spam_counts AS (
SELECT domain, COUNT(*) AS mark_as_spam
FROM mark_as_spam
GROUP BY domain
)
SELECT total_bounce_counts.domain,
total_bounce_counts.bounces AS total_bounces,
COALESCE(hard_bounce_counts.bounces, 0) AS hard_bounces,
COALESCE(soft_bounce_counts.bounces, 0) AS soft_bounces,
COALESCE(send_counts.sends, 0) AS sends,
COALESCE(delivered_counts.delivered, 0) AS delivered,
COALESCE(unique_opens_counts.unique_opens, 0) AS unique_opens,
COALESCE(unique_clicks_counts.unique_clicks, 0) AS unique_clicks,
COALESCE(unique_unsubscribes_counts.unique_unsubscribes, 0) AS unique_unsubscribes,
COALESCE(mark_as_spam_counts.mark_as_spam, 0) AS mark_as_spam
FROM total_bounce_counts
LEFT JOIN hard_bounce_counts ON total_bounce_counts.domain = hard_bounce_counts.domain
LEFT JOIN soft_bounce_counts ON total_bounce_counts.domain = soft_bounce_counts.domain
LEFT JOIN send_counts ON total_bounce_counts.domain = send_counts.domain
LEFT JOIN delivered_counts ON total_bounce_counts.domain = delivered_counts.domain
LEFT JOIN unique_opens_counts ON total_bounce_counts.domain = unique_opens_counts.domain
LEFT JOIN unique_clicks_counts ON total_bounce_counts.domain = unique_clicks_counts.domain
LEFT JOIN unique_unsubscribes_counts ON total_bounce_counts.domain = unique_unsubscribes_counts.domain
LEFT JOIN mark_as_spam_counts ON total_bounce_counts.domain = mark_as_spam_counts.domain
WHERE total_bounce_counts.domain IS NOT NULL
ORDER BY total_bounces DESC
LIMIT 100;Thanks!
Hope this helps!
Thanks so much, I will certainly explore this avenue.
Related Content
- 2 years ago
- 10 months ago
- 2 years ago