Read campaign stats

GET /api/v1/campaigns/{campaignId}/stats How the campaign performed — the same figures the Stats screen shows.
FieldDescription
sentEmails actually dispatched
deliveredsent minus bounced
bouncedSends rejected by the receiving side
uniqueOpens / openRateContacts who opened at least once, and their share of delivered
uniqueClicks / clickRateContacts who clicked at least once, and their share of delivered
unsubscribed / unsubscribeRateUnsubscribes attributed to this campaign, and their share of delivered
clicksByLinkPer URL: {url, uniqueClicks, totalClicks}
clicksOverTimePer day: {date, clicks}, date as YYYY-MM-DD
opensOverTimePer day: {date, opens}, date as YYYY-MM-DD
clicksByHour / opensByHourPer hour since sending: {hour, clicks} / {hour, opens}, hour from 0 to 47
The four series come in matching pairs, so clicks and opens can be compared on the same axis. The daily pair is keyed by calendar date and lists only the days that saw activity; the hourly pair is keyed by whole hours elapsed since sentAt and always carries all 48 slots, quiet hours included. Opens count first opens, one per recipient at most. Rates are shares of delivered, not of sent — an address that bounced never had the chance to open. While nothing is delivered yet the denominator is zero and every rate reads 0, never NaN: poll progress on the campaign itself to know whether the send is over before reading anything into them. A campaign of another organization answers 404, like an id that does not exist.

Authorization

Role in the organizationThis endpoint
OWNERAllowed
ADMINAllowed
MEMBERAllowed
A key carries the role its creator holds in this organization — never a role on AgentsMail itself. See API keys.

Example

bash
curl -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID/stats"

Response

  • 200 OK — the call succeeded.
  • 401 Unauthorized — missing or invalid key, or its owner left the organization.
  • 404 Not Found — no such resource, or it belongs to another organization. The API never confirms that an id exists to a caller who has no right to it.
  • 429 Too Many Requests — over 120 requests in a minute for this key.

Response fields

FieldTypeDescription
successbooleanIndicates if the operation was successful
data.sentnumberEmails handed to the provider
data.deliverednumberAccepted by the receiving server
data.bouncednumberPermanently rejected — the address goes to the suppression list
data.uniqueOpensnumberRecipients who loaded the tracking pixel at least once
data.openRatenumberComputed on delivered, not on sent
data.uniqueClicksnumberRecipients who followed at least one tracked link
data.clickRatenumberSame basis
data.unsubscribednumberRecipients who left through this campaign
data.unsubscribeRatenumberSame basis
data.clicksByLinkarrayOne entry per tracked URL
data.clicksOverTimearrayClicks bucketed by time

Example response

json
{
  "success": true,
  "data": {
    "sent": 12,
    "delivered": 12,
    "bounced": 12,
    "uniqueOpens": 12,
    "openRate": 12,
    "uniqueClicks": 12,
    "clickRate": 12,
    "unsubscribed": 12,
    "unsubscribeRate": 12,
    "clicksByLink": [
      {
        "url": "https://example.com",
        "clicks": 42
      }
    ],
    "clicksOverTime": [
      {
        "at": "2026-08-23T11:00:00.000Z",
        "clicks": 12
      }
    ]
  }
}