Mailchimp API: Campaign Report Deliverability and Bounce Rate
The Mailchimp Marketing API does not return a bounce rate for your campaign. GET /3.0/reports/{campaign_id} gives you raw counts, and you calculate the rate yourself: (bounces.hard_bounces + bounces.soft_bounces + bounces.syntax_errors) / emails_sent. The one field in that payload literally named bounce_rate lives inside industry_stats, and it is the benchmark for your industry, not your campaign. Reading it as your own number is the single most common mistake in Mailchimp reporting integrations, and it is silent: the value looks plausible, so nobody checks.
Deliverability in the same payload works the same way. Successful deliveries are emails_sent minus all three bounce types. There is no delivered field on the campaign report object, though there is one per domain on GET /reports/{campaign_id}/domain-performance, which is the endpoint you actually want when you are diagnosing a deliverability problem rather than counting it.
The request
Authenticate with HTTP Basic (any username, API key as the password) or an OAuth bearer token. The base URL carries your data center, which is the suffix on your API key after the dash, for example us19.
curl -u "anystring:$MC_API_KEY" \
"https://us19.api.mailchimp.com/3.0/reports/42694e9e57?fields=id,campaign_title,emails_sent,send_time,bounces,abuse_reports,unsubscribed,opens,clicks"
The fields parameter is not cosmetic. The full report object carries timeseries data, ecommerce blocks, A/B split results and share URLs. Trimming it cuts response size by an order of magnitude on high volume campaigns, which matters when you are pulling hundreds of reports.
An illustrative trimmed response:
{
"id": "42694e9e57",
"campaign_title": "March newsletter",
"emails_sent": 48210,
"abuse_reports": 6,
"unsubscribed": 91,
"bounces": { "hard_bounces": 412, "soft_bounces": 233, "syntax_errors": 5 },
"opens": { "opens_total": 30112, "unique_opens": 18904, "open_rate": 0.3975 },
"clicks": { "unique_clicks": 2870, "click_rate": 0.0603 },
"industry_stats": { "type": "Software and Web App", "bounce_rate": 0.0089 }
}
The denominator trap
Mailchimp does not use one denominator. Bounce rate is conventionally taken against emails sent, because a bounce is a failure to deliver and the delivered count already excludes it. Open rate and click rate are computed against successful deliveries. If you rebuild every rate against emails_sent for consistency, your numbers will not match the Mailchimp UI and someone will spend an afternoon on the discrepancy.
| Metric | Formula from the report payload | Example |
|---|---|---|
| Total bounces | hard + soft + syntax_errors | 650 |
| Bounce rate | total bounces / emails_sent | 1.35% |
| Hard bounce rate | hard_bounces / emails_sent | 0.85% |
| Successful deliveries | emails_sent minus total bounces | 47,560 |
| Delivery rate | deliveries / emails_sent | 98.65% |
| Open rate | unique_opens / deliveries | 39.75% |
| Complaint rate | abuse_reports / deliveries | 0.013% |
| Unsubscribe rate | unsubscribed / deliveries | 0.19% |
One more unit trap: open_rate and click_rate come back as fractions like 0.3975, while several *_pct fields on other report endpoints read as values you will want to verify before multiplying by 100 in your own code. Recompute from raw counts wherever the counts are available and the ambiguity disappears.
Hard, soft and syntax errors are three different problems
A hard bounce is a permanent rejection: the mailbox does not exist, the domain does not resolve, the server refuses the address outright. Mailchimp cleans these contacts immediately, moving them to cleaned status so they are excluded from future sends. That is why hard bounce rate on a healthy, regularly mailed list should be very low. A spike means new addresses entered the list, which points at an import, a form without validation, or a CRM sync.
A soft bounce is temporary: full mailbox, message too large, server unavailable, greylisting, or a reputation-based deferral. Mailchimp retries, and an address that soft bounces repeatedly across sends eventually gets cleaned as well. A soft bounce spike concentrated on one receiving domain is usually a reputation signal rather than a list quality signal, and that distinction changes what you do next.
A syntax error means the address failed format validation before it was ever sent. Small numbers are noise. Anything meaningful indicates a data path that is writing garbage into your list, and the fix belongs upstream, not in Mailchimp.
To get the actual addresses, use GET /reports/{campaign_id}/sent-to, where each recipient record carries a status distinguishing sent from hard and soft bounces, or GET /reports/{campaign_id}/email-activity, which returns a per subscriber event stream including bounce events with their type. Email activity is one record per recipient and gets very large. Use the since parameter and paginate with count (maximum 1000) and offset. Cross referencing the bounced addresses with GET /lists/{list_id}/members?status=cleaned tells you how much of your list has already been burned.
Domain performance is where deliverability actually shows
A 1.35% blended bounce rate tells you nothing about cause. GET /reports/{campaign_id}/domain-performance breaks the same send down by recipient domain, returning emails_sent, bounces, delivered, opens, clicks and unsubs for each. Same campaign as above:
| Domain | Sent | Bounces | Bounce rate | Open rate |
|---|---|---|---|---|
| gmail.com | 21,400 | 96 | 0.45% | 42.1% |
| yahoo.com | 6,100 | 71 | 1.16% | 33.4% |
| hotmail.com / outlook.com | 5,300 | 62 | 1.17% | 28.9% |
| Other | 15,410 | 421 | 2.73% | 41.0% |
Now the number is actionable. The consumer mailboxes are fine. Nearly two thirds of the bounces sit in "other", which on a B2B list means corporate mail servers, and that pattern almost always traces to departed employees at customer accounts rather than to your sending reputation. The opposite shape, where Gmail bounces climb while everything else holds steady, is a reputation problem and calls for authentication checks and Google Postmaster Tools, not list hygiene.
Pulling many campaigns without hitting the rate limit
GET /reports returns all campaign reports and accepts count up to 1000, offset, since_send_time, before_send_time and type (regular, plaintext, absplit, rss, variate, automation). Combine it with fields scoped to the array, for example fields=total_items,reports.id,reports.send_time,reports.emails_sent,reports.bounces, and one request gives you a full bounce trend.
Mailchimp allows 10 simultaneous connections per API key and answers excess requests with a 429. Parallelising 50 report fetches will fail. Run a small worker pool with exponential backoff, or use the batch endpoint: POST /3.0/batches accepts a list of operations, returns a batch ID immediately, and you poll GET /3.0/batches/{batch_id} until the status is finished, then download the results archive.
What bounce rate does not tell you
Bounce rate measures rejection. It says nothing about whether the accepted mail reached an inbox or a spam folder, and those are the messages that quietly kill campaign performance. Three gaps are worth knowing about.
Complaint rate is the metric large mailbox providers act on. Google and Yahoo's bulk sender requirements ask senders to stay below a 0.3% spam complaint rate, with a lower figure as the practical target, alongside SPF and DKIM authentication, a DMARC record, and one-click list unsubscribe support. Mailchimp's abuse_reports count only captures complaints from providers that operate a feedback loop, and Gmail does not offer per message complaint data, so abuse_reports systematically understates your true Gmail complaint rate. Google Postmaster Tools is the only place to see it.
Open rate stopped being a clean deliverability proxy once Apple Mail Privacy Protection began prefetching tracking pixels. Inflated and unreliable opens are exactly why teams lean harder on bounce rate, but bounce rate cannot see spam foldering at all.
| Signal | What it proves | Where it lives |
|---|---|---|
| Bounce rate | The receiving server refused the message | /reports/{id} |
| Per domain bounces | Whether the cause is list quality or reputation | /reports/{id}/domain-performance |
| Complaint rate | Recipients marked you as spam | /reports/{id} plus Postmaster Tools |
| Inbox placement | Accepted mail reached the inbox | Neither, requires seed testing |
A triage sequence that works
- Pull the report, compute total bounces over
emails_sent, and split hard from soft. - If hard bounces dominate, check what entered the list since the last send. Import, form, or sync.
- If soft bounces dominate, pull domain performance and look for concentration in one receiver.
- Confirm SPF, DKIM and DMARC pass on the sending domain before touching anything else.
- Pull the bounced addresses from
sent-toand reconcile them against your CRM, so the same records do not resurface on the next sync.
When the answer is not in Mailchimp
Step five is usually where reporting stops and manual work starts. The bounce list is in Mailchimp, the accounts those addresses belong to are in your CRM, the import that introduced them was discussed in Slack, and suppressing them properly means writing back to a system that is not the one holding the report. Skopx connects to Mailchimp alongside your CRM, your warehouse and your messaging tools, so you can ask for the bounced addresses from the last send matched to their account owners in one question, and build a small internal app that shows the breakdown and puts a suppress button next to each record for a person to click.
Skopx Team
The Skopx engineering and product team