---
title: "List and get"
description: "`emails->list`, `emails->listAll`, `emails->iterate`, `emails->get` and `emails->listEvents`."
url: "https://openemail.uk/docs/php/emails/list"
area: "PHP"
category: "Emails"
---

# List and get

`emails->list`, `emails->listAll`, `emails->iterate`, `emails->get` and `emails->listEvents`.

## emails->list

**list_emails.php**

```
$filters = ['status' => ['queued', 'scheduled'], 'from' => 'billing@acme.com'];

$first = $client->emails->list(...$filters, limit: 50);
$second = $first->hasMore ? $client->emails->list(...$filters, limit: 50, cursor: $first->nextCursor) : null;

echo count($first), ' ', $second === null ? 0 : count($second), PHP_EOL;
```

A page is an `OpenEmail\Result\Page` with `items`, `hasMore` and `nextCursor`. Pass `nextCursor` back as `cursor:`, with the same filters, for the page after it. Spreading one array of filters into each call, as `...$filters` does, keeps them the same.

## emails->iterate and emails->listAll

**iterate_emails.php**

```
foreach ($client->emails->iterate(status: 'failed') as $email) {
    error_log($email['id'] . ' ' . ($email['lastError'] ?? ''));
}

$failures = $client->emails->listAll(status: 'failed', from: 'billing@acme.com');
echo count($failures), PHP_EOL;
```

Both follow `nextCursor` for you. `iterate` returns a `Generator` that fetches a page only when the walk reaches it, so a `break` out of the `foreach` stops the requests, while `listAll` walks every page before it returns one array, so give it a filter that ends. Keyset paging either way, so a message arriving mid-iteration cannot make this skip a row the way an offset would.

## emails->get and emails->listEvents

**get_email.php**

```
$email = $client->emails->get('msg_3f9a1c07d2b84e6a9c5b1f20');
echo $email['status'], PHP_EOL;
print_r($email['recipients']);

$events = $client->emails->listAllEvents('msg_3f9a1c07d2b84e6a9c5b1f20');

foreach ($events as $event) {
    echo $event['type'], ' ', $event['createdAt'], PHP_EOL;
}
```

> `get` is the only call that returns `recipients`, one array per address with its own `status`, `error` and `deliveredAt`. A list of fifty messages each carrying its recipients is a page of report nobody asked for.

`listEvents` reads the event trail of one send, oldest first: `email.accepted`, `email.queued`, `email.sent`, `email.delivered`, `email.bounced`, `email.opened` and the rest, each with a `data` array whose shape depends on its `type`. `listAllEvents` and `iterateEvents` walk the whole trail for you. Webhooks deliver a subset of the same events as they happen, so this is where to look when a webhook was missed.

## Parameters

- `status` (string or array): One status or several (`queued`, `scheduled`, `sending`, `sent`, `partial`, `bounced`, `cancelled`, `failed`), matching any one of those given. `bounced` means every recipient the message went to bounced, while a message that bounced for some and reached the rest reads `partial`. The client sends an array as one comma-separated value because the server splits on commas, and a value outside the set is a 422 naming the unknown one.
- `broadcastId` (string): Only the copies of one broadcast, a `brd_` id from `broadcasts->send`. Every person a broadcast reaches gets a message of their own, so this lists who it went to and what happened to each copy. `broadcasts->listRecipients` lists the same people with their opens, clicks and unsubscribes.
- `from` (string): Exact match on the sending address as it was recorded, which is the bare `addr@host` lower-cased. The row is written with any display name stripped, so an angle-addr such as `Acme <billing@acme.com>` matches nothing. Your value is lower-cased before the comparison, and it is equality rather than a prefix or a domain match.
- `scheduledFrom` (DateTimeInterface or string): Only messages scheduled for this instant or later. With `scheduledTo:` and `status: ['scheduled', 'queued']` it lists what is waiting to go out in a window, as the calendar of the app does. A message with no `scheduledAt` is left out. Pass a `DateTimeInterface`, sent as an instant in UTC, or an ISO 8601 instant with its offset: a date string with no time is refused by these two filters.
- `scheduledTo` (DateTimeInterface or string): Only messages scheduled for this instant or earlier. `scheduledFrom:` after `scheduledTo:` is a 422 `invalid_parameter`.
- `limit` (int): Rows in this page, 1 to 100, defaulting to 25. A value outside that range is refused as a 422 rather than clamped. On `listAll` and `iterate` it is the size of each page they fetch.
- `cursor` (string): A message id (`msg_…`) to page from. Keyset rather than offset: rows come back strictly older than that message’s `createdAt`, so sends arriving mid-page cannot push a row past you. An id that names no message in this workspace is a 400 `invalid_cursor`.
- `apiKey` (string): Lists with this key instead of the client’s.

> A key narrowed to some addresses reads only the messages sent from addresses it covers, and the page is cut after that filter, so every page but the last still holds `limit` rows. A `from:` the key does not cover returns an empty last page rather than a 403.

## Response: OpenEmail\Result\Page

- `items` (array): One page of messages, newest first by `createdAt`, lifted out of the API’s `data` envelope. List rows never carry the per-address `recipients` breakdown. That is on `get`.
- `hasMore` (bool): Whether more rows match the filter beyond this page. Answered by fetching one row more than `limit` rather than by a second count query.
- `nextCursor` (string or null): The id to pass back as `cursor:`, and null on the last page. `iterate` and `listAll` stop when this is null or `hasMore` is false, since a page claiming more while naming no cursor would loop for ever.

**Each item**

- `object` (string): Always `email` on a row of this list.
- `id` (string): This API’s own id, `msg_…`. It is what every other emails call takes, and what a cursor names.
- `status` (string): Where the message is in its life. `partial` is a state of its own rather than a flavour of failed: some recipients have it and cannot be un-sent, so retrying is wrong. `bounced` means every recipient bounced after it left, so nobody has it, and each recipient in `get` says why.
- `mode` (string): `live` or `test`, taken from the key that sent it. A test send is recorded here and never transmitted.
- `from` (string): The address the send was authorised under, stored bare and lower-cased, so a display name given on `from` still goes out on the wire but is not kept here. A plain string rather than an array because this is the identity that was authorised: an address outside a key’s send scope, neither on a domain it holds nor named on it, is refused with a 403, never quietly swapped for one it does.
- `subject` (string or null): The subject as stored. null on a message recorded without one.
- `messageId` (string or null): The RFC 5322 Message-ID, not our id. null until the MIME exists, and rewritten by the sending service on the way out, so a later bounce or DSN carries a different id and correlates on `id` instead.
- `threadId` (string or null): The thread this message belongs to, where one was given or assigned. null otherwise.
- `transport` (string or null): How the bytes left. null until dispatch. Stored records can still name transports no longer in use, so treat a value you do not know as information rather than an error.
- `attempts` (int): How many dispatch attempts the message has had, 0 before the first.
- `lastError` (string or null): The most recent dispatch error, written for a person. null while nothing has failed.
- `scheduledAt` (string or null): When the message is due to leave, as an ISO 8601 instant. null only on an immediate send with no cancellation window: a window is a short delay and nothing else, so `cancellableForSeconds` fills this in too, on a row whose `status` is `queued` rather than `scheduled`.
- `cancellableUntil` (string or null): The instant the message is due to leave, carrying the same value as `scheduledAt` on any send that was deferred and null on one that was not. It is a timestamp to show rather than the test the server makes: `cancel` branches on `status`, and stops a message only while it is still `queued` or `scheduled`.
- `sentAt` (string or null): When it went. null until dispatch has completed, which is why `status` and not this is the field to branch on.
- `tags` (array): The labels supplied on the send, echoed back and never interpreted. Always an array, empty where none were set and never null, and echoed only: this list filters on `status`, `from`, `broadcastId` and the schedule window, so a tag is something to read off a message rather than a way to find one.
- `broadcastId` (string or null): The `brd_` broadcast this message is one copy of, or null for a message sent on its own.
- `source` (string): Which surface asked for the send: `composer`, `api`, `mcp`, `ai` or `queue`. `api` is this client.
- `createdAt` (string): When the send record was written, which is before dispatch. This is the field the list orders by and the field a cursor compares against.
- `tracking` (array): The engagement summary, present only on a row whose message was tracked and absent otherwise. Absent is the answer to "was this tracked", where an `openCount` of 0 would read as "nobody opened it", so read it with `?? null` rather than assuming the key.
- `translation` (array): Never present on a list row: the translation record lives in the stored request, which a list deliberately does not fetch. Its absence here says nothing about whether the message was translated. Ask `get`.

**An item’s tracking**

- `opens` (bool): Whether this message left with a pixel. What was applied to this message, not what the account setting says now.
- `clicks` (bool): Whether this message’s links were rewritten. False when the body had no links to rewrite, since nothing was then changed.
- `opened` (bool): Whether any counted open was recorded, derived from `openCount` above 0.
- `clicked` (bool): Whether any counted click was recorded, derived from `clickCount` above 0.
- `openCount` (int): Opens believed to have been caused by a person, summed over every copy of the message. Scanners and privacy proxies are recorded but excluded, and repeat fetches within thirty seconds collapse into one.
- `clickCount` (int): Counted clicks, summed over the copies. Deduplicated per link rather than per message, because following two links seconds apart is two acts and not a repeat.
- `firstOpenAt` (string or null): The earliest counted open across the copies, and null while there is none. Machine hits never move it.
