Skip to content

How to list iCloud email threads

Apple doesn’t offer a public email API for iCloud Mail, let alone a threading API. The only programmatic access is raw IMAP, and even that requires each user to manually create an app-specific password. Nylas handles the IMAP connection and constructs conversation threads from message headers, exposing iCloud threads through the same Threads API you use for Gmail and Outlook.

This guide covers listing threads from iCloud accounts, including the app-specific password requirement, how threading works without native provider support, and the 90-day message cache.

Why use Nylas for threads instead of IMAP directly?

Section titled “Why use Nylas for threads instead of IMAP directly?”

Building a conversation view from raw IMAP requires parsing In-Reply-To and References headers, grouping messages by conversation chain, and maintaining your own thread index. On top of that, iCloud’s biggest friction point is the app-specific password requirement, which can’t be generated programmatically. Every user must create one manually through their Apple ID settings.

Nylas wraps all of that in a REST API. The Threads API returns pre-grouped conversations with metadata, and Nylas guides users through the app password flow during authentication. Your code works across iCloud, Gmail, Outlook, Yahoo, and every other provider without modification.

You’ll need:

  • A Nylas application with a valid API key
  • A grant for an iCloud Mail account
  • An iCloud connector configured in your Nylas application

New to Nylas? Start with the quickstart guide to set up your app and connect a test account before continuing here.

iCloud requires app-specific passwords for third-party email access. Unlike Google or Microsoft OAuth, there’s no way to generate these programmatically. Each user must create one manually in their Apple ID settings.

With either Hosted OAuth or BYO Authentication, users need to:

  1. Go to appleid.apple.com and sign in
  2. Navigate to Sign-In and Security then App-Specific Passwords
  3. Generate a new app password
  4. Use that password (not their regular iCloud password) when authenticating

App-specific passwords can’t be generated via API. Your app’s onboarding flow should include clear instructions telling users how to create one. Users who enter their regular iCloud password will fail authentication.

The full setup walkthrough is in the iCloud provider guide and the app passwords guide.

Make a List Threads request with the grant ID. By default, Nylas returns the most recent threads. These examples limit results to 5:

The response includes a latest_draft_or_message object with the most recent message’s content. The same code works for Google, Microsoft, and Yahoo accounts.

You can narrow results with query parameters. Here’s what works with iCloud accounts:

ParameterWhat it doesExample
subjectMatch on subject line?subject=Weekly standup
fromFilter by sender[email protected]
toFilter by recipient[email protected]
unreadUnread only?unread=true
inFilter by folder or label ID?in=INBOX
received_afterAfter a Unix timestamp?received_after=1706000000
received_beforeBefore a Unix timestamp?received_before=1706100000
has_attachmentOnly results with attachments?has_attachment=true

Here’s how to combine filters. This pulls threads with unread messages from a specific sender:

iCloud supports the search_query_native parameter for IMAP-style search. Like Yahoo and other IMAP providers, iCloud lets you combine search_query_native with any other query parameter.

Some IMAP providers don’t fully support the SEARCH operator. If search_query_native returns unexpected results or a 400 error, fall back to standard query parameters like subject, from, and to instead.

See the search best practices guide for more on search_query_native across providers.

iCloud is IMAP-based with no native threading concept, which means it shares some behaviors with Yahoo and other IMAP providers. But Apple has its own quirks too.

iCloud doesn’t assign a thread ID or conversation identifier to messages. Nylas builds threads by analyzing In-Reply-To and References headers, combined with subject-line matching. This works well for standard reply chains but is less precise than Gmail’s native threading.

Edge cases to be aware of:

  • Forwarded messages may or may not join the original thread, depending on whether the mail client preserved the References header
  • Apple Mail’s threading behavior in the UI is based on its own local algorithm that may differ slightly from what Nylas constructs server-side
  • Messages without proper headers (from older clients) may not group correctly

Nylas maintains a rolling cache of messages from the last 90 days for IMAP-based providers. Threads are built from cached messages, so conversations that span beyond 90 days may appear incomplete. The message_ids array only includes messages within the cache window.

To access older messages directly (not as threads), use query_imap=true on the Messages API. The Threads API does not support query_imap.

Thread-level fields are computed from all cached messages in the conversation:

  • unread is true if any message in the thread is unread
  • starred is true if any message is starred
  • has_attachments is true if any message has attachments
  • participants is the union of all senders and recipients
  • earliest_message_date reflects the oldest cached message, not necessarily the start of the conversation

Nylas monitors the Inbox and Sent folders with low-bandwidth IMAP idle connections. Other folders are checked periodically. This means thread updates from Inbox activity appear quickly (within seconds), while threads with messages only in custom folders may take a few minutes to update.

You can authenticate iCloud accounts two ways:

MethodProvider typeWhat you get
iCloud connectoricloudEmail via IMAP plus calendar via CalDAV
Generic IMAPimapEmail only, no calendar access

Both provide full Threads API support. Use the dedicated iCloud connector if your app needs calendar access alongside email.

The Threads API returns paginated responses. When there are more results, the response includes a next_cursor value. Pass it back as page_token to get the next page:

Keep paginating until the response comes back without a next_cursor.