# Reports API - Message Activity

Use the message activity list to retrieve the ten activity metrics for your messages, one row per message, filtered by message, folder, recipient list, type, and date.

---

## Endpoints

| Method | URL | Description |
|---|---|---|
| GET | `https://e1.envoke.com/v2/reports/messages` | Return activity metrics per message. |

Authentication is via HTTP Basic Auth using your API ID and API Key.

---

## List Message Activity

```
GET https://e1.envoke.com/v2/reports/messages
```

Returns a JSON array of rows, one per message, most recently sent first. Each row describes the message and gives its ten [metrics](https://envoke-developers.pages.dev/v2/reports/fields/#fields) as integers. When nothing matches, the response is `[]`.

The list has no rates and no breakdowns; for those, request the message's [single report](https://envoke-developers.pages.dev/v2/reports/message-stats/).

### Which messages are included

By default the list includes:

- sent email campaigns (Message API status `Sent`), and
- automated messages (status `Nurture`).

`type=campaign` or `type=automated` narrows the list to one of the two. `include_in_progress=1` also includes campaigns that have started sending but are not finished (status `AB test in progress`, `Queuing`, or `Sending`). Their numbers change from one request to the next until the send completes, and an A/B test still in its test phase shows only the contacts sent so far.

Drafts, scheduled messages that have not started, messages of the `Transactional` status, and deleted or hidden messages (a message hidden or deleted in the app, stored with `visible` = 0) are never included. An automated message whose sequence is transactional has the `Nurture` status and is included like any automated message.

A message is listed when it matches every filter, whether or not it has any activity: a message sent to nobody, or one with nothing recorded inside the activity window, appears with every count `0`. When an activity window is given, messages launched after `activity_to` are left out, since nothing can have happened to them yet.

**A/B tests.** An A/B test is one message with one row. `has_variants` is `true`, `subject` is `null` for a subject-line test (each version's subject is in the single report), and the counts are those of all its versions together, including the contacts sent the winning version after the test. The versions are never listed as rows; their own counts are in the `variants` of the [single-message report](https://envoke-developers.pages.dev/v2/reports/message-stats/#ab-tests).

### Query Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `message_id` | integer or array | — | One message ID (`message_id=2001`) or several (`message_id[]=2001&message_id[]=2004`). |
| `group_id` | integer or array | — | One message folder ID or several. Messages filed in the folder or in any of its subfolders, at any depth, are included, as in the folder report in Envoke. |
| `tag_id` | integer | — | Only messages sent to this tag. One value. |
| `segment_id` | integer | — | Only messages sent to this segment. One value. |
| `subscription_id` | integer | — | Only messages sent to this subscription. One value. The account's mandatory subscription is not returned by the Subscription API, so messages sent to it are selected by `message_id`, `group_id` or date unless you know its ID from the app. |
| `type` | string | *(both)* | `campaign` or `automated`. |
| `include_in_progress` | integer | `0` | `1` to include campaigns still sending. |
| `sent_from` | string | — | Only messages whose `sent_time` is on or after this date or time. See [Date parameters](https://envoke-developers.pages.dev/v2/reports/fields/#date-parameters). |
| `sent_to` | string | — | Only messages whose `sent_time` is on or before this date or time. |
| `activity_from` | string | — | Count only activity on or after this date or time. |
| `activity_to` | string | — | Count only activity on or before this date or time. |
| `limit` | integer | `10` | Maximum number of rows; values above `100` return 100. |
| `skip` | integer | `0` | Number of rows to skip. |

The filters combine with AND. Any other parameter returns HTTP 400 (`Parameter 'segment' is not valid`); see [Unknown parameters](https://envoke-developers.pages.dev/v2/reports/fields/#unknown-parameters).

### Recipient list filters

`tag_id`, `segment_id`, and `subscription_id` select messages by the list recorded on the message when it was sent, not by the contacts who are on that list today. They match the same values the Message API returns in `recipient_tag`, `recipient_segment`, and `recipient_subscription` for the message:

| Filter | Matches a message whose |
|---|---|
| `subscription_id=514` | `recipient_subscription` is that subscription, with or without a tag or segment narrowing it. |
| `tag_id=512` | `recipient_tag` is that tag, alone or narrowed by a subscription. |
| `segment_id=77` | `recipient_segment` is that segment. |

A message sent to several tags at once is recorded against an internal segment that no API returns, so it matches neither `tag_id` for one of those tags nor any `segment_id`. Automated messages are usually not sent to a list and match none of the three.
<!-- VERIFY: segment_id matches the message's recipient segment (campaigns.fid); tag_id and subscription_id were confirmed on dev 2026-10-26 -->

**Names in the rows, IDs in the filters.** `recipient_subscription`, `recipient_tag`, and `recipient_segment` in the rows are the **names** of the lists recorded on the message, while the filters `tag_id`, `subscription_id`, and `segment_id` take **IDs**. The IDs and current names of tags and subscriptions come from `GET https://e1.envoke.com/v2/tags` ([Tag API](https://envoke-developers.pages.dev/v2/tags/read/)) and `GET https://e1.envoke.com/v2/subscriptions` ([Subscription API](https://envoke-developers.pages.dev/v2/subscriptions/read/)); segment IDs come from the segments in the Envoke app. A message whose list was deleted since it was sent can still show the list's name, but the deleted list is no longer returned by those APIs, so there is no ID to filter it by.

### Date window

`sent_from` and `sent_to` choose **which messages** are listed, by `sent_time`. `activity_from` and `activity_to` choose **which activity is counted**. They can be combined:

| Request | Result |
|---|---|
| no date parameter | Every message, lifetime counts. |
| `sent_from=2026-04-01&sent_to=2026-04-30` | Messages launched in April, lifetime counts. |
| `activity_from=2026-05-01&activity_to=2026-05-31` | Every matching message launched by the end of May, counting only May's activity. A message launched in April appears with `sends` `0` and its May opens and clicks. |
| `sent_from=2026-04-01&sent_to=2026-04-30&activity_from=2026-05-01&activity_to=2026-05-31` | Messages launched in April, counting only their May activity. |

For an automated message `sent_time` is the date it was set up or last activated, so `sent_from` and `sent_to` select automated messages by that date, not by when contacts received them. To report on what an automated message sent in a period, use `message_id` with `activity_from` and `activity_to`.

Inside an activity window `delivered` counts the contacts sent the message inside the window who have no bounce for it, so it is never negative and is not the window's `sends − bounces`; see [`delivered`](https://envoke-developers.pages.dev/v2/reports/fields/#delivered).

### Ordering and pagination

Rows are ordered by `sent_time`, newest first, and then by `id`, highest first, so messages launched in the same second always come back in the same order and pages never overlap. Messages with no `sent_time` come last. The order cannot be changed, and the order of `message_id[]` values in the request is not kept.

The response does not say how many rows exist in all. To read every row, request pages with `skip` until one returns fewer than `limit` rows.

### Response fields

| Field | Type | Description |
|---|---|---|
| `id` | string | Message ID; the Message API `id`. |
| `name` | string | Internal name. |
| `subject` | string or null | Stored subject line; `null` for a subject-line A/B test, and for a message with no stored subject (then `has_variants` is `false`). Can be the placeholder `{@ab_test}` (see [Messages](https://envoke-developers.pages.dev/v2/reports/fields/#messages)). |
| `type` | string | `campaign` or `automated`. |
| `sent_time` | string or null | Launch time, `YYYY-MM-DD HH:MM:SS`, account time zone. |
| `recipient_subscription` | string | The name of the subscription the message was sent to, or `""`. The same value the Message API returns. Filter by the subscription's ID with `subscription_id`. |
| `recipient_tag` | string | The name of the tag the message was sent to, or `""`. `""` when the message was sent to a subscription alone. Filter by the tag's ID with `tag_id`. |
| `recipient_segment` | string | The name of the segment the message was sent to, or `""`. Filter by the segment's ID with `segment_id`. |
| `has_variants` | boolean | `true` for an A/B test. |
| `sends` … `skips` | integer | The ten [metrics](https://envoke-developers.pages.dev/v2/reports/fields/#fields), in glossary order. |

See [Messages](https://envoke-developers.pages.dev/v2/reports/fields/#messages) in the fields reference for `subject`, `type`, and `sent_time` in detail.

### Response

```json
[
  {
    "id": "2004",
    "name": "Pirates Opening Night",
    "subject": null,
    "type": "campaign",
    "sent_time": "2026-05-14 09:30:00",
    "recipient_subscription": "Season News",
    "recipient_tag": "",
    "recipient_segment": "",
    "has_variants": true,
    "sends": 386,
    "delivered": 379,
    "bounces": 7,
    "hard_bounces": 4,
    "soft_bounces": 3,
    "opens": 201,
    "clicks": 64,
    "unsubscribes": 2,
    "spam_complaints": 0,
    "skips": 3
  },
  {
    "id": "2001",
    "name": "Spring Season Announcement",
    "subject": "Our spring season is here, {fname}",
    "type": "campaign",
    "sent_time": "2026-04-15 10:00:00",
    "recipient_subscription": "Season News",
    "recipient_tag": "",
    "recipient_segment": "",
    "has_variants": false,
    "sends": 412,
    "delivered": 403,
    "bounces": 9,
    "hard_bounces": 5,
    "soft_bounces": 4,
    "opens": 188,
    "clicks": 57,
    "unsubscribes": 4,
    "spam_complaints": 1,
    "skips": 6
  },
  {
    "id": "2002",
    "name": "Auditions Open",
    "subject": "Auditions for The Pirates of Penzance",
    "type": "campaign",
    "sent_time": "2026-03-10 18:00:00",
    "recipient_subscription": "",
    "recipient_tag": "Cast Call",
    "recipient_segment": "",
    "has_variants": false,
    "sends": 64,
    "delivered": 62,
    "bounces": 2,
    "hard_bounces": 1,
    "soft_bounces": 1,
    "opens": 47,
    "clicks": 21,
    "unsubscribes": 0,
    "spam_complaints": 0,
    "skips": 1
  }
]
```

---

## Errors

Invalid parameters return HTTP 400 with the validation envelope (see [Error Responses](https://envoke-developers.pages.dev/v2/reports/fields/#error-responses)). Besides the shared date, `limit`, `skip`, and unknown-parameter errors, the list returns:

| Error | Cause |
|---|---|
| `Invalid 'message_id': 'abc'` | A `message_id` value that is not a whole number. The same form for `group_id`, `tag_id`, `segment_id`, and `subscription_id`. |
| `Parameter 'tag_id' accepts one value` | A single-value parameter sent as an array (`tag_id[]=512`, `limit[]=5`, `type[]=campaign`): every parameter except `message_id` and `group_id`. |
| `Invalid 'type': 'nurture' (possible values: 'campaign', 'automated')` | `type` is not `campaign` or `automated`. |
| `Invalid 'include_in_progress': 'yes' (possible values: '0', '1')` | `include_in_progress` is not `0` or `1`. |

A filter value that is a whole number but matches nothing (an unknown folder, a tag in another account) is not an error: the response is `[]`.

---

## Examples

### The ten most recent messages

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages"
```

### Second page of 25

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?limit=25&skip=25"
```

### Lifetime counts for two messages

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?message_id%5B%5D=2001&message_id%5B%5D=2004"
```

The rows come back newest first, not in the order the IDs were given.

### Messages launched in April

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?sent_from=2026-04-01&sent_to=2026-04-30&limit=100"
```

### May activity for one message

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?message_id=2001&activity_from=2026-05-01&activity_to=2026-05-31"
```

Response (2001 was sent in April; only its May activity is counted, so `sends` is `0` and so is `delivered`, which counts only contacts sent the message in May):

```json
[
  {
    "id": "2001",
    "name": "Spring Season Announcement",
    "subject": "Our spring season is here, {fname}",
    "type": "campaign",
    "sent_time": "2026-04-15 10:00:00",
    "recipient_subscription": "Season News",
    "recipient_tag": "",
    "recipient_segment": "",
    "has_variants": false,
    "sends": 0,
    "delivered": 0,
    "bounces": 0,
    "hard_bounces": 0,
    "soft_bounces": 0,
    "opens": 12,
    "clicks": 3,
    "unsubscribes": 1,
    "spam_complaints": 0,
    "skips": 0
  }
]
```

### Automated messages only

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?type=automated"
```

Response:

```json
[
  {
    "id": "2006",
    "name": "Welcome to Maple Ridge",
    "subject": "Welcome to Maple Ridge Community Theatre, {fname}",
    "type": "automated",
    "sent_time": "2025-09-02 08:00:00",
    "recipient_subscription": "",
    "recipient_tag": "",
    "recipient_segment": "",
    "has_variants": false,
    "sends": 1284,
    "delivered": 1261,
    "bounces": 23,
    "hard_bounces": 15,
    "soft_bounces": 8,
    "opens": 702,
    "clicks": 233,
    "unsubscribes": 11,
    "spam_complaints": 2,
    "skips": 40
  }
]
```

### Messages in one folder

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?group_id=31"
```

The rows include the messages filed in the folder's subfolders.

### Messages sent to one tag

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?tag_id=512"
```

### Messages launched in December 2021

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?sent_from=2021-12-01&sent_to=2021-12-31"
```

Response (every metric is available whatever the message's age; only the single report's `email_clients` is `null` for messages sent before June 2022):

```json
[
  {
    "id": "2003",
    "name": "Holiday Gala Invitation",
    "subject": "You're invited to the Holiday Gala",
    "type": "campaign",
    "sent_time": "2021-12-01 10:00:00",
    "recipient_subscription": "Season News",
    "recipient_tag": "",
    "recipient_segment": "",
    "has_variants": false,
    "sends": 355,
    "delivered": 349,
    "bounces": 6,
    "hard_bounces": 4,
    "soft_bounces": 2,
    "opens": 171,
    "clicks": 38,
    "unsubscribes": 3,
    "spam_complaints": 0,
    "skips": 12
  }
]
```

### Including campaigns still sending

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/messages?type=campaign&include_in_progress=1"
```

---

*Note: The curl examples above are formatted for readability. All requests must use properly encoded URLs.*

*Full reference, OpenAPI description, and Postman collection: [developers.envoke.com](https://developers.envoke.com).*
