Maton

iCloud Mail

Access iCloud Mail over IMAP and SMTP with an app password. Read, send, and manage messages, drafts, threads, and folders.

Reference

iCloud Mail has no REST API. The maton icloud-mail commands speak IMAP and SMTP, handling quoting, folder-name encoding, threading headers and the Sent copy, and checking that a write lands on the right message. Connect a mailbox with maton connection create icloud-mail and an app-specific password from account.apple.com (two-factor authentication must be on). iCloud Mail is reached through the Maton bridge, which runs on your own computer on a home or office network.

Message ID

  • A message ID is <folder>/<uid>, e.g. INBOX/{uid} or Work/Clients/{uid}; it is split at the last /, so nested folders work. Quote IDs whose folder has a space: "Sent Messages/{uid}".
  • The ID changes when the message moves. iCloud has no server-side message ID, and a UID only means something within its folder. trash, untrash and modify --move-to print the new ID; use that one from then on. Renaming a folder changes the IDs of the messages in it.
  • A stale ID is an error, never a silent no-op: message … not found.
  • Thread IDs are a Message-ID. iCloud assigns none, so a thread ID is the Message-ID of the conversation's first message, as replies name it in their References header. It is in the THREAD column of message list and thread_id in message get --json. thread get also accepts the message_id of any message in the conversation.
  • message_id in JSON is the Message-ID header, written by whoever sent the message. It is not guaranteed unique, and some messages have none.

Bridges

maton bridge list
maton bridge install
maton bridge status

A bridge on a cloud or hosting network is refused. If there are multiple bridges, specify which one to use so requests route through the intended bridge with --bridge {bridgeId}. Without --bridge, requests use the oldest connected bridge with room, then the next.

Get Mailbox

maton icloud-mail whoami

List Folders

maton icloud-mail folder list

--folder takes NAME (eg. INBOX, Sent Messages, Drafts, Deleted Messages, Junk, Archive, Work/Clients). iCloud flags only Sent Messages and Deleted Messages so the CLI recognises the others by name.

Create Folder

maton icloud-mail folder create Receipts
maton icloud-mail folder create Work/Clients

Rename Folder

maton icloud-mail folder rename Receipts "Receipts 2026"

INBOX and the special folders can't be renamed. Renaming changes the IDs of the messages inside.

Delete Folder

maton icloud-mail folder delete "Receipts 2026"

Permanently deletes the folder and all its messages. Messages skip Trash and can't be recovered. INBOX and special folders can't be deleted.

List Messages

maton icloud-mail message list

Returns up to 20 unread INBOX messages, newest first. Listing never marks anything read.

All messages:

maton icloud-mail message list --all -L 50

Another folder:

maton icloud-mail message list --folder 'Sent Messages' --all

With filters (all must match):

maton icloud-mail message list --all --from boss@example.com --since 2026-10-01
maton icloud-mail message list --all --subject invoice --before 2026-10-15
maton icloud-mail message list --all --text "quarterly report"

--from, --to, --subject and --text match text anywhere in the field (--text covers headers and body); --since and --before take YYYY-MM-DD. There is no next-page cursor: -L/--max caps the results, so narrow with filters instead of paging.

Get Message

maton icloud-mail message get {messageId}
maton icloud-mail message get {messageId} --headers
maton icloud-mail message get {messageId} --raw

Prints the plaintext body without marking the message read. --headers adds From/To/Cc/Subject/Date, --html prints the HTML part, --raw the original source. --json adds the folder, flags, message_id and thread_id.

Send Message

maton icloud-mail message send --to alice@example.com --subject "Hello" --body "Hi there!"
maton icloud-mail message send --to alice@example.com --cc bob@example.com --subject "Report" --body "Attached." -a report.pdf

iCloud Mail keeps no copy of what it sends so the CLI saves one in Sent Messages and prints its ID (sent_id in JSON, alongside message_id). --html sends --body as HTML. reply, reply-all, forward and draft send work the same way.

Reply to Message

maton icloud-mail message reply {messageId} --body "Thanks!"
maton icloud-mail message reply {messageId} --body "Draft reply" --draft

The reply goes to the sender (or their Reply-To address, if set) and includes the threading headers and the quoted original so mail apps keep the conversation together. --draft saves the reply in Drafts instead of sending it and prints its draft ID.

Reply All to Message

maton icloud-mail message reply-all {messageId} --body "Sounds good"
maton icloud-mail message reply-all {messageId} --body "Updated" --remove bob@example.com

The reply goes to everyone in To and Cc, except your own address and any address passed with --remove. When replying to a message you sent, it goes to that message's original recipients.

Forward Message

maton icloud-mail message forward {messageId} --to dave@example.com --body "FYI"

The original's header block and body are included. Its attachments are not re-attached; attach files with -a.

Modify Message

maton icloud-mail message modify {messageId} --read
maton icloud-mail message modify {messageId} --unread --flag
maton icloud-mail message modify {messageId} --move-to Archive

IMAP uses flags and folders rather than labels. The message gets a new ID in its new folder; the output prints it.

Trash / Untrash Message

maton icloud-mail message trash {messageId}
maton icloud-mail message untrash {messageId}
maton icloud-mail message untrash {messageId} --to Archive

trash moves the message to Deleted Messages where it stays until the provider empties it. untrash moves it back to INBOX or to --to if given.

Create Draft

maton icloud-mail draft create --to alice@example.com --subject "Hello" --body "Draft body"

List Drafts

maton icloud-mail draft list

Get Draft

maton icloud-mail draft get {draftId}

Send Draft

maton icloud-mail draft send {draftId}

Sends the draft as saved, then deletes it from Drafts. BCC recipients receive it but aren't shown in the delivered message's headers. iCloud Mail keeps no copy of what it sends so the CLI saves one in Sent Messages and prints its ID (sent_id in JSON, alongside message_id).

Delete Draft

maton icloud-mail draft delete {draftId}

Permanently deletes the draft; it doesn't go to Trash.

Get Thread

maton icloud-mail thread get {threadId}
maton icloud-mail thread get {threadId} --json

Lists every message in the conversation across all folders including Sent and Trash, oldest first, as summaries; use message get for bodies.

Raw IMAP / SMTP

POST /icloud-mail/commands
Content-Type: application/json

{"commands": "<base64 of the IMAP or SMTP stream>"}
stream=$(printf 'M1 EXAMINE INBOX\r\nM2 UID SEARCH UNSEEN\r\n' | base64)
maton api -X POST '/icloud-mail/commands' -f commands="$stream" -q '.data' | base64 --decode

The request body is exactly {"commands": "<base64 of the stream>"} (any other field is refused), and the response is {"data": "<base64 of the server's reply>"}. A stream whose first word is an SMTP verb (EHLO, MAIL, …) goes to SMTP; anything else goes to IMAP. None of the commands' safeguards apply:

  • You own the protocol. Every IMAP command needs a unique tag; strings need quoting or literals (use {n+}); folder names with non-ASCII characters must be in modified UTF-7 (Café is Caf&AOk-); every line ends with CRLF, and a bare CR or LF is refused.
  • No folder is selected when a stream starts. SELECT or EXAMINE first, in the same stream.
  • Writes to a missing UID succeed silently. Check with UID FETCH <uid> (UID) in the same stream, and use UID EXPUNGE <uid>, never plain EXPUNGE, which removes every message marked deleted in the folder.
  • Fetching with BODY[] marks a message read. Use BODY.PEEK[].
  • EXAMINE is read-write on iCloud: a plain FETCH … BODY[] marks the message read even in an examined folder.
  • Keep the session reusable. LOGIN, AUTHENTICATE, LOGOUT, ENABLE, IDLE, NOTIFY, COMPRESS, CONDSTORE/QRESYNC options, synchronizing literals {n}, and SEARCH … RETURN options other than MIN, MAX, COUNT and ALL make Maton discard the session afterwards, so the next request logs in again, which is slower and counts against the login limits.
  • iCloud's SMTP server files no Sent copy. APPEND one to Sent Messages yourself, with the same Message-ID.
  • Choosing a bridge: with more than one, -H 'Maton-Bridge: <bridge_id>' (an ID from maton bridge list) pins the request to that computer.

Notes

  • Removal actions require review. folder delete and draft delete are permanent; list or get the target first so you can verify it.
  • Reading never marks mail read. message list, message get and thread get leave flags alone; mark a message with message modify --read.
  • --since and --before work by day, on the date the server received the message; the time of day is ignored.
  • message_id doesn't identify a message. The Message-ID header is written by the sending software: it can be missing, repeated, or copied from another message, and both copies of a message sent to yourself carry the same one. Act on messages by id; use message_id only with thread get.
  • iCloud needs the Maton bridge for reading and sending: Apple refuses mail from cloud servers, so Maton connects through the bridge on your own computer. While that computer sleeps, the connection shows PENDING (bridge_disconnected) and recovers by itself.
  • Moves change the ID. UIDs are per folder, so trash, untrash and modify --move-to give the message a new ID; use the one printed. (iCloud doesn't advertise IMAP MOVE, so the CLI copies the message and expunges the original.)
  • Sent copies are written by the CLI. iCloud keeps no copy of what it sends, so the CLI saves one in Sent Messages. Bcc recipients appear only in that copy. If saving it fails, the message was still sent and the CLI says so: don't send it again.
  • Threads come from reply headers. A thread ID is the Message-ID of the conversation's first message, found through References, else In-Reply-To, else the message's own; it matches how iCloud Mail on the web groups conversations, never by subject. A message without a valid Message-ID gets no thread_id, and replies to it start a new thread.
  • A thread ID may belong to no stored message. When a conversation started from Yahoo, Yahoo's server added a References entry like <id.ref@yahoo.com>, which becomes the thread ID. thread get on it, or on any message's message_id, still returns the whole thread.
  • thread get can miss a deep branch: a reply that carries only In-Reply-To, two or more replies down, isn't found from the root. It is found from its own branch's message_id.
  • Self-sent mail appears twice in thread get and searches: the delivered copy in INBOX and the CLI's copy in Sent Messages, with the same Message-ID.
  • Forwards stay in the thread: forward sets References (but not In-Reply-To) to the original.
  • draft send fills in missing headers. A draft without a Message-ID or Date gets them before sending, so the delivered copy and the Sent copy match.
  • Large messages are slow through the bridge, at about your upload speed (around 1 MB/s on a typical connection). Fetch big messages one at a time.
  • Folder names: non-ASCII names (Café) are converted to and from IMAP's encoding, and names with control characters are refused. Work/Clients can be created without creating Work first.
  • Retry reads, check writes. After a 500, 502 or 504, a send or change may already have happened: check the folder (and Sent) before repeating it. 429 and 503 changed nothing; retry after Retry-After.

Rate Limits

  • Requests: 10 per second per Maton account, with bursts up to 50.
  • Mail requests in progress: at most 4 at a time per Maton account, across all of its mailboxes; more get 429 for a second.
  • Per mailbox: 4 requests in flight. More workers only add 429s: iCloud reaches the account's request rate at about 4 at a time.
  • Logins per mailbox: 4 IMAP logins per 8 s and 10 SMTP logins per 20 s; each send logs in to SMTP. Maton reuses IMAP sessions, so this is only reached by reconnect-heavy raw streams or rapid sends.
  • Sending: 1000 messages and 1000 recipients per mailbox per day, across all its connections, reset at 00:00 UTC (Apple's published limit for iCloud Mail). A send over the allowance gets 429 before anything goes out.
  • Failed logins: a refused app password is retried only after a pause (5 minutes, doubling up to 6 hours).
  • Each bridge carries up to 8 IMAP and 4 SMTP connections at once, shared by all of the account's iCloud mailboxes; when full it answers 503 for a few seconds.

Resources

On this page