Skip to the documentation
SDK

List and get

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

emails.list

list-emails.ts
const first = await openemail.emails.list({  status: ['queued', 'scheduled'],  from: '[email protected]',  limit: 50,}) const second = first.nextCursor  ? await openemail.emails.list({ status: ['queued', 'scheduled'], limit: 50, cursor: first.nextCursor })  : null

A page is { items, hasMore, nextCursor }. Pass nextCursor back as cursor, with the same filters, for the page after it.

emails.iterate and emails.listAll

iterate-emails.ts
for await (const email of openemail.emails.iterate({ status: 'failed' })) {  console.error(email.id, email.lastError)} const failures = await openemail.emails.listAll({ status: 'failed', from: '[email protected]' })

Both follow nextCursor for you. iterate fetches a page only when the loop reaches it, so breaking out stops the requests, while listAll walks every page before it resolves to 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.ts
const email = await openemail.emails.get('msg_…')console.log(email.status, email.recipients) const events = await openemail.emails.listEvents('msg_…')for (const event of events) console.log(event.type, event.createdAt)

get is the only call that returns recipients, one row per address. A list of fifty messages each carrying its recipients is a page of report nobody asked for.

Parameters

statusEmailStatus | EmailStatus[]
One status or several (`queued`, `scheduled`, `sending`, `sent`, `partial`, `cancelled`, `failed`), matching any one of those given. The SDK sends an array as a single comma-separated value because the server splits on commas; a value outside that set is a 422 naming the unknown one.
fromstring
Exact match on the sending address as it was recorded, which is the bare `addr@host` lowercased. The row is written with any display name stripped, so an angle-addr such as `Acme <[email protected]>` matches nothing. Your value is lowercased before comparison, and it is equality rather than a prefix or domain match.
limitnumber
Rows in this page, 1 to 100, defaulting to 25. A value outside that range is refused as a 422 rather than clamped.
cursorstring
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.

Response: Page<EmailResource>

itemsEmailResource[]
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`.
hasMoreboolean
Whether more rows match the filter beyond this page. Answered by fetching one row more than `limit` rather than by a second count query.
nextCursorstring | 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.
items[].object'email'
Always `'email'` on a row of this list.
items[].idstring
This API's own id, `msg_…`. It is what every other emails endpoint takes, and what a cursor names.
items[].statusEmailStatus
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.
items[].modeApiKeyMode
`live` or `test`, taken from the key that sent it. A test send is recorded here and never transmitted.
items[].fromstring
The address the send was authorised under, stored bare and lowercased, so a display name given on `from` still goes out on the wire but is not kept here. A plain string rather than an object 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.
items[].subjectstring | null
The subject as stored. Null on a message recorded without one.
items[].messageIdstring | 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 `items[].id` instead.
items[].threadIdstring | null
The thread this message belongs to, where one was given or assigned. Null otherwise.
items[].transportEmailTransport | (string & {}) | null
How the bytes left. Null until dispatch, and typed open so a transport this SDK does not yet name is not a breaking change: stored records can still name ones no longer in use.
items[].attemptsnumber
How many dispatch attempts the message has had, 0 before the first.
items[].lastErrorstring | null
The most recent dispatch error, written for a human. Null while nothing has failed.
items[].scheduledAtstring | 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`.
items[].cancellableUntilstring | 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`.
items[].sentAtstring | null
When it went. Null until dispatch has completed, which is why `status` and not this is the field to branch on.
items[].tagsRecord<string, string>
The labels supplied on the send, echoed back and never interpreted. Always an object (`{}` where none were set, never null), and echoed only: this endpoint filters on `status` and `from`, so a tag is something to read off a message rather than a way to find one.
items[].sourceEmailSource
Which surface asked for the send: `composer`, `api`, `mcp`, `ai` or `queue`. `api` is this client.
items[].createdAtstring
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.
items[].trackingEmailTrackingSummary
The engagement summary, present only on a row whose message was tracked and absent otherwise. Absent is the answer to "was this tracked", where `openCount: 0` would read as "nobody opened it".
items[].tracking.opensboolean
Whether this message left with a pixel. What was applied to this message, not what the account setting says now.
items[].tracking.clicksboolean
Whether this message's links were rewritten. False when the body had no links to rewrite, since nothing was then changed.
items[].tracking.openedboolean
Whether any counted open was recorded, derived from `openCount > 0`.
items[].tracking.clickedboolean
Whether any counted click was recorded, derived from `clickCount > 0`.
items[].tracking.openCountnumber
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.
items[].tracking.clickCountnumber
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.
items[].tracking.firstOpenAtstring | null
The earliest counted open across the copies, and null while there is none. Machine hits never move it.
items[].translationEmailTranslationResource
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`.