myMail-mcp
Allows connecting Gmail accounts and managing email through IMAP/SMTP, including searching, reading, sending, drafting, moving, archiving, and deleting messages.
Allows connecting iCloud Mail accounts and managing email through IMAP/SMTP, including searching, reading, sending, drafting, moving, archiving, and deleting messages.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@myMail-mcpsearch my Gmail for invoices from Stripe"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Email MCP Server for Cloudflare Workers
Fork notice. This repository is a fork of Xzese/email-mcp-for-cloudflare-workers (MIT), tracking upstream commit
0180c5c. Local changes: a Yahoo Mail provider preset, Claude Cowork / Claude Code setup instructions, a Yahoo and iCloud app-password guide, and an Outlook secret that is only required when Outlook is actually configured. Everything runs on Cloudflare's free tier — Workers, SQLite-backed Durable Objects, Workers KV, and Cloudflare Access (free up to 50 users).
A container-free remote MCP server for Gmail, Outlook, iCloud, Yahoo, and custom IMAP/SMTP accounts.
The Worker connects directly to IMAP and SMTP using Cloudflare outbound TCP sockets. Mailbox credentials are AES-256-GCM encrypted before being stored in Workers KV.
Table of contents
Related MCP server: simple-email-mcp
Public source, private deployments
This repository publishes the server source under the MIT License. It does not provide a shared, publicly accessible email service. Every operator deploys their own Worker, KV namespace, Cloudflare Access application, Microsoft Entra application, and secrets.
All configured mailboxes share one encrypted account store within a deployment. The server is therefore intended for one owner or a small group of mutually trusted users. Do not grant Cloudflare Access to unrelated tenants or users who should not share mailbox access.
The checked-in wrangler.toml is a sanitized example. For a local or manually initiated
deployment, copy it to the ignored production configuration and replace every placeholder with
identifiers from your own accounts:
cp wrangler.toml wrangler.production.tomlNever commit wrangler.production.toml, .dev.vars, private keys, certificates, Worker secrets,
mailbox credentials, or OAuth client secrets.
For repository-connected Cloudflare builds, do not commit or upload
wrangler.production.toml. The build generates an ignored wrangler.generated.json containing
the real KV binding while preserving the runtime variables and secrets already configured in the
Cloudflare dashboard. See Cloudflare repository builds.
Tools
email_add_account,email_list_accounts,email_remove_accountemail_test_connectionemail_list_folders,email_get_mailbox_status,email_get_all_account_mailbox_statusesemail_search_messages,email_search_all_accounts,email_list_all_inbox_messages,email_get_message,email_get_message_thread,email_get_message_attachmentemail_update_message_flags,email_move_messages,email_archive_messages,email_move_messages_to_trash,email_delete_messages_permanentlyemail_create_message_draft,email_create_forward_draft,email_update_message_draft,email_send_draft
Every tool publishes an MCP output schema and returns validated structuredContent. A JSON text
copy is also returned for compatibility with clients that do not yet consume structured output.
Local setup
Create .dev.vars:
CREDENTIAL_ENCRYPTION_KEY=base64-encoded-32-byte-key
ACCESS_LOCAL_DEV=trueGenerate the encryption key with:
openssl rand -base64 32Then run:
npm install
npm run devConnect an MCP client to http://localhost:8787/myMail/mcp.
Because wrangler.toml declares a route, wrangler dev simulates the route hostname rather than
localhost, and the ACCESS_LOCAL_DEV bypass (which requires a loopback hostname) will not engage.
Run wrangler dev --host localhost to develop against the bypass.
ACCESS_LOCAL_DEV bypasses Access verification only when the request hostname is localhost,
127.0.0.1, or ::1. Never configure it as a production Worker variable or secret.
MCP tool calls emit structured events to Cloudflare Workers Logs with the tool name, status,
duration, and a safe error category. Tool arguments, upstream error text, credentials, and email
content are not logged.
View production events under Workers & Pages → email-mcp-server → Observability or stream
local/deployed events with npx wrangler tail.
In VS Code, open Run and Debug, select Email MCP: Local server, and click Run (or
press F5). Wrangler loads the same .dev.vars file automatically in the integrated terminal.
Deploy from the dashboard (no terminal)
The whole deployment works from the Cloudflare dashboard using Workers Builds, with no local
Wrangler install and no build variables. Everything Wrangler needs is committed in
wrangler.toml; the only thing set in the dashboard is the one real secret.
1. Create the Worker from this repository
Workers & Pages → Create → Workers → Import a repository, then select this repository and its default branch. Set the deploy command to:
npm run deployUse wrangler deploy, not wrangler versions upload: only deploy can apply the Durable Object
migration this Worker needs on its first release.
Name the Workers Builds project to match the name field in wrangler.toml, or update that field
to match the project, so Wrangler does not override it at build time.
2. Add the encryption key
Under Settings → Build → Variables and secrets, add:
Name | Type | Value |
| Secret | A base64-encoded 32-byte AES key |
A Worker whose configuration is managed by wrangler.toml does not expose a separate runtime
Variables and Secrets card in the dashboard, only the build section. Build secrets reach the deploy
command's environment but are never bound to the running Worker, so the deploy script
(scripts/deploy.mjs) promotes this one to a runtime secret with
wrangler deploy --secrets-file, writing it to a private temporary file that is deleted when the
deploy finishes. Secrets absent from that file are preserved from the previous version.
Everything else — the KV namespace ID, the Access team domain and audience — lives in
wrangler.toml, because wrangler deploy treats that file as the source of truth for [vars].
3. Add the DNS record for the hub hostname
This Worker is attached with a route (mcp.timothyjacobs.net/myMail*) rather than a custom
domain, so that other MCP servers can take their own paths on the same hostname later. Routes do
not create DNS records, and without one the hostname never resolves and requests never reach the
Worker.
In DNS → Records on timothyjacobs.net, add:
Type | Name | Content | Proxy status |
AAAA |
|
| Proxied |
100:: is Cloudflare's reserved placeholder for originless setups. Because the record is proxied,
requests never reach that address — the route intercepts them first.
4. First deploy
Trigger a build. The MCP endpoint is then https://mcp.timothyjacobs.net/myMail/mcp and the admin
UI is at https://mcp.timothyjacobs.net/myMail.
Until Access is configured the Worker fails closed — the placeholder issuer and audience reject every request. That is expected at this stage.
4. Configure Access, then commit the real values
Follow Cloudflare Access to create the self-hosted application, enable
Managed OAuth, and obtain the AUD tag and team domain. Edit wrangler.toml — directly on
github.com is fine — replacing both placeholders:
[vars]
TEAM_DOMAIN = "https://<your-team>.cloudflareaccess.com"
POLICY_AUD = "<your-access-application-aud-tag>"Committing triggers a redeploy. These are identifiers rather than credentials, so committing them to a public repository does not grant anyone access; if you would rather not publish them at all, make the repository private first.
5. Add mailboxes
Open the Worker's root URL, sign in through Access, and add each account with its provider button. See Yahoo and iCloud app passwords.
Cloudflare Access
Production authentication is handled by a Cloudflare Access self-hosted application with
Managed OAuth. The Worker also verifies every Cf-Access-Jwt-Assertion signature, issuer, and
audience before routing a request.
Deploy the Worker and add the hub DNS record first. Until Access is configured, the placeholder issuer and audience make the Worker fail closed.
In Zero Trust → Access controls → Applications, create a Self-hosted and private application scoped to the path this Worker occupies, not the whole hostname:
Subdomain
Domain
Path
mcptimothyjacobs.netmyMailScoping to the path keeps the rest of
mcp.timothyjacobs.netfree for other MCP servers, each with its own Access application and policy.Add an Allow policy restricted to your email or identity group. This server uses one shared encrypted account store, so do not authorize unrelated users.
Configure one-time PIN or an identity provider. Enable MFA at the identity provider or in the Access policy where appropriate.
Under the application's advanced settings, enable Managed OAuth. Configure only the redirect URIs required by your MCP clients; enable localhost or loopback redirects only when needed for local clients such as MCP Inspector.
Copy the application AUD tag and your Zero Trust team domain into the ignored
wrangler.production.toml:
[vars]
TEAM_DOMAIN = "https://your-team.cloudflareaccess.com"
POLICY_AUD = "your-access-application-aud-tag"
OUTLOOK_CLIENT_ID = "your-microsoft-entra-application-client-id"
OUTLOOK_TENANT = "consumers"See Cloudflare's Secure MCP servers and Managed OAuth guides for the current dashboard and redirect-URI configuration.
Microsoft Entra app registration for Outlook
Outlook.com, Hotmail, Live, and Microsoft 365 accounts use interactive Microsoft OAuth. The Microsoft account that owns the app registration does not need to be the mailbox account. Each mailbox owner signs in and grants the app access when Outlook is selected in the account form.
In the Microsoft Entra admin center, open Entra ID → App registrations → New registration.
Enter any name and choose the supported account type:
For Outlook.com, Hotmail, and Live accounts only, choose Personal Microsoft accounts only.
To support both personal and work/school accounts, choose Accounts in any organizational directory and personal Microsoft accounts.
After registering, copy Application (client) ID from Overview. Do not use the object ID, client-secret ID, or client-secret value as
OUTLOOK_CLIENT_ID.Open Authentication → Add a platform → Web and add every callback URL from which the flow will be started. The path and port must match exactly:
https://mcp.timothyjacobs.net/myMail/oauth/outlook/callback http://localhost:8787/oauth/outlook/callbackThe localhost entry is only needed for local development. Add another localhost URL if Wrangler runs on a different port. Leave both implicit grant checkboxes clear and leave Allow public client flows disabled; this server uses the authorization-code flow with PKCE.
Open API permissions → Add a permission → APIs my organization uses → Office 365 Exchange Online → Delegated permissions, then add:
IMAP.AccessAsUser.AllSMTP.Send
The Worker also requests
openid,profile,email, andoffline_accessduring sign-in. Organizational policies may require an administrator to grant consent; personal accounts can normally grant consent interactively.Open Certificates & secrets → Client secrets → New client secret. Copy the secret's Value immediately. Microsoft only displays it once. Do not copy the Secret ID.
Set the application client ID and appropriate Microsoft sign-in tenant in
wrangler.production.toml:
[vars]
OUTLOOK_CLIENT_ID = "your-application-client-id"
OUTLOOK_TENANT = "consumers"Use "consumers" for personal Microsoft accounts, "organizations" for work/school accounts,
"common" for both, or a specific tenant ID for one organization. The app registration's
supported account type must allow the value selected here.
For local development, put the client-secret Value in .dev.vars:
OUTLOOK_CLIENT_SECRET="your-client-secret-value"For production, install it as a Worker secret instead of placing it in the TOML configuration:
printf '%s' 'your-client-secret-value' | npx wrangler secret put OUTLOOK_CLIENT_SECRET --config wrangler.production.tomlCommon configuration errors:
unauthorized_clientor “not enabled for consumers” means the app's supported account type does not allow personal accounts, orOUTLOOK_CLIENT_IDis not the Application (client) ID.AADSTS50011means the callback URL does not exactly match a configured Web redirect URI.invalid_clientduring the callback usually means the secret is expired or the Secret ID was supplied instead of the secret Value.
See Microsoft's documentation for registering an application, redirect URIs, client credentials, and OAuth for IMAP and SMTP.
Production secret and deployment
Create and fill wrangler.production.toml as described above. The public example intentionally
cannot be deployed until its placeholder KV namespace and Access values are replaced.
For an existing deployment, first confirm the required secrets are present:
npx wrangler secret list --config wrangler.production.tomlDo not replace an existing CREDENTIAL_ENCRYPTION_KEY; existing encrypted account records depend
on it. For a brand-new deployment with no stored accounts, generate and install the key without
writing it to disk:
CREDENTIAL_ENCRYPTION_KEY="$(openssl rand -base64 32)"
printf '%s' "$CREDENTIAL_ENCRYPTION_KEY" | npx wrangler secret put CREDENTIAL_ENCRYPTION_KEY --config wrangler.production.toml
unset CREDENTIAL_ENCRYPTION_KEYDeploy after updating the Access variables:
npm run deployThe production MCP endpoint is https://mcp.timothyjacobs.net/myMail/mcp.
Open https://mcp.timothyjacobs.net/myMail to manage email accounts through the
Access-protected web interface. Credentials submitted there go directly from the browser to the
Worker and do not pass through an MCP client or language model.
Connect ChatGPT
After deploying the Worker and configuring Cloudflare Access, add its MCP endpoint to ChatGPT:
MCP server URL: https://mcp.timothyjacobs.net/myMail/mcpOpen Settings → Security and login and turn on Developer mode.
Open Settings → Plugins and select Add custom plugin.
Enter
Email MCP Serveras the name andhttps://mcp.timothyjacobs.net/myMail/mcpas the server URL.Select OAuth if prompted, then select Scan Tools and complete the Cloudflare Access sign-in.
Select Create, then choose the plugin in a new chat.
Connect Claude
The Worker sits behind Cloudflare Access, which advertises OAuth to MCP clients. Both Claude surfaces complete the Access sign-in in a browser and store the resulting token themselves — no API key or bearer header to paste, and no credential ever reaches the model.
Endpoint: https://mcp.timothyjacobs.net/myMail/mcp
Claude Cowork / claude.ai
Open Settings → Connectors and select Add custom connector.
Name it
Emailand enterhttps://mcp.timothyjacobs.net/myMail/mcpas the URL.Select Add, then Connect on the new connector.
Complete the Cloudflare Access sign-in in the popup. The connector turns green when the tool list loads.
Claude Code
claude mcp add --transport http email https://mcp.timothyjacobs.net/myMail/mcpThen run /mcp inside Claude Code and choose Authenticate for the email server. A browser
opens for the Access sign-in and the token is saved locally. Add --scope user to make the server
available across all your projects instead of just the current one.
Verify with /mcp — the server should report as connected with its tools listed.
Yahoo and iCloud app passwords
Neither provider accepts your normal account password over IMAP, and neither offers third-party
OAuth for mail. Both require a provider-issued app password, which you paste into the Worker's
own Access-protected web UI at https://mcp.timothyjacobs.net/myMail — not into a Claude
client, and not into this repository.
Yahoo — sign in, then Account Info → Account Security → Generate app password. Name it
something like Email MCP and copy the 16-character value. If the option is missing, turn on
two-step verification first.
iCloud — go to account.apple.com, then Sign-In and Security → App-Specific Passwords → Generate an app-specific password. This requires two-factor authentication on the Apple Account.
Add each account in the web UI with the matching provider button — Yahoo and iCloud prefill the correct hosts and ports:
Provider | IMAP | SMTP |
Yahoo |
|
|
iCloud |
|
|
The app password is encrypted with AES-256-GCM under CREDENTIAL_ENCRYPTION_KEY before it is
written to KV, and is never rendered back into the page after saving.
Cloudflare repository builds
Cloudflare only receives files committed to the connected Git repository. Because wrangler.toml
carries the KV namespace binding and the Access variables, a Git-connected build needs no build
variables at all — set the deploy command to npx wrangler deploy and add
CREDENTIAL_ENCRYPTION_KEY under Settings → Variables and Secrets.
The deploy command must be npx wrangler deploy. wrangler versions upload cannot apply
Durable Object migrations, and this Worker declares one to create the MyMCP class, so an
upload-only first deploy fails with "This Worker has a pending Durable Object migration".
Once that migration has been applied by a real deploy, npx wrangler versions upload is usable
for staging later changes without promoting them.
The Worker name in wrangler.toml must match the Workers Builds project name, otherwise Wrangler
overrides it during the build and opens a pull request proposing the rename.
npm run cloudflare:deploy and npm run cloudflare:upload remain as aliases for those two
commands, so a deploy command configured before the generator was removed keeps working.
Dependency updates
Dependabot checks npm packages and GitHub Actions weekly. Minor and patch updates are grouped into
one pull request per ecosystem (npm-routine and github-actions-routine). Major updates remain
individual pull requests and require manual review; the auto-merge workflow never selects them.
For same-repository Dependabot pull requests targeting main, the workflow reads Dependabot's
metadata without checking out or executing pull request code. It queues squash auto-merge only for
minor and patch updates. GitHub merges the pull request after every required check and ruleset
condition succeeds, using the current pull request head commit.
Enable the following repository settings for this workflow:
Under Settings → General → Pull Requests, enable Allow auto-merge.
Configure the
mainruleset to require branches to be up to date and these status checks:checks(theCIworkflow, GitHub Actions app ID15368) andWorkers Builds: email-mcp-server(Cloudflare app ID85455). Keep Cloudflare builds enabled for Dependabot branches; a missing required check blocks merging.If the ruleset restricts who can update
main, it must allow the GitHub Actions identity used by the workflow to complete an approved auto-merge. Keep required checks enabled and do not use a bypass actor for the Dependabot workflow. An admins-only update restriction can leave auto-merge queued indefinitely.
Account settings
Prefer the web management interface at the Worker root to add, edit, or remove accounts. Editing
can change the display name, email address, IMAP/SMTP hosts, ports, and TLS modes without replacing
the stored password or OAuth tokens. The email_add_account tool remains available for clients
that explicitly need programmatic setup.
Each configured account also has a Test connection action that authenticates with its stored
credentials and reports the connection result without exposing those credentials to the browser.
Use the exact folder path returned by email_list_folders. Search results default to newest
first; set sortOrder to oldest for chronological order. Results include their folder path;
pass that folder and IMAP UID to email_get_message or mutation tools.
email_search_messages returns a structured result with status, outcome, count, total, empty,
messages, and an opaque nextCursor when another page is available. Pass nextCursor back as
cursor with otherwise identical search criteria. An outcome of no_matches explicitly means
the IMAP search completed successfully with zero matching messages; it does not indicate a
connection or folder error.
email_list_all_inbox_messages lists message summaries from INBOX across all configured accounts,
or the supplied accountIds, without requiring search filters. email_search_all_accounts applies the
same summary shape with optional filters across INBOX by default or another supplied folder. Both
return a flat message list with account identity on every message plus per-account totals, errors,
and account-specific nextCursor values for follow-up single-account searches. email_get_all_account_mailbox_statuses
checks message counts across the same selected accounts. These multi-inbox tools are read-only;
archive, move, trash, delete, and mark operations remain scoped to one explicit account.
Search filters can be combined and include from, to, cc, bcc, subject, body-only text,
all header/body text, Message-ID, internal and sent dates, message size, IMAP keywords, and the
seen, flagged, answered, draft, or deleted states. All supplied filters must match. Search
summaries include messageId, inReplyTo, references, and a header-derived threadId.
State filters use explicit three-way values rather than checkboxes: for example, seen accepts
any, seen, or unseen, while flagged accepts any, flagged, or unflagged. The answered,
draft, and deleted filters follow the same pattern, so an unset state is distinct from not
filtering by that state.
email_get_message_thread accepts the folder and IMAP UID of any message and returns up to 100 summaries from
the same header-based conversation, oldest first. This works without requiring the optional IMAP
THREAD extension, but it is scoped to one folder and depends on messages having valid
Message-ID, In-Reply-To, and References headers. Use each returned IMAP UID with email_get_message when
full message content is required.
email_get_mailbox_status returns the total and unread message counts plus IMAP RECENT, UIDNEXT,
and UIDVALIDITY values for one folder.
email_get_message includes a zero-based attachmentIndex for every attachment. Pass the folder, IMAP UID,
and index to email_get_message_attachment to retrieve its raw bytes as contentBase64.
IMAP configuration is required for every account. SMTP is optional; accounts without it can read,
search, draft, and manage mail but cannot use email_send_draft. email_list_accounts reports
capabilities.canSend so MCP clients can determine whether sending is available before attempting it.
email_update_message_flags accepts one IMAP UID or an array of up to 100 IMAP UIDs and seen: true/false for read/unread
and flagged: true/false for flagged/unflagged. Either or both states can be changed in one call.
email_move_messages accepts either one IMAP UID or an array of up to 100 IMAP UIDs and moves them in one IMAP
operation to an explicit targetFolder.
Use email_archive_messages for archiving; it moves up to 100 messages to the account's advertised
IMAP Archive folder, or the provider all-mail folder when that is the advertised archive destination.
Use email_move_messages_to_trash for normal deletion; it moves up to 100 messages to the folder advertised with
the IMAP \Trash special-use flag. email_delete_messages_permanently permanently marks and expunges up to 100
messages by IMAP UID and should only be used when permanent deletion is intended.
Sending is a two-step workflow: call email_create_message_draft, then pass its returned folder and
IMAP UID field uid to email_send_draft. After SMTP accepts the message, the server appends a copy to the IMAP Sent
folder and then removes the draft. A Sent append failure is reported as sentSaved: false, and a
cleanup failure is reported as draftDeleted: false, without reporting the already accepted send
as failed.
Draft attachments use native MIME encoding. Pass up to 20 attachments as attachments, each
with filename, contentType, and contentBase64. The base64 value must contain the raw file
bytes without a data-URL prefix. Draft text and HTML bodies are emitted as quoted-printable MIME
parts; base64 is only required for attachment bytes.
To create a reply, call email_create_message_draft with replyToMessage.folder and
the original message's IMAP UID in replyToMessage.uid. The server derives the recipient and subject and adds the correct
In-Reply-To and References headers. Reply drafts quote the original message by default; set
replyToMessage.quoteOriginal: false only when the original message should not be quoted. Set
replyAll within replyToMessage when needed; top-level to, cc, and subject values override
the derived values. Set top-level replyTo when the outgoing draft should include a Reply-To header.
email_create_forward_draft creates a forward draft and includes the original attachments unless
includeAttachments is false. email_update_message_draft replaces an existing IMAP draft and returns its
new IMAP UID; omitted fields are preserved, and an empty attachments array removes all attachments.
Provider | IMAP | SMTP | Authentication |
Gmail |
|
| Google app password |
Outlook |
|
| Microsoft OAuth2 sign-in |
iCloud |
|
| Apple app-specific password |
Yahoo |
|
| Yahoo app password |
Custom | Provider supplied | Provider supplied | Password or OAuth2 |
For port 587, set smtpSecure to false; the native SMTP client negotiates STARTTLS. Cloudflare blocks outbound SMTP port 25.
Outlook refresh uses the Entra application client ID and Worker-held client secret. Microsoft may require tenant administrator consent for delegated Exchange permissions, depending on the tenant's consent policy. SMTP AUTH must also be enabled for the Exchange Online mailbox if sending fails.
Security
Cloudflare Access authenticates users and enforces the application policy before traffic reaches
the Worker. The Worker independently validates the Access JWT using the team JWKS, issuer, and
application audience. Mailbox passwords and OAuth tokens are AES-256-GCM encrypted in Workers KV
and are never returned by account tools. Changing CREDENTIAL_ENCRYPTION_KEY makes existing
stored accounts unreadable.
This server cannot be deployed
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
Read, send, file and search email in any Gmail, Microsoft 365 or IMAP mailbox, plus its calendar.
Send transactional email and manage domains, audiences, and broadcasts from any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables reading and sending emails via IMAP and SMTP through the MCP protocol. Supports multiple email accounts and configuration via UI or environment variables.BSD 3-Clause
- AlicenseBqualityBmaintenanceEnables users to manage email accounts via IMAP/SMTP, including reading, searching, sending emails with attachments and calendar invites, all through natural language interactions with MCP-compatible clients.14MIT
- AlicenseAqualityDmaintenanceEnables Gmail management via MCP, including email search, retrieval, labeling, sending, and forwarding through IMAP and SMTP.1511BSD 3-Clause
- AlicenseAqualityBmaintenanceMCP server that enables email management (send, read, search, delete, etc.) via IMAP/SMTP, compatible with Gmail, Outlook, Yahoo, iCloud, and other standard mail servers.11MIT