Skip to the documentation
Ruby

List and get

`emails.list`, `emails.list_all`, `emails.iterate`, `emails.get` and `emails.list_events`.

emails.list

list_emails.rb
filters = {status: ["queued", "scheduled"], from: "[email protected]"} first = client.emails.list(**filters, limit: 50)second = client.emails.list(**filters, limit: 50, cursor: first.next_cursor) if first.next_cursor p first.items.size, second&.items&.size

A page is an OpenEmail::Page with items, has_more? and next_cursor. Pass next_cursor back as cursor:, with the same filters, for the page after it.

emails.iterate and emails.list_all

iterate_emails.rb
client.emails.iterate(status: "failed") do |email|  warn "#{email[:id]} #{email[:lastError]}"end failures = client.emails.list_all(status: "failed", from: "[email protected]")puts failures.size

Both follow next_cursor for you. iterate fetches a page only when the walk reaches it, so break in the block, or first or find on the Enumerator it returns without one, stops the requests, while list_all 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.list_events

get_email.rb
email = client.emails.get("msg_3f9a1c07d2b84e6a9c5b1f20")puts email[:status]p email[:recipients] events = client.emails.list_all_events("msg_3f9a1c07d2b84e6a9c5b1f20")events.each { |event| puts "#{event[:type]} #{event[:createdAt]}" }

get is the only call that returns recipients, one Hash 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.

list_events 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 Hash whose shape depends on its type. list_all_events and iterate_events 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

statusString or Array<String>
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 gem 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.
broadcast_idString
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.list_recipients` lists the same people with their opens, clicks and unsubscribes.
fromString
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 <[email protected]>` matches nothing. Your value is lower-cased before the comparison, and it is equality rather than a prefix or a domain match.
scheduled_fromTime, DateTime or String
Only messages scheduled for this instant or later. With `scheduled_to:` 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 Time, a DateTime or an ISO 8601 instant with its offset: a Ruby Date is sent as a bare date, which these two filters refuse.
scheduled_toTime, DateTime or String
Only messages scheduled for this instant or earlier. `scheduled_from:` after `scheduled_to:` is a 422 `invalid_parameter`.
limitInteger
Rows in this page, 1 to 100, defaulting to 25. A value outside that range is refused as a 422 rather than clamped. On `list_all` and `iterate` it is the size of each page they fetch.
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 `invalid_cursor`.
api_keyString
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::Page

itemsArray<Hash>
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`.
has_more?Boolean
Whether more rows match the filter beyond this page. Answered by fetching one row more than `limit` rather than by a second count query.
next_cursorString or nil
The id to pass back as `cursor:`, and nil on the last page. `iterate` and `list_all` stop when this is nil or `has_more?` is false, since a page claiming more while naming no cursor would loop for ever.

Each item

objectString
Always `email` on a row of this list.
idString
This API’s own id, `msg_…`. It is what every other emails call takes, and what a cursor names.
statusString
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.
modeString
`live` or `test`, taken from the key that sent it. A test send is recorded here and never transmitted.
fromString
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 a Hash 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.
subjectString or nil
The subject as stored. nil on a message recorded without one.
messageIdString or nil
The RFC 5322 Message-ID, not our id. nil 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.
threadIdString or nil
The thread this message belongs to, where one was given or assigned. nil otherwise.
transportString or nil
How the bytes left. nil 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.
attemptsInteger
How many dispatch attempts the message has had, 0 before the first.
lastErrorString or nil
The most recent dispatch error, written for a person. nil while nothing has failed.
scheduledAtString or nil
When the message is due to leave, as an ISO 8601 instant. nil 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`.
cancellableUntilString or nil
The instant the message is due to leave, carrying the same value as `scheduledAt` on any send that was deferred and nil 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`.
sentAtString or nil
When it went. nil until dispatch has completed, which is why `status` and not this is the field to branch on.
tagsHash
The labels supplied on the send, echoed back and never interpreted. Always a Hash, empty where none were set and never nil, and echoed only: this list filters on `status`, `from`, `broadcast_id` and the schedule window, so a tag is something to read off a message rather than a way to find one.
broadcastIdString or nil
The `brd_` broadcast this message is one copy of, or nil for a message sent on its own.
sourceString
Which surface asked for the send: `composer`, `api`, `mcp`, `ai` or `queue`. `api` is this client.
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.
trackingHash
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".
translationHash
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

opensBoolean
Whether this message left with a pixel. What was applied to this message, not what the account setting says now.
clicksBoolean
Whether this message’s links were rewritten. False when the body had no links to rewrite, since nothing was then changed.
openedBoolean
Whether any counted open was recorded, derived from `openCount` above 0.
clickedBoolean
Whether any counted click was recorded, derived from `clickCount` above 0.
openCountInteger
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.
clickCountInteger
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.
firstOpenAtString or nil
The earliest counted open across the copies, and nil while there is none. Machine hits never move it.