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.
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. Every user must log in to their Apple ID, navigate to the security settings, and manually create a password. On top of that, you’d need to build the same IMAP 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
New to Nylas? Start with the quickstart guide to set up your app and connect a test account before continuing here.
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
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.
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 --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=5" \ --header 'Accept: application/json, application/gzip' \ --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"}app.get("/nylas/recent-emails", async (req, res) => { try { const identifier = process.env.USER_GRANT_ID; const messages = await nylas.messages.list({ identifier, queryParams: { limit: 5, }, });
res.json(messages); } catch (error) { console.error("Error fetching emails:", error); }});from dotenv import load_dotenvload_dotenv()
import osimport sysfrom nylas import Client
nylas = Client( os.environ.get('NYLAS_API_KEY'), os.environ.get('NYLAS_API_URI'))
grant_id = os.environ.get("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}") }}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 messages 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 messages 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 and other IMAP providers, iCloud lets you combine search_query_native with any other query parameter.
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, })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.
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 does not 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 via IMAP plus calendar via 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”- 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