Pagination
`list` pour une page, `list_all` pour toutes les pages, et `iterate` pour un élément à la fois.
Une page : 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']Une page est {'items': [...], 'hasMore': ..., 'nextCursor': ...}. Renvoyez nextCursor comme cursor=, avec les mêmes filtres, pour obtenir la page suivante.
nextCursor vaut None sur la dernière page, et hasMore indique si d'autres lignes correspondent au-delà de celle-ci. Dans openemail.types, une page est un Page[T] : emails.list renvoie donc un Page[EmailResource], et un vérificateur de types sait ce que contient chaque élément.
L'API pagine les fils et les brouillons avec un pageToken. Le client vous le remet sous le nom nextCursor et le reprend comme cursor=, comme pour toute autre liste, et list_all et iterate le suivent pour vous. Il est opaque : renvoyez ce qu'on vous a donné et n'en construisez jamais un.
Toutes les pages : 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 suit nextCursor jusqu'à la dernière page et renvoie une seule list. Toutes les pages sont récupérées avant qu'il ne rende la main : donnez-lui donc un filtre qui se termine.
addresses.list_all est le seul qui renvoie autre chose : le carnet d'adresses entier, {'unrestricted': ..., 'addresses': [...], 'domains': [...]}, avec les adresses de toutes les pages.
Un à la fois : 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 renvoie un générateur. L'appeler n'envoie rien : la première page est récupérée quand la boucle demande son premier élément, et chaque page suivante seulement quand la boucle atteint la fin de la précédente. Sortir de la boucle, ou prendre une tranche avec itertools.islice, arrête les requêtes.
limit= sur list_all et iterate est la taille de chaque page qu'ils récupèrent, pas un plafond sur le nombre d'éléments obtenus : un limit plus grand signifie donc moins de requêtes. cursor= fait partir le parcours d'une page que vous détenez déjà. Les deux s'arrêtent quand hasMore est faux ou que nextCursor vaut None, et aussi quand une page désigne le curseur qui l'a récupérée : une page défaillante ne peut donc pas boucler indéfiniment.
Les listes d'un espace de noms
list_all et iterate paginent sur le même endpoint que le list qui les accompagne, tout comme les autres paires nommées d'après la liste qu'elles parcourent. Chaque paire accepte les mêmes filtres que sa liste.
| Espace de noms | Une page | Toutes les pages | Un à la fois |
|---|---|---|---|
| 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 |
Les pages qui portent davantage
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']))Quelques listes portent plus qu'une page. contacts.list_people ajoute seen à côté d'items, hasMore et nextCursor, temp_mail.list_messages ajoute expiresAt, et addresses.list renvoie addresses à la place d'items, à côté d'unrestricted, domains, hasMore et nextCursor. Chacune se poursuit toujours avec cursor= et se parcourt avec son list_all et son iterate.
Deux listes se paginent autrement. templates.list_sends compte les pages : passez page= et page_size=, et lisez total, page et pageSize à côté d'items. imports.list_failures est laissée telle que l'API l'envoie, {'object': 'list', 'data': [...], 'nextCursor': ...}, et se poursuit avec after=, le nombre contenu dans nextCursor. Aucune des deux n'a de list_all ni d'iterate.
Tailles de page
La plupart des listes acceptent un limit= de 1 à 100, 25 par défaut, les deux nombres de PAGE_LIMITS (PAGE_LIMITS.MAX_LIMIT et PAGE_LIMITS.DEFAULT_LIMIT). contacts.list, audiences.list_contacts, broadcasts.list_recipients et les listes de tracking acceptent jusqu'à 200, 50 par défaut, et temp_mail.list_messages jusqu'à 50. Une valeur hors de la plage est refusée avec un 422 au lieu d'être ramenée dans les bornes.
Sur AsyncOpenEmail
Le client asynchrone pagine de la même façon. list et list_all s'appellent avec await, et iterate renvoie un itérateur asynchrone que vous parcourez avec async for, sans await devant l'appel.
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())