Apple doesn’t offer a public email API for iCloud Mail. There’s no REST interface, no SDK, and no developer portal for mail. The only programmatic access is raw IMAP, and even that requires each user to manually create an app-specific password through their Apple ID settings. Nylas handles all of that and exposes iCloud through the same Messages API you use for Gmail and Outlook.
This guide covers listing messages from iCloud accounts, including the app-specific password requirement, the 90-day message cache, and iCloud-specific behaviors you should know about.
Is there an iCloud Mail API?
Section titled “Is there an iCloud Mail API?”No. Apple publishes no public REST API or SDK for iCloud Mail, and there’s no developer portal for it. The only programmatic access is IMAP for reading and SMTP for sending, and each user must generate a 16-character app-specific password from their Apple ID account page before any client can connect.
That constraint is why most iCloud integrations go through a layer that wraps IMAP. Reading mail still happens over IMAP, which exposes the last 90 days through the message cache, with older messages reachable on demand. This guide uses a unified REST layer so the same call that lists Gmail or Outlook messages also lists iCloud mail across all 6 providers.
How do I list iCloud Mail messages with the Nylas API?
Section titled “How do I list iCloud Mail messages with the Nylas API?”Send a GET request to /v3/grants/{grant_id}/messages using the iCloud connector and your API key. The endpoint returns the 50 most recent messages by default and up to 200 per page. iCloud is IMAP-backed, so only messages within the 90-day sync cache are searchable server-side. See List messages for the request and response.
Why use Nylas instead of IMAP directly?
Section titled “Why use Nylas instead of IMAP directly?”iCloud’s biggest friction point for developers is the app-specific password requirement. There’s no way to generate these programmatically, so every user must log in to their Apple ID, open the security settings, and manually create a password before your app can connect. On top of that, you carry the full IMAP burden.
That means building the same infrastructure as any other IMAP integration: connection management, MIME parsing, sync state tracking, and a separate SMTP connection for sending.
Nylas handles the IMAP connection and guides users through the app password flow during authentication. You get a REST API with JSON responses, automatic sync and caching, and the same code works across iCloud, Gmail, Outlook, Yahoo, and every other provider.
If you’re comfortable with raw IMAP and only targeting iCloud, you can connect directly. For multi-provider apps or faster development, Nylas saves you significant time.
Before you begin
Section titled “Before you begin”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
iCloud authentication setup
Section titled “iCloud authentication setup”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.
Nylas supports two authentication flows for iCloud:
| Method | Best for |
|---|---|
| Hosted OAuth | Production apps where Nylas guides users through the app password flow |
| Bring Your Own (BYO) Authentication | Custom auth pages where you collect credentials directly |
With either method, users need to:
- Go to appleid.apple.com and sign in
- Navigate to Sign-In and Security then App-Specific Passwords
- Generate a new app password
- Use that password (not their regular iCloud password) when authenticating
The full setup walkthrough is in the iCloud provider guide and the app passwords guide.
List messages
Section titled “List messages”Make a List Messages request with the grant ID. By default, Nylas returns the 50 most recent messages. These examples limit results to 5:
curl --compressed --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=5" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'{ "request_id": "d0c951b9-61db-4daa-ab19-cd44afeeabac", "data": [ { "starred": false, "unread": true, "folders": ["UNREAD", "CATEGORY_PERSONAL", "INBOX"], "grant_id": "1", "date": 1706811644, "attachments": [ { "id": "1", "grant_id": "1", "filename": "invite.ics", "size": 2504, "content_type": "text/calendar; charset=\"UTF-8\"; method=REQUEST" }, { "id": "2", "grant_id": "1", "filename": "invite.ics", "size": 2504, "content_type": "application/ics; name=\"invite.ics\"", "is_inline": false, "content_disposition": "attachment; filename=\"invite.ics\"" } ], "from": [ { "name": "Nylas DevRel", } ], "id": "1", "object": "message", "snippet": "Send Email with Nylas APIs", "subject": "Learn how to Send Email with Nylas APIs", "thread_id": "1", "to": [ { "name": "Nyla", } ], "created_at": 1706811644, "body": "Learn how to send emails using the Nylas APIs!" } ], "next_cursor": "123"}import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function fetchRecentEmails() { try { const messages = await nylas.messages.list({ identifier: "<NYLAS_GRANT_ID>", queryParams: { limit: 5, }, });
console.log("Messages:", messages); } catch (error) { console.error("Error fetching emails:", error); }}
fetchRecentEmails();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"
messages = nylas.messages.list( grant_id, query_params={ "limit": 5 })
print(messages)require 'nylas'
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')query_params = { limit: 5 }messages, _ = nylas.messages.list(identifier: '<NYLAS_GRANT_ID>', query_params: query_params)
messages.each {|message| puts "[#{Time.at(message[:date]).strftime("%d/%m/%Y at %H:%M:%S")}] \ #{message[:subject]}"}import com.nylas.NylasClient;import com.nylas.models.*;import java.text.SimpleDateFormat;
public class ListMessages { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build(); ListMessagesQueryParams queryParams = new ListMessagesQueryParams.Builder().limit(5).build(); ListResponse<Message> message = nylas.messages().list("<NYLAS_GRANT_ID>", queryParams);
for(Message email : message.getData()) { String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"). format(new java.util.Date((email.getDate() * 1000L)));
System.out.println("[" + date + "] | " + email.getSubject()); } }}import com.nylas.NylasClientimport com.nylas.models.*import java.text.SimpleDateFormatimport java.util.*
fun main(args: Array<String>) { val nylas = NylasClient(apiKey = "<NYLAS_API_KEY>") val queryParams = ListMessagesQueryParams(limit = 5) val messages = nylas.messages().list("<NYLAS_GRANT_ID>", queryParams).data
for (message in messages) { val date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(Date(message.date.toLong() * 1000)) println("[$date] | ${message.subject}") }}List iCloud messages from the terminal
Section titled “List iCloud messages from the terminal”Apple ships no email API, so iCloud Mail is IMAP-only and every user must create a 16-character app-specific password before any client connects. Once that grant exists, the Nylas CLI reads the mailbox like any other connected account, with no IMAP client code on your side. The nylas email list command pulls the inbox straight from your terminal.
The Nylas CLI mirrors the Messages API, so you can read the same inbox from your terminal without writing any code. After nylas init and nylas auth login, the email list command returns the 10 most recent inbox messages by default and pages through everything automatically once --limit goes over 200:
# List the 10 most recent inbox messagesnylas email list
# Show only unread messages from a specific sender
# Fetch everything across all folders, paginated automaticallynylas email list --all --all-folders --max 500To find specific messages rather than list them, email search runs a full-text query with its own filters for sender (--from), date range (--after and --before), and attachments (--has-attachment). Search returns 20 results by default and only fetches more than one page once --limit goes over 200:
# Full-text search restricted to one sender, attachments onlyBoth commands accept --json, so you can pipe results into jq or a script. See the email list and email search command reference for every flag.
iCloud folder names aren’t fixed. They vary by account and can be locale-specific, so a German account stores trash under Papierkorb rather than Deleted Messages. The Apple Mail display name also won’t always match the IMAP folder on the server. List each account’s real names first, then read one by name:
# Folder names vary per account, so list the real ones firstnylas email folders list
# Read a folder by its actual iCloud namenylas email list --folder "Sent Messages"iCloud keeps roughly 90 days of mail searchable through the cache. iCloud sending runs through the same CLI. See Send email from the terminal for the send commands.
Filter messages
Section titled “Filter messages”You can narrow results with query parameters. Here’s what works with iCloud accounts:
| Parameter | What it does | Example |
|---|---|---|
subject | Match on subject line | ?subject=Weekly standup |
from | Filter by sender | [email protected] |
to | Filter by recipient | [email protected] |
unread | Unread only | ?unread=true |
in | Filter by folder or label ID | ?in=INBOX |
received_after | After a Unix timestamp | ?received_after=1706000000 |
received_before | Before a Unix timestamp | ?received_before=1706100000 |
has_attachment | Only results with attachments | ?has_attachment=true |
Here’s how to combine filters. This pulls unread messages from a specific sender:
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/[email protected]&unread=true&limit=10" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const messages = await nylas.messages.list({ identifier: grantId, queryParams: { unread: true, limit: 10, },});messages = nylas.messages.list( grant_id, query_params={ "unread": True, "limit": 10, })Search with search_query_native
Section titled “Search with search_query_native”iCloud supports the search_query_native parameter for IMAP-style search. Like Yahoo, iCloud isn’t subject to the in, limit, and page_token co-parameter restriction that Google and Microsoft enforce, so you can pair it with standard filters like from and subject.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?search_query_native=subject:invoice&limit=10" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const messages = await nylas.messages.list({ identifier: grantId, queryParams: { searchQueryNative: "subject:invoice", limit: 10, },});messages = nylas.messages.list( grant_id, query_params={ "search_query_native": "subject:invoice", "limit": 10, })See the search best practices guide for more on search_query_native across providers.
Things to know about iCloud
Section titled “Things to know about iCloud”iCloud is IMAP-based, which means it shares some behaviors with Yahoo and other IMAP providers. But Apple has its own quirks too.
The 90-day message cache
Section titled “The 90-day message cache”Nylas maintains a rolling cache of messages from the last 90 days for all IMAP-based providers, including iCloud. Anything received or created within that window is synced and available through the API. For older messages, set query_imap=true to query iCloud’s IMAP server directly. This is slower but gives you access to the full mailbox.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?query_imap=true&in=INBOX&limit=10" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const messages = await nylas.messages.list({ identifier: grantId, queryParams: { queryImap: true, in: "INBOX", limit: 10, },});messages = nylas.messages.list( grant_id, query_params={ "query_imap": True, "in": "INBOX", "limit": 10, })When using query_imap, you must include the in parameter to specify which folder to search.
Webhooks don’t cover old messages
Section titled “Webhooks don’t cover old messages”Nylas webhooks only fire for changes to messages within the 90-day cache window. If a user modifies or deletes a message older than 90 days, you won’t receive a notification. Plan your sync strategy accordingly if your app needs to track changes across the full mailbox.
iCloud folder names
Section titled “iCloud folder names”iCloud uses standard IMAP folder names, but the exact names can vary. Some accounts use locale-specific names (for example, “Papierkorb” instead of “Trash” on German accounts), and the display name in Apple Mail may not always match the IMAP folder name on the server.
Common folder names on English-language accounts:
| Apple Mail UI name | Folder name |
|---|---|
| Inbox | INBOX |
| Sent | Sent Messages |
| Drafts | Drafts |
| Trash | Deleted Messages |
| Junk | Junk |
| Archive | Archive |
Don’t hardcode these. Always use the List Folders endpoint to discover the exact folder names for each account.
Sending rate limits
Section titled “Sending rate limits”Apple publishes specific sending limits for iCloud Mail:
| Limit | Value |
|---|---|
| Messages per day | 1,000 |
| Recipients per day | 1,000 |
| Recipients per message | 500 |
| Max message size | 20 MB |
These are sending limits, not read limits. Nylas handles retries if you hit throttling on reads, but if your app sends email through iCloud accounts, keep these limits in mind. Source: Apple support documentation.
No unsubscribe headers
Section titled “No unsubscribe headers”iCloud doesn’t support List-Unsubscribe-Post or List-Unsubscribe custom headers when sending email. If your app sends subscription-style email through iCloud accounts, you’ll need to handle unsubscribe links in the message body instead. This limitation is shared with some Microsoft Graph accounts.
Contacts are disabled by default
Section titled “Contacts are disabled by default”The Contacts API is disabled by default for iCloud grants. If your app needs to access iCloud contacts, contact Nylas Support to enable it. When enabled, contacts are parsed from email headers (From, To, CC, BCC, Reply-To fields) rather than synced from an address book.
Sync and threading
Section titled “Sync and threading”iCloud accounts use IMAP for reading and SMTP for sending. This is invisible to your code, but it affects a few behaviors:
- Sync relies on IMAP idle. Nylas maintains low-bandwidth connections to monitor the Inbox and Sent folders. Other folders are checked periodically, so changes outside Inbox and Sent may take a few minutes to appear.
- Message IDs are IMAP UIDs, which are numeric values unique within a folder but not globally unique across the account. If you need a stable identifier across folders, use the
Message-IDheader. - Thread grouping relies on subject-line and
In-Reply-Toheader matching. This works well for most conversations but isn’t as precise as Gmail’s native threading.
iCloud connector vs. generic IMAP
Section titled “iCloud connector vs. generic IMAP”You can authenticate iCloud accounts two ways in Nylas:
| Method | Provider type | What you get |
|---|---|---|
| iCloud connector | icloud | Email through IMAP plus calendar through CalDAV |
| Generic IMAP | imap | Email only, no calendar access |
Use the dedicated iCloud connector if your app needs calendar access alongside email. The generic IMAP connector works for email-only use cases but doesn’t include CalDAV support.
Paginate through results
Section titled “Paginate through results”The Messages 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:
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=10&page_token=<NEXT_CURSOR>" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'let pageCursor = undefined;
do { const result = await nylas.messages.list({ identifier: grantId, queryParams: { limit: 10, pageToken: pageCursor, }, });
// Process result.data here
pageCursor = result.nextCursor;} while (pageCursor);page_cursor = None
while True: query = {"limit": 10} if page_cursor: query["page_token"] = page_cursor
result = nylas.messages.list(grant_id, query_params=query)
# Process result.data here
page_cursor = result.next_cursor if not page_cursor: breakKeep paginating until the response comes back without a next_cursor.
What’s next
Section titled “What’s next”- List iCloud email threads to group these messages into conversations
- List Gmail messages for the same task on Google accounts
- List Microsoft messages for the same task on Microsoft 365 accounts
- Messages API reference for full endpoint documentation and all available parameters
- Using the Messages API for search, modification, and deletion
- Threads to group related messages into conversations
- Search best practices for advanced search with
search_query_nativeacross providers - Webhooks for real-time notifications instead of polling
- iCloud provider guide for full iCloud setup including authentication
- App passwords guide for generating app-specific passwords for iCloud and other providers
- IMAP provider guide for general IMAP configuration and behavior