Pagination
`list` for one page, `list_all` for every page, and `iterate` for one item at a time.
One 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']A page is {'items': [...], 'hasMore': ..., 'nextCursor': ...}. Pass nextCursor back as cursor=, with the same filters, for the page after it.
nextCursor is None on the last page, and hasMore says whether more rows match beyond this one. In openemail.types a page is Page[T], so emails.list returns a Page[EmailResource] and a type checker knows what each item holds.
The API pages threads and drafts with a pageToken. The client hands it to you as nextCursor and takes it back as cursor=, like every other list, and list_all and iterate follow it for you. It is opaque: pass back what you were given and never build one.
Every page: 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 follows nextCursor to the last page and returns one list. Every page is fetched before it returns, so give it a filter that ends.
addresses.list_all is the one that returns something else: the whole address book, {'unrestricted': ..., 'addresses': [...], 'domains': [...]}, with the addresses of every page in it.
One at a time: 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 returns a generator. Calling it sends nothing: the first page is fetched when the loop asks for its first item, and each page after it only when the loop reaches the end of the one before. Breaking out of the loop, or taking a slice with itertools.islice, stops the requests.
limit= on list_all and iterate is the size of each page they fetch, not a cap on how many items you get, so a larger limit means fewer requests. cursor= starts the walk from a page you already hold. Both stop when hasMore is false or nextCursor is None, and also when a page names the cursor that fetched it, so a misbehaving page cannot loop for ever.
Lists inside a namespace
list_all and iterate page through the same endpoint as the list beside them, and so do the other pairs named after the list they walk. Each pair takes the same filters as its list.
| Namespace | One page | Every page | One at a time |
|---|---|---|---|
| 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 |
Pages that carry more
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']))A few lists carry more than a page. contacts.list_people adds seen beside items, hasMore and nextCursor, temp_mail.list_messages adds expiresAt, and addresses.list returns addresses in place of items, beside unrestricted, domains, hasMore and nextCursor. Each still continues with cursor= and walks with its list_all and iterate.
Two lists page another way. templates.list_sends counts pages: pass page= and page_size=, and read total, page and pageSize beside items. imports.list_failures is left as the API sends it, {'object': 'list', 'data': [...], 'nextCursor': ...}, and continues with after=, the number in nextCursor. Neither has a list_all or an iterate.
Page sizes
Most lists take a limit= from 1 to 100 and default to 25, the two numbers in PAGE_LIMITS (PAGE_LIMITS.MAX_LIMIT and PAGE_LIMITS.DEFAULT_LIMIT). contacts.list, audiences.list_contacts, broadcasts.list_recipients and the tracking lists take up to 200 with a default of 50, and temp_mail.list_messages takes up to 50. A value outside the range is refused with a 422 rather than clamped.
On AsyncOpenEmail
The async client pages the same way. list and list_all are awaited, and iterate returns an async iterator that you loop over with async for, with no await in front of the call.
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())