Use the message contacts report to list the contacts behind a number in a message's report: who was sent the message, who opened it, who clicked a particular link, who bounced, who did not open it. It is the API form of clicking a count in the Sent message report in Envoke.
Endpoints
Method | URL | Description |
GET |
| Return the contacts with one event for this message. |
Authentication is via HTTP Basic Auth using your API ID and API Key.
List a Message's Contacts
GET https://e1.envoke.com/v2/reports/messages/{ID}/contacts?event={event}Returns a JSON array with one row per contact: the contact's id, email, and remote_id, when the event happened, and a count. Replace {ID} with the Envoke message ID and {event} with one of the events below. Rows are slim on purpose: to read anything else about a contact, request it from the Contact API by id.
The report answers for the same messages as the single-message report: sent campaigns, automated messages, and campaigns that have started sending. A message that does not exist, belongs to another account, is deleted, or has not started sending returns [] with HTTP 200, as does an event that no contact has.
Each event lists exactly the contacts that one of the ten metrics counts, so the number of rows for event=opened is the opens the single-message report gives for the message with the same activity window, and so on for every event; the exceptions are stated under Events and Contacts no longer in the account.
Accounts restricted from contact-level email activity. On an account restricted from seeing contacts associated with email activity, this endpoint returns HTTP 403 for every event, before any parameter is checked, with the same body as the contact activity report. The message reports are not affected.
Query Parameters
Parameter | Type | Default | Description |
| string | — | Required. Which contacts to list: |
| integer | — | With |
| string or array | — | With |
| string | — | Only activity on or after this date or time. See Date parameters and Activity window. |
| string | — | Only activity on or before this date or time. |
| string | (all) | Comma-separated list of the row fields to return, from |
| integer |
| Maximum number of rows. Values above |
| integer |
| Number of rows to skip. |
Any other parameter, including sent_from, sent_to, sort, filter, and result_type, returns HTTP 400 (Parameter 'sent_from' is not valid); see Unknown parameters.
Events
Every event lists distinct contacts: a contact appears once, however many records they have for the event.
| Contacts listed | Rows equal the single-message report's |
|
|
| Contacts the message was sent to (type |
| The contact's send. |
|
| Contacts sent the message who have no bounce for it (see |
| The contact's send. |
|
| Contacts with any bounce (type |
| The contact's first bounce (of the reasons asked for). |
|
| Contacts who opened the message (type |
| The contact's first open. | The number of email clients the contact opened the message in. |
| Contacts who clicked a tracked link (type |
| The contact's first click (on that link, with | The number of different links the contact clicked; |
| Contacts who revoked consent or used one-click unsubscribe from the message (type |
| The earlier of the two records. |
|
| Contacts who reported the message as spam (type |
| The earlier of the two records. |
|
| Contacts selected for the message but not sent it (type |
| The contact's first skip record. |
|
| Contacts in |
|
|
|
| Contacts in |
|
|
|
sent and delivered. event_time for both is the time the message was handed off to the contact, the only time Envoke records before an open; there is no separate delivery time yet. A contact counts as delivered when it was sent the message and has no bounce record of any kind, including a temporary deferral that was later delivered, exactly as the delivered metric says. When Envoke switches delivered to its confirmed-delivery records, this event switches with it and event_time becomes the delivery time. Lifetime, the delivered rows equal totals.delivered when every contact with a bounce record for the message was also sent it. Some messages sent before 2015 have bounce records for contacts with no send record for the message; totals.delivered (sends − bounces) subtracts those contacts too, while this event lists every sent contact without a bounce, so on such a message the rows can be more than totals.delivered.
bounced and bounce_type. With no bounce_type, every bounce reason counts, deferrals included. bounce_type takes the keys of the single-message report's bounces block, so bounce_type=hard_bounce lists the contacts behind its hard_bounce entry. Several values list contacts with any of the reasons, each once: bounce_type[]=hard_bounce&bounce_type[]=invalid_address lists exactly the contacts counted in hard_bounces. other includes the retired sub-type 13, so soft_bounce, deferred, rejected, and other together can list more contacts than soft_bounces on old messages.
opened and count. count is the number of email clients the contact's opens came from, as in the contact activity report's opens: one open record exists per contact per email client, so a contact who opened ten times in one client has count 1. An open by Apple Mail Privacy Protection is one of those email clients, so a contact with only a machine open is listed with count 1, and a contact who also opened in another client has count 2. Summed over all rows, count gives the Sent message report's Opens row for the message, less any contacts deleted since (see Contacts no longer in the account).
clicked and link_id. count is the number of different links the contact clicked, including the view-online and preferences links, not the number of clicks. link_id must be a tracked link of this message; the IDs are the id values of the single-message report's links and of the contact activity report's links. A tracked link nobody has clicked is accepted and gives [] (the links block lists only links with clicks, so its ID is not shown there). Identify a link by its id, not its url: the url in links is the link as written in the message, with merge tags unreplaced, so it can carry a merge tag such as {@customfield-url_test}. Poll answers are not links for this purpose and are never accepted as link_id. A click can also be recorded against a link that is not one of this message's tracked links (a link belonging to another message, for example after content was reused); such a click is in clicked and its count, and in the contact activity report with url "", but its ID is not accepted as link_id.
Anonymous polls. Envoke never shows which contacts answered an anonymous poll. When a message contains an anonymous poll, clicks on any poll answer in that message are left out of clicked and out of its count, as the Sent message report's own drill-down does: a contact who also clicked a regular link is still listed, and a contact whose only clicks were poll answers is not. For such a message the clicked rows can therefore be fewer than totals.clicks, which counts every click. Those contacts stay in not_clicked, so the two lists together never single out the people who answered. A message without an anonymous poll lists poll-answer clicks like any other click. The poll results themselves are in the single-message report's polls.
unsubscribed and spam_reported. These are the contacts of the unsubscribes and spam_complaints metrics. There is no parameter to separate revoked consent from one-click unsubscribe, or Envoke's report-abuse link from a mailbox provider's complaint, and the other preference changes of the single-message report (subscription, language, express consent) have no event here.
skipped. Contacts in the message's recipient list who were not sent it, for any reason (no consent, invalid or unverified address, API error, invalid parameters). Skips have been recorded since 17 June 2014, so the list is empty for older messages. There is no parameter to choose a skip reason. A contact skipped once and sent the message later, which can happen with an automated message, is in both skipped and sent.
Did not open and did not click
not_opened and not_clicked are computed as a difference: the contacts in sent, minus the contacts in opened or clicked. This is how the "didn't open" and "didn't click" campaign activity conditions of Envoke's segments work, so the two always agree. A contact whose message bounced is therefore listed in not_opened and not_clicked; to leave bounced addresses out (for example, before a re-send), remove the contacts of event=bounced yourself.
Lifetime,
not_openedplus the openers who were sent the message equalssent, and the same for clicks. Envoke can record an open or a click for a contact with no send record for the message (a contact who was skipped, or who received the message another way, and then opened it); such a contact is inopenedorclickedand in neithersentnornot_opened, sonot_openedplusopenedcan exceedsentby those contacts. A contact who was deferred (a bounce) and then delivered and opened is inbouncedandopened, and not innot_opened.Opens recorded by mail privacy proxies count as opens, so a contact whose only open was a machine open is in
opened, not innot_opened.not_clickedwithlink_idis the sent contacts who did not click that link, whether or not they clicked another.Rows have
event_timenullandcount1.
Activity window
With activity_from or activity_to, each event lists the contacts with a matching record inside the window, the same contacts the metric counts for that window:
sent: contacts sent the message inside the window.delivered: contacts sent the message inside the window who have no bounce for it at any time. A contact sent the message on the last day of the window whose bounce arrived the next day is in neitherdeliverednorbouncedfor that window.opened,clicked,bounced,unsubscribed,spam_reported,skipped: contacts with that record inside the window, whenever they were sent the message. A contact sent the message before the window who opened it inside the window is inopenedbut not insentordelivered.not_openedandnot_clicked: the window'ssentcontacts with no open (or click) record inside the window, bounced contacts included. A contact sent inside the window who opened after it is innot_openedfor that window.
event_time is then the contact's first record of the event inside the window, and count counts only email clients or links with a record inside the window.
fields and the 10,000 limit
fields chooses which fields each row carries: fields=id returns rows of { "id": "1004" } only, fields=id,email rows of id and email. The fields always come back in the order of Response fields, whatever their order in fields.
As on the Contact API, giving fields lifts the limit: with fields and no limit, up to 10,000 rows are returned, and a limit above 100 is honoured up to 10,000. Larger values return 10,000. This applies to every event, including not_opened and not_clicked. limit=0, an empty limit and a limit of several zeros (limit=00) all give 10,000 with fields. To read a list longer than 10,000, page with skip. Without fields, the cap is 100 as on every other Reports API list.
Ordering and pagination
Rows are ordered by event_time, newest first, then by contact id, highest first, so contacts with the same time always come back in the same order. not_opened and not_clicked rows, which have no time, are ordered by id, highest first. The order cannot be changed and is the same whether or not event_time is among the fields.
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. limit=1 returns an array of one row. For a message that is still sending, rows are added between requests, so pages read a few minutes apart can overlap or miss a contact.
Contacts no longer in the account
Envoke keeps a message's activity records when a contact is deleted, and the metrics still count them; this report can only list contacts that still exist. So the rows for an event can be fewer than the metric by the number of contacts deleted since, and a deleted contact never appears with an empty email. The Sent message report's drill-down lists only existing contacts in the same way.
email and remote_id are the contact's values now, not the address the message was sent to: a contact who changed their email address since is listed with the new one.
Relation to the Sent message report
Every count in the Sent message report in Envoke links to a list of contacts (https://e1.envoke.com/search_contacts.php?camp={id}&type=…&sub_type=…). For the same message and dates, each event lists the same contacts as that page:
| Drill-down on the Sent message report |
| Sent |
| Delivered (lifetime; see the note below for a window) |
| Bounced; with |
| Opens |
| Clicks; with |
| Revoked consent and One-click unsubscribe together |
| Envoke spam reported and External spam reported together |
| Skipped |
| No drill-down on the report; the contacts follow the "didn't open" and "didn't click" campaign activity conditions of segments (sent minus the event) |
The report's own numbers count some events per email client or per bounce reason (see How the counts work); the list behind each number has one entry per contact, as this report does. Two differences remain. Inside a date range, the report's Delivered list leaves out only contacts whose bounce falls inside the range, where delivered here leaves out a contact whose bounce arrived at any time (see delivered). And the report's links table merges links with the same URL into one row, where link_id takes one link at a time.
Response fields
Field | Type | Description |
| string | Envoke contact ID; the Contact API |
| string | The contact's email address now. |
| string | The contact's remote ID now, or |
| string or null | When the event first happened for this contact and message, |
| integer | For |
Response
[ { "id": "1008", "email": "p.nguyen@example.org", "remote_id": "MRCT-0501", "event_time": "2026-05-16 19:44:02", "count": 2 }, { "id": "1005", "email": "l.ferreira@example.org", "remote_id": "", "event_time": "2026-05-15 07:12:55", "count": 1 }, { "id": "1004", "email": "maya.chen@example.org", "remote_id": "MRCT-0412", "event_time": "2026-05-14 12:02:44", "count": 1 } ]Errors
Invalid requests return HTTP 400 with the validation envelope, every problem listed together (see Error Responses):
{ "success": false, "result_data": { "errors": [ "Parameter 'event' must be provided", "Invalid 'activity_from': '05/14/2026' (expected YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)" ] }, "result_text": "Validation failed, see 'result_data' -> 'errors'" }Besides the shared date, limit, skip, and unknown-parameter errors:
Error | Cause |
| No |
|
|
|
|
|
|
|
|
| A |
|
|
| A name in |
|
|
The requires event errors are given only when event is itself valid. A message that is not found returns [] once every parameter is valid, whatever link_id says.
Response | Cause |
| The message does not exist, belongs to another account, is deleted, or has not started sending; or no contact has the event. |
HTTP 404, |
|
HTTP 403, | The account is restricted from seeing contacts associated with email activity. Returned for every event, before the parameters are checked. |
The 403 body is the same as the contact activity report's:
{ "success": false, "result_text": "Contact-level email activity is not available on this account", "result_data": [] }Examples
The ten most recent openers
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2004/contacts?event=opened"
Everyone who clicked one link
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2004/contacts?event=clicked&link_id=88125&limit=100"
Response (first rows; 39 rows in all, the link's clicks in the single-message report):
[ { "id": "1009", "email": "s.gill@example.org", "remote_id": "MRCT-0533", "event_time": "2026-05-19 21:05:37", "count": 1 }, { "id": "1004", "email": "maya.chen@example.org", "remote_id": "MRCT-0412", "event_time": "2026-05-14 12:03:10", "count": 1 } ]Sent but did not open, IDs only
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2004/contacts?event=not_opened&fields=id"
Response (up to 10,000 rows, highest ID first; 2004 was sent to 386 contacts, of whom 201 opened and 185 did not):
[ { "id": "1006" }, { "id": "1003" }, { "id": "1002" } ]Hard bounces and invalid addresses
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2002/contacts?event=bounced&bounce_type%5B%5D=hard_bounce&bounce_type%5B%5D=invalid_address"
Response (the contacts counted in hard_bounces):
[ { "id": "1007", "email": "r.osei@example.org", "remote_id": "", "event_time": "2026-03-10 18:02:13", "count": 1 } ]Openers in the first week, every row
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2004/contacts?event=opened&activity_from=2026-05-14&activity_to=2026-05-20&fields=id,email,event_time"
Second page of 100 senders
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2004/contacts?event=sent&limit=100&skip=100"
A message that is not found
curl -X GET -gs -u "$API_ID:$API_KEY" \ "https://e1.envoke.com/v2/reports/messages/2099/contacts?event=opened"
Response:
[]
Note: The curl examples above are formatted for readability. All requests must use properly encoded URLs.
For code examples and interactive testing, visit api.envoke.com.
