icloud-mail-mcp
README.md
# icloud-mail-mcp
[](https://github.com/nickhalterman/icloud-mail-mcp/actions/workflows/ci.yml)



**Give Claude secure access to your iCloud Mail and Calendar on every device.** A small, serverless [MCP](https://modelcontextprotocol.io) server that lets Claude read, search, organize and draft replies in iCloud Mail, and read your iCloud Calendar. It runs on AWS Lambda for pennies a month and works in Claude chat, the iPhone app and scheduled agents.
Apple doesn't offer a Claude connector for iCloud Mail or Calendar, so I built one.
> "Anything from recruiters this week?" → Claude searches your inbox, reads the relevant threads, and answers.
> "Draft a reply saying Thursday works." → The draft appears in Apple Mail. You review it and hit Send.
> "Trash last week's promos." → Claude lists them, you approve, they're moved to Deleted Messages.
> "What's on my calendar tomorrow?" → Claude reads your iCloud calendars and lists the day.
## How it works
```mermaid
flowchart LR
A[You<br/>iPhone · Mac · web] --> B[Claude]
B -- HTTPS + API key --> C[AWS Lambda<br/>Function URL]
C -- secrets --> D[(SSM Parameter Store)]
C -- IMAP over TLS --> E[iCloud Mail]
C -- CalDAV over TLS, read-only --> G[iCloud Calendar]
E -. drafts sync .-> F[Apple Mail app]
```
Claude calls the server as a [custom connector](https://support.claude.com/en/articles/11175166-about-custom-connectors). Each request spins up a stateless Lambda that checks the API key, loads your iCloud app-specific password from SSM, does its work over IMAP (mail) or CalDAV (calendar), and shuts down. There's no server to maintain.
## Tools
| Tool | What it does | Suggested Claude permission |
|---|---|---|
| `list_mailboxes` | List folders | Always allow |
| `list_recent` | Newest messages from the last N days | Always allow |
| `search_mail` | Filter by text, sender, subject, date, unread | Always allow |
| `read_message` | Full text and attachment list (doesn't mark as read) | Always allow |
| `create_draft` | Save a draft, threaded as a reply if needed. **Never sends.** | Always allow |
| `create_mailbox` | Create a folder | Always allow |
| `set_flags` | Mark read/unread, flag/unflag | Always allow |
| `move_messages` | Move up to 50 messages to another folder | **Needs approval** |
| `trash_messages` | Move up to 50 messages to Deleted Messages | **Needs approval** |
| `list_calendars` | List iCloud calendar names | Always allow |
| `list_events` | Events for a range of days (1-31), repeating events expanded, in your time zone. **Read-only.** | Always allow |
## Security model
This server touches your email, so it's built to fail safe:
- **It can't send email.** There's no SMTP code at all. Drafts wait for you in Apple Mail.
- **It can't change your calendar.** The calendar code only sends CalDAV read requests (`PROPFIND`, `REPORT`). There is no code that creates, edits or deletes events.
- **Your password only goes to Apple.** Calendar requests are refused unless the address is `https://*.icloud.com`.
- **Nothing is permanently deleted.** "Trash" moves mail to Deleted Messages, which iCloud keeps recoverable for 30 days.
- **Blast radius is capped.** Organizing tools act on at most 50 messages per call and report exactly which uids they touched.
- **Reads are read-only.** Mailboxes are opened with `EXAMINE`, and bodies are fetched with `BODY.PEEK`, so reading never changes state.
- **Least-privilege secrets.** Your iCloud app-specific password and the API key live only in SSM as SecureStrings. The Lambda's IAM role can read only its own parameter path. Revoking the app-specific password at Apple cuts access instantly.
- **Locked endpoint.** Every request needs `Authorization: Bearer <key>` (a 64-character random key, checked in constant time).
- **Prompt-injection aware.** The server tells Claude to treat email content as untrusted data, and the destructive tools are meant to sit behind Claude's per-tool approval.
- **Kill switches.** `ALLOW_ORGANIZE=false` makes it read-and-draft only, `ALLOW_DRAFTS=false` makes it strictly read-only, and `ALLOW_CALENDAR=false` removes the calendar tools.
## Deploy on AWS (about 20 minutes)
**You need:** an AWS account, the [AWS CLI](https://aws.amazon.com/cli/) and [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html), Python 3.13, and an iCloud **app-specific password** (account.apple.com → Sign-In and Security → App-Specific Passwords).
**1. Store your secrets** (pick a region, e.g. `us-east-2`):
```bash
export AWS_REGION=us-east-2
aws ssm put-parameter --name /icloud-mail-mcp/icloud/user --type String --value "you@icloud.com"
read -s "PW?iCloud app-specific password: " && echo # bash: read -s -p "..." PW
aws ssm put-parameter --name /icloud-mail-mcp/icloud/app-password --type SecureString --value "$PW"; unset PW
aws ssm put-parameter --name /icloud-mail-mcp/bridge/api-key --type SecureString --value "$(openssl rand -hex 32)"
```
Use your primary @icloud.com address.
**2. Deploy:**
```bash
sam build
sam deploy --guided --stack-name icloud-mail-mcp
```
Answer `y` when asked whether the Function URL may have no AWS auth; the API key is the lock. The output prints a **ConnectorUrl**. Set **DefaultTimezone** to your IANA time zone (e.g. `America/Chicago`) so "today" on the calendar means your today.
The calendar uses the same app-specific password as mail. If your Apple Account email isn't your @icloud.com address and calendar sign-in is rejected, set the `ICLOUD_CALDAV_USER` environment variable on the function to your Apple Account email.
**3. Verify** (`401` means it's up and locked):
```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST <ConnectorUrl>
```
**4. Connect it to Claude.** claude.ai → Settings → Connectors → **Add custom connector**:
- **URL:** your ConnectorUrl
- **Authentication:** no sign in
- **Request header:** `Authorization` = `Bearer <your key>`
Get your key back with:
```bash
aws ssm get-parameter --name /icloud-mail-mcp/bridge/api-key --with-decryption --query Parameter.Value --output text
```
**Cost:** Lambda, SSM and logs for personal use are typically free tier. The template also adds a monthly budget alert (default $5).
## Alternative: self-host with Docker
It also runs as a normal HTTP server, for example on a homelab behind a Cloudflare Tunnel:
```bash
cp .env.example .env # fill in iCloud user, app password, API key, tunnel token
docker compose up -d --build
```
The container publishes no ports, so only `cloudflared` can reach it. Set `PUBLIC_HOST` to your tunnel hostname to enable DNS-rebinding protection.
## Development
```bash
pip install -r requirements.txt -r requirements-dev.txt
python tests/test_bridge.py # HTTP server: auth, host checks, every tool, draft threading
python tests/test_lambda.py # Lambda mode: Function URL events, warm re-invocations
python tests/test_organize.py # move / trash / flags / folders, safety limits, MOVE fallback
python tests/test_calendar.py # CalDAV discovery, time zones, repeating and all-day events, read-only checks
cfn-lint template.yaml
```
Tests run the real server against fake IMAP and CalDAV backends. No AWS account or iCloud login needed.
**Layout:**
- `mail.py`: iCloud IMAP client (read-only selects, batch limits, `MOVE` with `COPY`+`UID EXPUNGE` fallback)
- `icloud_calendar.py`: iCloud CalDAV client (read-only, expands repeating events, iCloud-only hosts)
- `server.py`: MCP tools, API-key middleware, transport security
- `lambda_handler.py`: Lambda entry point (Function URL → ASGI via Mangum, fresh app per request)
- `template.yaml`: AWS SAM: function, URL, least-privilege IAM, logs, budget
## Limitations
- Search terms must be plain ASCII (an iCloud IMAP limitation in this version).
- Works only with iCloud Mail. Other IMAP providers would need different host settings.
- No sending, by design.
- Calendar is read-only, and covers at most 31 days per request. Reminders aren't included.
## Roadmap
- [x] iCloud Calendar over CalDAV (read-only)
- [ ] Download attachments to S3
- [ ] Terraform version of the infrastructure
## License
MIT. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues