Apple’s connection instructions for iCloud Mail fit on one support page: an IMAP server at imap.mail.me.com on port 993, an SMTP server at smtp.mail.me.com on port 587, and a note to use an app-specific password. There’s no REST endpoint and no mail SDK. This page covers what those settings mean for an integration and how the Nylas Email API turns an iCloud inbox into JSON.
Does Apple have an iCloud Mail API?
Section titled “Does Apple have an iCloud Mail API?”No. Apple publishes no REST or HTTP API for iCloud Mail. Programmatic access uses the standard IMAP protocol for reading and SMTP for sending, authenticated with an app-specific password the user creates by hand. The Nylas Email API connects to those servers for you and returns iCloud messages in the same JSON schema it uses across 6 providers.
Apple states on its iCloud Mail server settings page that iCloud Mail uses the IMAP and SMTP standards and doesn’t support Post Office Protocol (POP). Everything else you’d expect from a mail API, including JSON responses, change notifications, and MIME parsing, is left to your code or to a service in front of IMAP. A search for “iCloud API” leads to the same answer for mail: the protocol is the API.
What are the iCloud Mail IMAP and SMTP settings?
Section titled “What are the iCloud Mail IMAP and SMTP settings?”iCloud Mail uses 2 servers. Incoming mail is on imap.mail.me.com, port 993, with SSL required. Outgoing mail is on smtp.mail.me.com, port 587, with SSL required and SMTP authentication on. Both take an app-specific password, never the user’s main Apple Account password.
| Setting | Incoming (IMAP) | Outgoing (SMTP) |
|---|---|---|
| Server | imap.mail.me.com | smtp.mail.me.com |
| Port | 993 | 587 |
| Security | SSL required (Apple suggests TLS if SSL errors) | SSL required (TLS or STARTTLS if SSL errors) |
| Username | Usually the name part of the address, or the full address | The full iCloud Mail address |
| Password | App-specific password | The same app-specific password |
Values come from Apple’s iCloud Mail server settings for other email client apps. The username rule differs between the two servers, which is a common cause of failed logins in hand-rolled IMAP clients. When you connect through Nylas, you don’t configure any of these values. The iCloud connector already knows them.
How do app-specific passwords work for iCloud Mail?
Section titled “How do app-specific passwords work for iCloud Mail?”An app-specific password is a separate credential that lets a third-party app sign in to an Apple Account without seeing the main password. Users generate one at account.apple.com under Sign-In and Security. The account must have two-factor authentication, and Apple allows up to 25 active app-specific passwords per account.
Two details from Apple’s app-specific password instructions matter for production apps:
- Password resets revoke everything. When a user changes or resets their main Apple Account password, Apple revokes all of their app-specific passwords. Your grant stops working until the user creates a new one and reconnects.
- Revocation signs the app out. A user can remove a single password or revoke all of them. The app using that password is signed out of the account.
Apple’s page also mentions that some supported third-party apps can authorize with an Apple Account instead, but an iCloud grant uses the app-specific password. Apple doesn’t offer an API for creating these passwords, so this step stays in your onboarding UI. Nylas accepts the password through Hosted authentication or Bring Your Own Authentication with "provider": "icloud", as described in the iCloud provider guide and the app passwords guide. Subscribe to grant.expired notifications so your app can prompt the user to reconnect after a password is revoked.
iCloud IMAP vs the Nylas Email API
Section titled “iCloud IMAP vs the Nylas Email API”Building directly on iCloud IMAP means owning the connection, the parser, and the sync state for every user. The table compares that work with the Nylas Email API, which reads iCloud through the same GET /v3/grants/{grant_id}/messages endpoint you call for Gmail and Outlook. List requests return 50 messages by default and up to 200 per page.
| Task | iCloud IMAP and SMTP (direct) | Nylas Email API |
|---|---|---|
| Response format | Raw RFC 822 messages you parse yourself | JSON message objects with parsed headers and bodies |
| Reading mail | IMAP FETCH and SEARCH per folder | GET /v3/grants/{grant_id}/messages with filters |
| Sending mail | Separate SMTP session on port 587 | POST /v3/grants/{grant_id}/messages/send |
| Conversations | You group by In-Reply-To and References | GET /v3/grants/{grant_id}/threads |
| Change detection | Long-lived IMAP connections or polling | Webhooks for messages in the 90-day cache |
| Other providers | iCloud only | Google, Microsoft, Yahoo, iCloud, IMAP, and Exchange |
If iCloud is the only mailbox you’ll ever support and you already maintain an IMAP stack, connecting directly is reasonable. For anything multi-provider, the per-provider branching is what the unified API removes.
How do I read an iCloud inbox as JSON?
Section titled “How do I read an iCloud inbox as JSON?”Reading an iCloud inbox through Nylas takes one GET request to /v3/grants/{grant_id}/messages with an iCloud grant ID. The response is a JSON array of the most recent messages with sender, recipients, subject, snippet, folder IDs, and body. The default page size is 50 messages; the curl, Node.js, and Python samples set limit=5 to keep the output short.
Use a grant from the dedicated iCloud connector rather than a generic IMAP grant. Both read mail, but only the iCloud connector also provides iCloud Calendar and CardDAV contacts on the same grant.
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'
# Initialize Nylas clientnylas = Nylas::Client.new( api_key: "<NYLAS_API_KEY>")
messages, _ = nylas.messages.list(identifier: "<NYLAS_GRANT_ID>")messages.each {|message| puts "[#{Time.at(message[:date]).strftime("%d/%m/%Y at %H:%M:%S")}] | \#{message[:id]} | \#{message[:subject]} | \#{message[:folders]}"}import com.nylas.NylasClient;import com.nylas.models.*;import java.text.SimpleDateFormat;
public class ReadEmail { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build(); ListResponse<Message> message = nylas.messages().list("<NYLAS_GRANT_ID>");
for(Message email : message.getData()){ String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"). format(new java.util.Date((email.getDate() == null ? 1 : 1000L)));
System.out.println(email.getId() + "[" + date + "] | " + email.getSubject() + " | " + email.getFolders()); } }}import com.nylas.NylasClientimport com.nylas.models.*import java.text.SimpleDateFormatimport java.util.*
fun dateFormatter(milliseconds: String): String { return SimpleDateFormat("dd/MM/yyyy HH:mm:ss"). format(Date(milliseconds.toLong() * 1000)). toString()}
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val messages : List<Message> = nylas.messages().list("<NYLAS_GRANT_ID>").data
for(message in messages){ println("[${message.id}] | " + "${message.subject} | " + "${message.folders}") }}The list iCloud messages recipe covers filters, search_query_native, folder names, and pagination. The list iCloud threads recipe covers grouped conversations.
How far back can you read iCloud mail?
Section titled “How far back can you read iCloud mail?”Nylas caches iCloud messages for 90 days after they’re received or created, and list requests read from that cache by default. To reach older mail, set query_imap=true along with the in folder parameter. The API then queries the iCloud IMAP server directly instead of the cache.
The query_imap parameter works on Get Message, List Messages, Get Draft, List Drafts, and the Attachments endpoints. Direct IMAP queries are slower than cached reads, so use them for backfills and on-demand lookups of older mail rather than for every inbox refresh.
Webhook notifications follow the same 90-day boundary. Nylas doesn’t send notifications for changes to messages older than 90 days, so if a user moves or deletes a two-year-old message, your app learns about it only on the next direct read. The iCloud provider guide lists the full set of cache rules.
What limits does Apple put on iCloud Mail sending?
Section titled “What limits does Apple put on iCloud Mail sending?”Apple caps iCloud Mail at 1,000 messages per day, 1,000 recipients per day, and 500 recipients per message, with a 20 MB message size limit. These limits apply to the user’s account no matter which client sends the message, so they also apply to messages your app sends through a Nylas grant.
Apple describes iCloud Mail as designed primarily for personal use in its mailbox size and message sending limits article. When an account exceeds a limit, Apple shows a sending-limit error and the message isn’t sent. Plan for this in any feature that sends on a user’s behalf: track daily volume per grant, and surface a clear error instead of retrying in a loop. For application-originated mail such as password resets, transactional send from a verified domain doesn’t depend on a user’s personal mailbox.
What’s next
Section titled “What’s next”- List iCloud email messages for filters, native search, and pagination
- List iCloud email threads for header-based conversation grouping
- iCloud provider guide for connector setup and both authentication flows
- App passwords guide for the user-facing password steps
- Apple Calendar API for iCloud Calendar over CalDAV
- Apple Contacts API for iCloud contacts over CardDAV