iCloud Mail for Codex
iCloud Mail
iCloud Mail gives Codex a local, dependency-free email integration using Apple's documented IMAP and SMTP endpoints. It offers the practical core of the Gmail and Outlook integrations: mailbox counts, structured search, message and conversation reading, attachment access, flags, archive/move/Trash, drafts, replies, forwards, and sending.
Access while your Mac is offline
Version 0.4.0 adds an optional read-only HTTPS server for an approved always-on host. It uses OAuth/OIDC per user, encrypted private credentials, scoped reads, and durable watch checkpoints. Local installation still uses stdio and Keychain. No hosted endpoint is included. A service must be deployed and tested from a cloud runner with the Mac offline before unattended access is available. See the remote operator and private onboarding guide.
Try it in Codex
Choose a skill or use one of these starter requests.
| Skill | What it helps with | Starter request |
|---|---|---|
| iCloud Mail | Read, organize, and draft iCloud email | Summarize important unread iCloud Mail and suggest replies. |
What is different from Gmail and Outlook
iCloud Mail exposes standards-based IMAP/SMTP rather than a public, provider-specific mailbox API. This creates several honest differences:
- Apple documents account authorization for supported third-party apps, but does not publish a general registration/token flow for an arbitrary local Codex plugin. This plugin therefore uses an app-specific password.
- iCloud has folders and IMAP flags, not Gmail's many-to-many label model.
- Search uses structured portable fields, not Gmail search syntax or Microsoft Graph query syntax.
- Conversation grouping is reconstructed from RFC 5322
Message-ID,References, andIn-Reply-Toheaders. It is useful but not equivalent to a provider-issued thread identifier. - Draft and send operations use IMAP and SMTP. SMTP acceptance proves that Apple's server accepted the message, not downstream delivery.
Configure securely
On macOS, select Connect to iCloud Mail on the plugin page. This starts a chat that opens the local setup page. You can also ask Codex:
Connect iCloud Mail.
open_account_setup opens one temporary page in your browser, running only on
this Mac. Enter your iCloud Mail address and Apple app-specific password
there, never in chat.
Do not enter your normal Apple Account password. Open
Apple Account settings, choose
Sign-In and Security → App-Specific Passwords, and generate a password for
Codex iCloud Mail. The Apple Account that owns the mailbox must have two-factor
authentication enabled. Keep the setup page open while creating the password.
The page checks both IMAP and SMTP without sending a message. Only after those checks succeed does it save the credential in macOS Keychain and the non-secret account settings. Reconnecting the same account preserves optional sender settings; choosing a different account resets those settings. A failed validation leaves the existing connection unchanged. If saving cannot be rolled back, the page reports that local connection settings need repair and asks you to reconnect.
The page closes its local service after success or ten minutes. Reopen setup if
it expires. No hosted login service, remote credential broker, or persistent
setup daemon is used. Opening the page is not proof of connection:
get_account_status reports saved settings, while validate_account checks
current authentication. A password revoked at Apple requires reconnecting.
The account address is the primary full iCloud Mail address used to authenticate
SMTP—for example name@icloud.com. It is not necessarily the address used to
sign in to the Apple Account, and it is not an alias. The app-specific password
is generated by the Apple Account that owns that mailbox.
All incoming mail delivered to every iCloud Mail alias is already visible in
the same IMAP mailbox. Aliases therefore require no incoming-mail setting.
Optional aliases in allowed_from control only which addresses the plugin may
use as an outgoing From identity.
Saved non-secret settings live at:
- macOS:
~/Library/Application Support/Codex/iCloud Mail/config.json - Other systems:
${XDG_CONFIG_HOME:-~/.config}/codex/icloud-mail/config.json
The file uses mode 0600; its directory uses mode 0700. Writes are atomic.
It never contains the app-specific password.
Manual setup
The guided page is macOS-only. configure_account remains available for
non-secret settings, including optional outgoing identities. On macOS, use
open_keychain_access if you need to manage the generic-password item manually:
service codex-icloud-mail, account set to the primary full mailbox address.
Enter the password in Keychain Access, never as a command argument or tool input.
The server reads the Keychain item at use time. A saved Keychain credential takes
precedence over ICLOUD_MAIL_APP_PASSWORD in the launch environment. On hosts
without macOS Keychain, that environment variable remains the manual credential
source. Avoid shell history, checked-in .env files, and chat messages. An export
in another shell does not update an already running Codex process.
For IMAP, Apple documents the address's local part (for example, name for
name@icloud.com) as the usual username and the full iCloud Mail address as
the fallback. The plugin tries those forms in that order, using a new TLS
connection for each attempt. Save imap_username only when an account needs
an explicit override. SMTP always authenticates with the full account address.
Storage and privacy
The following retention description applies to local stdio; remote encrypted account/watch storage is described in the remote guide. Local stdio connects to the iCloud-hosted mailbox on demand. It creates no local mail repository: no mailbox replica, search index, message or attachment cache, offline mailbox, or background synchronization. Search runs against iCloud; requested messages, headers, attachments, and drafts are processed in bounded memory. Mail content is not written to diagnostic logs.
Only connection settings, a settings lock file, and the Keychain credential persist locally. Mail content returned to Codex enters the conversation and is subject to Codex's own data handling. The plugin does not control conversation retention or operating-system memory management. Saving a message or attachment to disk requires an explicit user request; there is no automatic export.
This release does not register a native Codex Connected Accounts entry or promise eligibility for Reference my writing style. Those platform integration contracts have not been verified for this local plugin.
Fixed endpoints and optional settings
Defaults follow Apple's published settings:
| Purpose | Host | Port | Security |
|---|---|---|---|
| IMAP | imap.mail.me.com | 993 | TLS |
| SMTP | smtp.mail.me.com | 587 | STARTTLS |
Saved settings:
account_address: primary full iCloud Mail address used for SMTP login.imap_username: optional incoming-login override.default_from: account address or one allowed sender alias.allowed_from: optional aliases permitted for outgoing mail, supplied as an array or comma-delimited list.display_name: optional outgoing display name.
ICLOUD_MAIL_TIMEOUT remains an optional socket timeout of 5–120 seconds;
default 30. Legacy username/display-name environment variables still work when
no saved configuration exists, allowing existing installations to migrate.
Server hosts are deliberately not configurable. This prevents a mailbox credential from being redirected to an arbitrary endpoint.
Supported tools
Read tools:
get_account_statusvalidate_accountlist_mailboxessearch_emailsread_emailread_email_threadread_attachmentlist_drafts
Mutation tools:
open_account_setupconfigure_accountclear_account_configurationopen_apple_password_pageopen_keychain_accessset_email_flagsmove_emailsarchive_emailstrash_emailscreate_draftupdate_draftsend_emailsend_draftforward_emails
No tool permanently deletes or expunges messages.
Paged results
search_emails, list_drafts, list_mailboxes, and read_email_thread return
fixed pages of 10 results. The final page can contain fewer. Each response
includes page_size (10), returned, has_more, and next_page_token (a string
when continuation is available, otherwise JSON null). Existing result arrays
remain emails, mailboxes, and messages respectively.
Omit page_token for the first page. To continue, call the same tool with the
returned next_page_token as page_token, repeating the same search filters or
thread message_id. Stop when has_more is false. Fetch only the pages needed
for the user's request; do not automatically walk the whole mailbox.
For example, the first search is:
{"mailbox": "INBOX", "unread": true}
If its response has has_more: true, the next call is:
{"mailbox": "INBOX", "unread": true, "page_token": "<next_page_token from the response>"}
Treat tokens as opaque, untrusted continuation values. They contain no mail bodies or credentials, require the normal account authentication on every call, and create no local paging state or mail cache. Never edit or fabricate them. Invalid tokens or changed query/mailbox UIDVALIDITY return an error asking you to restart without the token.
Search and draft pages use descending IMAP UID order (newest assigned UID
first, which can differ from the message's Date header). Continuation searches
only older UIDs, so new arrivals do not shift subsequent pages. Filters and
counts are evaluated live; moved, deleted, or newly nonmatching messages may
vanish between calls. matched_before_attachment_filter counts candidates
remaining in the current UID range, not a snapshot total for all pages.
Each call scans at most 80 summaries. Attachment filtering, oversized summaries,
or disappearing messages can produce a short or empty page with has_more: true;
follow its token to continue past the scanned candidates.
Folders sort by name and fetch counts for only the current page. Thread pages sort by parsed Date, then UID and identifier. The requested anchor appears once in that order, on the page containing it. Folder and thread tokens bind to their bounded discovery results: if those results change, restart from the first page. Thread reconstruction remains best effort (up to ten reference searches, 50 summaries per batch, and 80 candidates per search); paging does not make it an exhaustive conversation search. Folder discovery keeps its 1,000-entry and 512 KiB scan bounds.
has_more describes available continuation. truncated independently reports
scan/discovery omissions, skipped oversized summaries, or thread body-processing
limits; has_more: false with truncated: true is not exhaustive coverage.
Thread pages also retain the 2 MiB serialized response budget and can be shorter
than ten; their continuation retries the next message. A single message that
cannot fit returns an error directing callers to read_email or their mail app.
Body truncation flags still apply to each returned message.
Migration from 0.2.x: max_results is removed from these public tools.
Refresh tool discovery, remove that argument, and follow continuation tokens.
Existing account configuration and message identifiers need no data migration.
The marketplace advertises and installs version 0.4.0, pinned to the immutable
source commit containing this release's package. The icloud-mail-v0.4.0 release
tag contains the same package files. Refresh and install through the marketplace:
codex plugin marketplace upgrade sealad886-codex-marketplace
codex plugin add icloud-mail@sealad886-codex-marketplace
Start a fresh Codex chat after installation to load the updated tools and skill. Existing chats can retain the earlier tool schema.
Message reads, including messages returned in threads, cap each body representation
at 100,000 characters. body_text_truncated and body_html_truncated identify
incomplete representations; read the complete message in your mail app when either
flag is true. Inline body sections are included in order, while MIME alternatives
remain separate plain-text and HTML representations.
Forwarding refuses to send when the complete body and note exceed the processing limit. Shorten the note or forward that message in your mail app; the plugin does not silently cut off forwarded text.
When updating a draft, omit attachment_files to preserve its existing
attachments, pass an empty array to remove them, or pass absolute local file
paths to replace them. A JSON null value is rejected so it cannot
accidentally remove attachments.
Reply conversation headers are also preserved unless a nonempty
reply_message_id selects a different reply target.
Opening setup, Apple Account, or Keychain Access requires explicit user intent.
Clearing configuration requires confirm=true and deliberately leaves the
Keychain credential untouched. It does not revoke the password at Apple. Remove
the item in Keychain Access or revoke it in Apple Account settings when wanted.
Validation
From the repository root:
export PYTHONDONTWRITEBYTECODE=1
python3 plugins/icloud-mail/mcp/server.py --self-test
python3 scripts/check_plugin.py plugins/icloud-mail --layout source
python3 scripts/check_distribution_bundle.py plugins/icloud-mail
plugin_system_skills="${HOME}/.codex/skills/.system"
python3 "${plugin_system_skills}/plugin-creator/scripts/validate_plugin.py" plugins/icloud-mail
python3 "${plugin_system_skills}/skill-creator/scripts/quick_validate.py" plugins/icloud-mail/skills/icloud-mail
python3 scripts/check_marketplace.py .
python3 -m unittest discover -s tests -p 'test_*.py' -v
Live mailbox validation requires a separately created test account or explicit authorization to touch a real mailbox. The offline self-test never opens a network connection.