Paginação
Uma página, todas as páginas ou um item de cada vez, em todas as listas paginadas.
list, list_all e iterate
Todas as listas paginadas têm três métodos. list obtém uma página e devolve uma OpenEmail::Page. list_all segue o cursor por todas as páginas e devolve um único Array. iterate percorre as mesmas páginas um item de cada vez: passa cada item a um bloco, ou devolve um Enumerator quando não lhe dá nenhum. Os três aceitam os filtros da lista, limit:, cursor: e api_key:.
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?Os mesmos três nomes repetem-se sempre que um espaço de nomes tem mais do que uma lista, com o nome da lista que percorrem: list_events, list_all_events e iterate_events em emails, list_deliveries, list_all_deliveries e iterate_deliveries em webhooks, e assim por diante.
OpenEmail::Page
itemsArray<Hash>- As linhas desta página, retiradas do envelope `data` da API, cada uma um Hash com chaves Symbol. Vazio quando a página não contém nada.
has_more?Boolean- Se há outra página a seguir. `has_more` sem o ponto de interrogação lê o mesmo valor. Quando a API não envia `hasMore`, é true exatamente quando existe um `next_cursor`.
next_cursorString or nil- O que deve devolver como `cursor:` para obter a página seguinte, e nil na última.
Uma página é um objeto Data do Ruby, por isso está congelada, compara-se por valor e converte-se num Hash com to_h.
Um bloco ou um Enumerator
Com um bloco, iterate percorre todas as páginas nesse momento e passa-lhe cada item. Sem bloco, devolve um Enumerator e não obtém nada até o consumir. Em ambos os casos só pede a página seguinte depois de entregues todos os itens da atual, por isso tudo o que para mais cedo também para os pedidos: first(10) só lê as páginas de que dez itens precisam, find para na correspondência e break num bloco termina o percurso.
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] }Um método de Enumerable que precisa de todos os itens, como select, map ou count chamado diretamente sobre o Enumerator, lê todas as páginas antes de devolver, tal como list_all. Ponha lazy à frente para os encadear e continuar a poder parar mais cedo.
Um Enumerator recomeça o seu percurso cada vez que é consumido, por isso chamar first(10) duas vezes sobre o mesmo obtém a primeira página duas vezes. Guarde o resultado, e não o Enumerator, quando precisar dele de novo.
Retomar a partir de um cursor
Um cursor é opaco. Guarde o next_cursor da última página que leu e devolva-o como cursor: para continuar a partir daí, num pedido posterior ou noutro processo. list_all e iterate também aceitam cursor: e começam o seu percurso a seguir a ele.
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.sizeendUm cursor pertence à lista e aos filtros de onde veio, por isso envie os mesmos filtros com ele. Um que a lista não consiga situar é recusado com invalid_cursor, e a solução é então recomeçar sem cursor.
limit:
limit: é o tamanho de cada página, não um total. Em list é quantas linhas voltam. Em list_all e iterate é quantas cada pedido pede, por isso um valor maior significa menos idas e voltas para as mesmas linhas. Cada lista tem o seu próprio intervalo e valor predefinido, quase sempre de 1 a 100 com 25 se não enviar nenhum, e um valor fora do intervalo é recusado em vez de ajustado. A página de cada lista indica o seu intervalo.
Quando um percurso para
- Quando uma página diz que
has_more?é false. - Quando uma página não traz
next_cursor, já que uma página que diz haver mais sem indicar nenhum cursor ficaria em ciclo para sempre. - Quando a API devolve o mesmo cursor que acabou de receber, pelo mesmo motivo.
Cada página é um GET, por isso é repetida por si só, como qualquer leitura, antes de se lançar o que quer que seja. Uma falha que sobreviva às repetições é lançada a partir de list_all, e os itens já obtidos são descartados. Em iterate, os itens das páginas anteriores já foram entregues nessa altura, por isso torne seguro executar duas vezes o que o bloco faz, ou pagine com list e guarde cada next_cursor para que uma segunda tentativa possa começar onde a primeira parou.
Conversas e rascunhos
threads.list e drafts.list, com os seus list_all e iterate, paginam com o pageToken e o nextPageToken da API em vez de um cursor. A gem esconde a diferença: passe o token como cursor: e leia-o de next_cursor.
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&.sizeO servidor oferece um token sempre que uma página volta cheia, por isso has_more? pode ser true naquela que acaba por ser a última página, e a chamada seguinte não devolve então nenhum item.
Páginas que trazem mais
Algumas listas respondem com mais do que linhas, e devolvem um objeto Data próprio em vez de OpenEmail::Page.
| Método | Devolve | O que acrescenta |
|---|---|---|
| addresses.list | OpenEmail::AddressBookPage | addresses em vez de items, mais unrestricted e domains, com has_more? e next_cursor. |
| addresses.list_all | OpenEmail::AddressBook | Todos os endereços em addresses, com unrestricted e domains tal como a última página os indicou. É o único list_all que devolve o livro de endereços inteiro em vez de um Array. addresses.iterate entrega apenas os endereços. |
| contacts.list_people | OpenEmail::PeoplePage | seen, false quando a chave não consegue ler os endereços vistos no correio. list_all_people e iterate_people devolvem apenas as pessoas. |
| temp_mail.list_messages | OpenEmail::TempMessagesPage | expires_at, o momento em que a caixa expira. list_all_messages e iterate_messages devolvem apenas as mensagens. |
| templates.list_sends | OpenEmail::TemplateSends | Paginado por número em vez de por cursor: items, total, page e page_size. Peça a página seguinte com page:. |
| emails.send_batch | OpenEmail::BatchResult | Não é uma página: items, um para cada mensagem que enviou, com as contagens sent e failed. |
Listas que devolvem o Hash da API
Algumas listas paginam por deslocamento, por número de página ou por um cursor numérico próprio, e devolvem o corpo analisado tal como chegou, um Hash com data, em vez de uma OpenEmail::Page. Não têm list_all nem iterate, por isso tem de as paginar você mesmo.
| Método | Pagina com | O que volta |
|---|---|---|
| exports.list | limit: e offset: | data, total e hasMore. |
| imports.list_failures | after: e limit: | data, e nextCursor, um Integer a devolver como after: que é nil na última página. |
| subscriptions.list e subscriptions.list_domains | limit: e offset: | data, total, counts e hasMore. |
| billing.list_invoices | page: e limit: | data, total, page, limit, hasMore e metered. |
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].sizeendUma lista que não é paginada, como languages.list, labels.list_colors ou roles.list_permissions, devolve logo um Array.