Skip to the documentation
Ruby

Pagination

One page, every page, or one item at a time, on every list that pages.

list, list_all and iterate

Every list that pages has three methods. list fetches one page and returns an OpenEmail::Page. list_all follows the cursor through every page and returns one Array. iterate walks the same pages one item at a time: it yields each item to a block, or returns an Enumerator when you give it none. All three take the list’s filters, limit:, cursor: and api_key:.

three_ways.rb
page = client.emails.list(status: "failed", limit: 50)page.items.each { |email| puts "#{email[:id]} #{email[:lastError]}" } failures = client.emails.list_all(status: "failed") client.emails.iterate(status: "failed") do |email|  puts email[:id]end puts failures.size, page.has_more?

The same three names repeat wherever a namespace has more than one list, named after the list they walk: list_events, list_all_events and iterate_events on emails, list_deliveries, list_all_deliveries and iterate_deliveries on webhooks, and so on.

OpenEmail::Page

itemsArray<Hash>
The rows of this page, lifted out of the API’s `data` envelope, each a Hash with Symbol keys. Empty when the page holds nothing.
has_more?Boolean
Whether another page follows. `has_more` without the question mark reads the same value. When the API sends no `hasMore`, it is true exactly when there is a `next_cursor`.
next_cursorString or nil
What to pass back as `cursor:` for the next page, and nil on the last one.

A page is a Ruby Data object, so it is frozen, compares by value, and turns into a Hash with to_h.

A block or an Enumerator

Given a block, iterate walks every page now and yields each item to it. Without one it returns an Enumerator and fetches nothing until you consume it. Either way it asks for the next page only once every item of the current one has been yielded, so anything that stops early stops the requests too: first(10) reads only as many pages as ten items need, find stops at the match, and break in a block ends the walk.

enumerator.rb
latest = client.emails.iterate(status: "failed", limit: 100).first(10) invoice = client.emails.iterate(status: "failed").find do |email|  email.dig(:tags, :invoice) == "inv_2026_09_4192"end from_api = client.emails.iterate(status: "bounced").lazy.select { |email| email[:source] == "api" }.first(5) p latest.size, invoice&.fetch(:id), from_api.map { |email| email[:id] }

An Enumerable method that needs every item, such as select, map or count called straight on the Enumerator, reads every page before it returns, the way list_all does. Put lazy in front to chain them and still stop early.

An Enumerator starts its walk again each time it is consumed, so calling first(10) on the same one twice fetches the first page twice. Keep the result, not the Enumerator, when you need it again.

Resuming from a cursor

A cursor is opaque. Keep the next_cursor of the last page you read and pass it back as cursor: to carry on from there, in a later request or in another process. list_all and iterate take cursor: too, and start their walk after it.

resume.rb
first_page = client.emails.list(status: "failed", limit: 25)saved = first_page.next_cursor if saved  rest = client.emails.list_all(status: "failed", cursor: saved)  puts rest.sizeend

A cursor belongs to the list and the filters it came from, so send the same filters with it. One the list cannot place is refused with invalid_cursor, and the answer then is to start again without one.

limit:

limit: is the size of each page, not a total. On list it is how many rows come back. On list_all and iterate it is how many each request asks for, so a larger value means fewer round trips for the same rows. Each list has its own range and default, most often 1 to 100 with 25 when you send none, and a value outside the range is refused rather than clamped. The page for each list gives its range.

When a walk stops

  • When a page says has_more? is false.
  • When a page carries no next_cursor, since a page that claims more while naming no cursor would loop for ever.
  • When the API hands back the cursor it was just given, for the same reason.

Each page is a GET, so it is retried on its own like any read before anything raises. A failure that survives the retries raises out of list_all, and the items already fetched are discarded. In iterate the items of the earlier pages have already been yielded by then, so make what the block does safe to run twice, or page with list and keep each next_cursor so a second attempt can start where the first one stopped.

Threads and drafts

threads.list and drafts.list, with their list_all and iterate, page with the API’s pageToken and nextPageToken rather than a cursor. The gem hides the difference: pass the token as cursor: and read it from next_cursor.

page_token.rb
page = client.threads.list(folder: "inbox", limit: 50)later = client.threads.list(folder: "inbox", limit: 50, cursor: page.next_cursor) if page.has_more? p page.items.size, later&.items&.size

The server offers a token whenever a page comes back full, so has_more? can be true on what turns out to be the last page, and the next call then returns no items.

Pages that carry more

A few lists answer with more than rows, and return a Data object of their own in place of OpenEmail::Page.

MethodReturnsWhat it adds
addresses.listOpenEmail::AddressBookPageaddresses in place of items, plus unrestricted and domains, with has_more? and next_cursor.
addresses.list_allOpenEmail::AddressBookEvery address in addresses, with unrestricted and domains as the last page reported them. It is the one list_all that returns the whole address book rather than an Array. addresses.iterate yields the addresses alone.
contacts.list_peopleOpenEmail::PeoplePageseen, false when the key cannot read the addresses seen in mail. list_all_people and iterate_people return the people alone.
temp_mail.list_messagesOpenEmail::TempMessagesPageexpires_at, when the inbox runs out. list_all_messages and iterate_messages return the messages alone.
templates.list_sendsOpenEmail::TemplateSendsPaged by number rather than by cursor: items, total, page and page_size. Ask for the next page with page:.
emails.send_batchOpenEmail::BatchResultNot a page: items, one for each message you sent, with the sent and failed counts.

Lists that return the API’s Hash

Some lists page by offset, by page number or by a numeric cursor of their own, and return the parsed body as it came, a Hash with data, rather than an OpenEmail::Page. They have no list_all or iterate, so you page them yourself.

MethodPages withWhat comes back
exports.listlimit: and offset:data, total and hasMore.
imports.list_failuresafter: and limit:data, and nextCursor, an Integer to pass back as after: that is nil on the last page.
subscriptions.list and subscriptions.list_domainslimit: and offset:data, total, counts and hasMore.
billing.list_invoicespage: and limit:data, total, page, limit, hasMore and metered.
offset_paging.rb
offset = 0 loop do  batch = client.subscriptions.list(status: "active", limit: 50, offset:)  batch[:data].each { |row| puts "#{row[:senderEmail]} #{row[:total]}" }   break unless batch[:hasMore]   offset += batch[:data].sizeend

A list that is not paged at all, such as languages.list, labels.list_colors or roles.list_permissions, returns an Array straight away.