Reports
Reports are read live from your own tables. Opens and clicks count people, not machines: SES’s bot flag, Apple’s mail privacy prefetching and link scanners are filtered out and counted separately.
A campaign to report on
Section titled “A campaign to report on”If you don’t have one yet, send a small campaign:
curl -X POST "$BASE/templates" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \ -d '{"name":"Report demo","version":{"subject":"Hello","html":"<p>Hello, <a href=\"https://example.com/pricing\">see pricing</a></p><p><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a> {{ footer_address }}</p>","fromName":"Example Co","fromEmail":"news@example.com"}}'
curl -X POST "$BASE/campaigns" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \ -d '{"name":"Report demo","templateId":"<tp_...>","templateVersion":1,"segment":{},"stagePlan":{"type":"preset","name":"single"}}'
curl -X POST "$BASE/campaigns/<cm_...>/schedule" -H "Authorization: Bearer $KEY" -H 'Idempotency-Key: report-demo-schedule'Read the report
Section titled “Read the report”curl "$BASE/campaigns/<cm_...>/report" -H "Authorization: Bearer $KEY"Check it worked: stats.audience is the number of contacts the campaign is for, and sent and delivered grow as it goes out.
| Counter | Counts |
|---|---|
audience |
Contacts the campaign is for, fixed when it starts |
enqueued, sent |
Emails queued, and accepted by SES |
skipped |
Contacts who unsubscribed or were suppressed after the campaign started, and, if you cancel a campaign, everyone it hadn’t sent to yet |
failed |
Emails SES refused, with the reason on the send record |
unknown |
Emails whose outcome SES never confirmed, after 30 minutes |
delivered, delayed |
Delivered to the recipient’s mail server; delayed on the way |
uniqueOpens, machineOpens |
People who opened; opens by machines, such as Apple’s prefetching |
uniqueClicks, clicks, botClicks |
People who clicked; every human click; clicks by link scanners |
hardBounces, softBounces |
Permanent and temporary bounces |
undeterminedBounces, suppressedBounces |
Bounces SES couldn’t classify, and addresses on the SES suppression list. Neither counts toward the bounce rate. |
complaints, unsubscribes |
Spam complaints, and unsubscribes from this campaign’s emails |
rates gives delivery, open, click, clickToOpen, hardBounce, complaint and unsubscribe, each from human events only.
By stage and by link
Section titled “By stage and by link”curl "$BASE/campaigns/<cm_...>/report?by=stage" -H "Authorization: Bearer $KEY"curl "$BASE/campaigns/<cm_...>/report?by=link" -H "Authorization: Bearer $KEY"byStage gives the counters for each stage of the campaign’s plan, with each stage’s audience. It’s empty until sending starts, since that’s when the audience is split into stages; from then on it lists every stage, including ones that haven’t started yet. Stages are numbered from 0 here, while the console numbers them from 1. It shows where bounces or complaints came from if a campaign paused. byLink gives clicks and uniqueClicks for each link. We tag every link in your template with its position, the first link being 0, and click events carry only that position, never the URL, since a URL can hold merge values. The report names each link by the href written at that position in the campaign’s template version, Liquid included, such as https://example.com/pricing.
Every recipient
Section titled “Every recipient”curl "$BASE/campaigns/<cm_...>/recipients?state=sent&limit=100" -H "Authorization: Bearer $KEY"Each send record has the contact ID, its stage and state (queued, claimed, sent, failed, skipped or unknown), the SES messageId, and when it was sent, delivered, first opened and first clicked, plus any bounce, complaint, unsubscribe or failure reason. Filter by state and stage (numbered from 0, so the console’s stage 1 is stage=0), and page with cursor (see Pagination).
Check it worked: each record’s sentAt is set, and deliveredAt follows within seconds for delivered emails.
To get every record as a file, export it: see Erasure and export. For every raw SES event, query the archive with Athena.