---
title: "Broadcast statistics"
description: "How the broadcast performed: copies sent, delivered, bounced, reported as spam and failed, and how many people opened, clicked and unsubscribed, as totals and as a series cut to `grain`."
url: "https://nextmail.sh/docs/api/broadcasts/stats"
area: "API"
category: "Mailbox"
---

# Broadcast statistics

How the broadcast performed: copies sent, delivered, bounced, reported as spam and failed, and how many people opened, clicked and unsubscribed, as totals and as a series cut to `grain`.

`GET /broadcasts/{id}/stats`

## GET /broadcasts/{id}/stats

How the broadcast performed: copies sent, delivered, bounced, reported as spam and failed, and how many people opened, clicked and unsubscribed, as totals and as a series cut to `grain`.

## Parameters

- `id` (string, required): In the path. A `brd_` id from `POST /broadcasts` or `GET /broadcasts`.
- `grain` (string): Bucket width of the series: `minute`, `hour` or `day`. Defaults to `hour`.
- `offsetMinutes` (integer): Minutes east of UTC to cut the buckets in, from -840 to 840. Defaults to 0. Pass `-new Date().getTimezoneOffset()` for the local zone.
- `days` (integer): Also reads a window this many days long, from 1 to 1095. It starts at the beginning of its first `grain` bucket and ends now. Leave it and `minutes` out for no window.
- `minutes` (integer): The window in minutes, from 1 to 1576800, which wins over `days` when both are sent.

## Example

Needs `emails:read`.

**curl**

```
curl "$OE/broadcasts/brd_5a8c1e3f7b2d94a06c8e1f3b/stats?grain=day&days=7" -H "$AUTH"
```

**Response**

```
{
  "object": "broadcast_stats",
  "broadcastId": "brd_5a8c1e3f7b2d94a06c8e1f3b",
  "grain": "day",
  "totals": {
    "recipients": 412,
    "pending": 0,
    "sent": 410,
    "delivered": 404,
    "bounced": 4,
    "complained": 1,
    "failed": 2,
    "opened": 187,
    "clicked": 52,
    "unsubscribed": 3,
    "opens": 296,
    "clicks": 71
  },
  "window": {
    "since": "2026-09-18T00:00:00.000Z",
    "delivered": 404,
    "bounced": 4,
    "complained": 1,
    "opened": 187,
    "clicked": 52,
    "unsubscribed": 3
  },
  "series": [
    { "bucket": "2026-09-23", "delivered": 404, "opened": 161, "clicked": 45, "unsubscribed": 3 },
    { "bucket": "2026-09-24", "delivered": 0, "opened": 26, "clicked": 7, "unsubscribed": 0 }
  ],
  "links": [
    { "url": "https://acme.com/changelog", "label": "Read the changelog", "clicks": 48, "people": 39 },
    { "url": "https://acme.com/pricing", "label": null, "clicks": 17, "people": 15 }
  ],
  "clients": [
    { "client": "Apple Mail", "people": 92, "opens": 141 },
    { "client": "Gmail", "people": 71, "opens": 109 },
    { "client": null, "people": 24, "opens": 46 }
  ],
  "countries": [
    { "country": "US", "people": 88 },
    { "country": "DE", "people": 41 }
  ],
  "abTest": {
    "metric": "opens",
    "winner": "b",
    "variants": [
      { "id": "a", "subject": null, "sent": 41, "opened": 15, "clicked": 4 },
      { "id": "b", "subject": "Five things we shipped in September", "sent": 41, "opened": 21, "clicked": 6 }
    ]
  }
}
```

> `opened`, `clicked` and `unsubscribed` count people, while `opens` and `clicks` count events. `pending` counts the copies still queued, scheduled or sending, and `failed` the copies that failed or were cancelled.

> `series` is sparse and oldest first: one bucket per `grain` in which something happened. It counts each person once, at the first time it happened to them, so it adds up to the totals.

> `bucket` is `YYYY-MM-DD`, `YYYY-MM-DDTHH` or `YYYY-MM-DDTHH:MM`, in the offset asked for.

> `window` counts the copies delivered, bounced, reported as spam, opened, clicked and unsubscribed inside the window, and `series` then keeps only its buckets. `totals` always covers the whole broadcast. Without `days` or `minutes`, `window` is null.

> `links` lists up to 10 links, most clicked first, each with the `people` who clicked it and its `clicks`. `clients` lists up to 8 email apps the copies were opened in and `countries` up to 10 countries they were opened from, each counting people. Opens a mail provider makes by itself, such as a privacy proxy fetching the images, are left out.

> `abTest` is null unless the broadcast runs an A/B test. Then it holds the `metric`, the `winner` once one is picked, and for each version its `id`, its `subject` (null when it keeps the broadcast's own), the copies `sent` and the people who `opened` and `clicked`.

## Refusals

| Status | Code | When |
| --- | --- | --- |
| 400 | `invalid_cursor` | On the recipients list, a `cursor` that list did not hand out. |
| 403 | `insufficient_scope` | The key does not hold `emails:read`. |
| 404 | `broadcast_not_found` | The id names no broadcast in this workspace, or the key is limited to particular addresses or domains and the broadcast was sent from one it does not hold. |
| 404 | `recipient_not_found` | On `GET /broadcasts/{id}/recipients/{emailId}`, an `emailId` that is not a copy of this broadcast. |

## Reference

- [`GET /broadcasts/{id}/stats`](https://nextmail.sh/docs/api/reference/broadcasts#get-broadcasts-id-stats): full reference
