Skip to the documentation
Python

Pagination

`list` for one page, `list_all` for every page, and `iterate` for one item at a time.

One page: list

list_page.py
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

list_all.py
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

iterate.py
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.

NamespaceOne pageEvery pageOne at a time
emailslist_eventslist_all_eventsiterate_events
calendarlist_eventslist_all_eventsiterate_events
templateslist_versionslist_all_versionsiterate_versions
templateslist_imageslist_all_imagesiterate_images
trackinglist_openslist_all_opensiterate_opens
trackinglist_clickslist_all_clicksiterate_clicks
ruleslist_runslist_all_runsiterate_runs
audienceslist_contactslist_all_contactsiterate_contacts
broadcastslist_recipientslist_all_recipientsiterate_recipients
contactslist_peoplelist_all_peopleiterate_people
contactslist_threadslist_all_threadsiterate_threads
domainslist_addresseslist_all_addressesiterate_addresses
formslist_submissionslist_all_submissionsiterate_submissions
memberslist_invitationslist_all_invitationsiterate_invitations
fileslist_linkslist_all_linksiterate_links
temp_maillist_messageslist_all_messagesiterate_messages
webhookslist_deliverieslist_all_deliveriesiterate_deliveries
webhookslist_workspace_deliverieslist_all_workspace_deliveriesiterate_workspace_deliveries
webhookslist_activitylist_all_activityiterate_activity
webhookslist_workspace_activitylist_all_workspace_activityiterate_workspace_activity
keyslist_requestslist_all_requestsiterate_requests
keyslist_activitylist_all_activityiterate_activity
keyslist_workspace_requestslist_all_workspace_requestsiterate_workspace_requests
keyslist_workspace_activitylist_all_workspace_activityiterate_workspace_activity

Pages that carry more

other_pages.py
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.

async_pages.py
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())