Paginación
`list` para una página, `list_all` para todas las páginas e `iterate` para un elemento cada vez.
Una página: list
cursor: str | None = None while True: page = client.emails.list(status='failed', limit=50, cursor=cursor) for email in page['items']: print(email['id'], email['lastError']) if not page['hasMore'] or page['nextCursor'] is None: break cursor = page['nextCursor']Una página es {'items': [...], 'hasMore': ..., 'nextCursor': ...}. Devuelve nextCursor como cursor=, con los mismos filtros, para obtener la página siguiente.
nextCursor es None en la última página, y hasMore indica si hay más filas que coinciden después de esta. En openemail.types una página es Page[T], así que emails.list devuelve un Page[EmailResource] y un comprobador de tipos sabe qué contiene cada elemento.
La API pagina los hilos y los borradores con un pageToken. El cliente te lo entrega como nextCursor y lo recibe de vuelta como cursor=, igual que cualquier otra lista, y list_all e iterate lo siguen por ti. Es opaco: devuelve lo que te dieron y nunca construyas uno.
Todas las páginas: list_all
complaints = client.suppressions.list_all(reason='complaint')print(len(complaints), [row['email'] for row in complaints]) book = client.addresses.list_all()print(book['unrestricted'], len(book['addresses']), len(book['domains']))list_all sigue nextCursor hasta la última página y devuelve una única list. Obtiene todas las páginas antes de devolver el resultado, así que dale un filtro que termine.
addresses.list_all es el único que devuelve otra cosa: la libreta de direcciones completa, {'unrestricted': ..., 'addresses': [...], 'domains': [...]}, con las direcciones de todas las páginas dentro.
Uno a uno: iterate
from itertools import islice for email in client.emails.iterate(status='bounced'): if email['createdAt'] < '2026-09-01': break print(email['id'], email['lastError']) first_ten = list(islice(client.threads.iterate(folder='inbox'), 10))print([thread['id'] for thread in first_ten])iterate devuelve un generador. Llamarlo no envía nada: la primera página se obtiene cuando el bucle pide su primer elemento, y cada página siguiente solo cuando el bucle llega al final de la anterior. Salir del bucle, o tomar un fragmento con itertools.islice, detiene las solicitudes.
limit= en list_all e iterate es el tamaño de cada página que obtienen, no un tope de cuántos elementos recibes, así que un limit mayor significa menos solicitudes. cursor= empieza el recorrido desde una página que ya tienes. Ambos se detienen cuando hasMore es falso o nextCursor es None, y también cuando una página nombra el cursor con el que se obtuvo, así que una página defectuosa no puede provocar un bucle infinito.
Listas dentro de un espacio de nombres
list_all e iterate paginan el mismo endpoint que el list que tienen al lado, y lo mismo hacen las demás parejas que llevan el nombre de la lista que recorren. Cada pareja acepta los mismos filtros que su lista.
| Espacio de nombres | Una página | Todas las páginas | Uno a uno |
|---|---|---|---|
| emails | list_events | list_all_events | iterate_events |
| calendar | list_events | list_all_events | iterate_events |
| templates | list_versions | list_all_versions | iterate_versions |
| templates | list_images | list_all_images | iterate_images |
| tracking | list_opens | list_all_opens | iterate_opens |
| tracking | list_clicks | list_all_clicks | iterate_clicks |
| rules | list_runs | list_all_runs | iterate_runs |
| audiences | list_contacts | list_all_contacts | iterate_contacts |
| broadcasts | list_recipients | list_all_recipients | iterate_recipients |
| contacts | list_people | list_all_people | iterate_people |
| contacts | list_threads | list_all_threads | iterate_threads |
| domains | list_addresses | list_all_addresses | iterate_addresses |
| forms | list_submissions | list_all_submissions | iterate_submissions |
| members | list_invitations | list_all_invitations | iterate_invitations |
| files | list_links | list_all_links | iterate_links |
| temp_mail | list_messages | list_all_messages | iterate_messages |
| webhooks | list_deliveries | list_all_deliveries | iterate_deliveries |
| webhooks | list_workspace_deliveries | list_all_workspace_deliveries | iterate_workspace_deliveries |
| webhooks | list_activity | list_all_activity | iterate_activity |
| webhooks | list_workspace_activity | list_all_workspace_activity | iterate_workspace_activity |
| keys | list_requests | list_all_requests | iterate_requests |
| keys | list_activity | list_all_activity | iterate_activity |
| keys | list_workspace_requests | list_all_workspace_requests | iterate_workspace_requests |
| keys | list_workspace_activity | list_all_workspace_activity | iterate_workspace_activity |
Páginas que traen más
people = client.contacts.list_people(sort='recent', limit=50)print(len(people['items']), people['seen'], people['nextCursor']) sends = client.templates.list_sends('order-shipped', page=2, page_size=50)print(sends['total'], sends['page'], sends['pageSize'], len(sends['items']))Algunas listas traen algo más que una página. contacts.list_people añade seen junto a items, hasMore y nextCursor, temp_mail.list_messages añade expiresAt, y addresses.list devuelve addresses en lugar de items, junto a unrestricted, domains, hasMore y nextCursor. Aun así, cada una continúa con cursor= y se recorre con su list_all y su iterate.
Dos listas paginan de otra forma. templates.list_sends cuenta páginas: pasa page= y page_size=, y lee total, page y pageSize junto a items. imports.list_failures se deja tal como la envía la API, {'object': 'list', 'data': [...], 'nextCursor': ...}, y continúa con after=, el número que hay en nextCursor. Ninguna de las dos tiene list_all ni iterate.
Tamaños de página
La mayoría de las listas aceptan un limit= de 1 a 100, con 25 por defecto, los dos números de PAGE_LIMITS (PAGE_LIMITS.MAX_LIMIT y PAGE_LIMITS.DEFAULT_LIMIT). contacts.list, audiences.list_contacts, broadcasts.list_recipients y las listas de tracking aceptan hasta 200 con un valor predeterminado de 50, y temp_mail.list_messages acepta hasta 50. Un valor fuera del rango se rechaza con un 422 en lugar de ajustarse al límite.
En AsyncOpenEmail
El cliente asíncrono pagina de la misma forma. list y list_all se llaman con await, e iterate devuelve un iterador asíncrono que recorres con async for, sin await delante de la llamada.
import asyncio from openemail import AsyncOpenEmail async def main() -> None: async with AsyncOpenEmail() as client: page = await client.emails.list(status='failed', limit=50) print(len(page['items']), page['nextCursor']) everyone = await client.members.list_all() print(len(everyone)) async for delivery in client.webhooks.iterate_deliveries('whe_…', status='failed'): print(delivery['eventType'], delivery['responseCode']) asyncio.run(main())