MailMCP
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
MailMCP
A complete mail system for one domain on serverless AWS, plus an MCP server so an AI assistant can search, read and draft your mail.
It receives, sends, stores and searches mail, has a web inbox and an admin console, and runs no servers, containers, VPC or NAT gateway. Everything, including the full-text search index, lives in one DynamoDB table and one S3 bucket.
A stateful mail server splits into managed services plus small, stateless, idempotent functions, and the AI gets exactly the access you grant: drafts by default, sends only with an explicit scope, every call audited.

To use the app (accounts, inbox, AI access, admin console), see the user guide.
How it works
Three front doors share one storage layer and meet only in storage and in SQS queues.
Mail in. SES receives for the domain. The receipt rule lists every active mailbox and alias, so unknown recipients are rejected at SMTP time. SES writes the raw MIME to
raw/and notifies SNS, which feeds theinboundqueue.ingestparses, sanitizes the HTML with DOMPurify, resolves aliases, and writes the message, folder counters and usage in one transaction guarded byattribute_not_exists, so a redelivery is a no-op.Mail out.
apibuilds the MIME once, refuses suppressed addresses, and queues one job per route. Local recipients go straight ontoinboundand never touch SES. External jobs go tooutbound, wheresendertakes aSEND#jobIdlock before calling SES, so a retried job never sends twice. SES events flow back through a configuration set toses-events, which sets delivered, bounced or complained and maintains the suppression list.People. One CloudFront distribution serves the React app from
web/and routes/api/*and/admin*to Lambda function URLs that only CloudFront can call (origin access control). Sessions are httpOnly cookies backed by Cognito.Agents. AgentCore Gateway is the MCP server. It validates Cognito access tokens, a request interceptor writes the verified caller into every tool call, a Cedar policy engine permits each tool only with its scope, and
mail-toolsruns the ten tools.Admins. A server-rendered console creates and deletes mailboxes, sets quotas, manages aliases and redrives dead-letter queues. Its IAM role can only touch
MBX#,ALIAS#,USER#,DOMAIN#andSUPPRESS#keys and has no access to the mail bucket, so admins cannot read mail.
Related MCP server: universal-email-mcp
Single-table design
Every read is one Query or GetItem; search is one Query per word plus a BatchGet.
# | Pattern | Used by | Index | Key condition |
AP1 | Resolve a recipient (alias, then mailbox) | ingest, api | Table |
|
AP2 | Mailboxes a user can open | api, mail-tools | Table |
|
AP3 | Mailbox settings, quota, usage | api, admin | Table |
|
AP4 | Folders with total and unread counts | api, mail-tools | Table |
|
AP5 | Messages in a folder, newest first, paged | api, mail-tools | GSI1 |
|
AP6 | One message | api, mail-tools | Table |
|
AP7 | A thread, oldest first | api, mail-tools | GSI2 |
|
AP8 | Search by word prefix | api, mail-tools | Table |
|
AP9 | Is an address suppressed? | api | Table |
|
AP10 | Sent message from an SES event | ses-events | Table |
|
AP11 | Send lock | sender | Table | conditional put |
AP12 | All mailboxes on the domain | admin | GSI1 |
|
AP13 | Every item of a mailbox | purge | Table |
|
AP14 | MCP audit trail, newest first | api | Table |
|
Entity | PK | SK | GSI1 | GSI2 |
Domain |
|
| ||
Mailbox |
|
|
| |
Folder |
|
| ||
Membership |
|
| ||
Alias |
|
| ||
Message |
|
|
|
|
Search token |
|
| ||
Sent lookup |
|
| ||
Send lock |
|
| ||
Suppression |
|
| ||
Audit row |
|
|
Search index in the table. One item per distinct word per message, sharded by the word's first two letters so a prefix query stays in one partition. Multi-word search intersects message IDs in the function. Body indexing can be turned off per domain.
Tokens on the message. Each message keeps its token list (capped at 300), so deleting a message or a mailbox knows exactly which index items to remove.
Idempotent writes. Message ID = hash(SES message ID + mailbox); creates use
attribute_not_exists. Transactions that touch shared counters retry onTransactionConflictwith jitter.Moves are one update. Only GSI1PK changes; counters move in the same transaction. A weekly job recomputes counters and usage to fix drift.
Bodies never sit in DynamoDB. GSIs project list fields only; message items stay under 4 KB.
MCP layer
Tool | Scope | What it does |
|
| Mailboxes the caller can open |
|
| Folders with total and unread counts |
|
| A folder's messages, newest first, paged |
|
| Word-prefix search with folder, sender and date filters |
|
| Headers, attachment list, plain-text |
|
| Every message in a thread, oldest first |
|
| Read, unread, flagged on up to 50 messages |
|
| Move up to 50 messages; Trash is as far as it goes |
|
| New draft or reply, tagged |
|
| Send a draft, if the owner allows it |
Caller identity. The Lambda target only receives tool arguments, so the request interceptor verifies the access token and overwrites a reserved
_callerargument on everytools/call. A client-supplied_callernever survives.Sending is off by default. Each mailbox has an MCP mode,
drafts-onlyorsend, switched by the owner under Settings: AI access. A Cedarpermitletssend_draftthrough only for tokens withmail/send; Cedar denies by default, so a token without it does not even see the tool. At most 20 MCP sends per mailbox per hour.Untrusted content. Bodies come back as plain text with links and HTML removed, capped at 20,000 characters, in a field named
untrusted_body. Tool descriptions say mail content is data, not instructions.Audit. Every tool call writes an
AUDIT#addrrow with the tool, an argument summary (bodies replaced by their length), the result and the client ID.Schemas. The gateway accepts only
type,properties,required,itemsanddescription, sotool-schema.jsonuses nothing else and zod validates the rest inmail-tools.
Features
Receive for several mailboxes and aliases per domain, with spam, virus, SPF, DKIM and DMARC verdicts stored on each message; spam goes to Junk, viruses are dropped; unknown recipients are rejected at SMTP time.
Web inbox: folders, read and unread, flags, threads, attachments, word-prefix search, compose, reply, reply all, forward, drafts, delivery status.
Bounce and complaint tracking with a suppression list; per-mailbox daily send cap of 200.
Admin console: mailboxes, owners, quotas, aliases, body indexing, dead-letter redrive, purge on delete.
Mail that would exceed a quota waits in the inbound dead-letter queue until an admin raises the quota and redrives it, instead of bouncing to a probably forged sender.
Stack
Folder | What |
| CDK app, one stack: |
| TypeScript Lambdas on Node 24 (arm64): |
| React 19 + Vite inbox, hash routes, one stylesheet |
|
|
Quick start
npm run install:all # each package has its own lockfile
cp infra/.env.example infra/.env # MAIL_DOMAIN (a subdomain) and ALERT_EMAIL
cd infra && npx cdk bootstrap && cd .. # once per account and region (us-east-1)
npm run infra:deploy # builds the client, deploys, writes infra/cdk-outputs.json
# delegate MAIL_DOMAIN: add the NameServers output as NS records at the parent zone
npm run mail:seed -- --mailbox you --alias hello # domain, mailbox, alias, receipt rule
npm run mail:add-user -- --email you@example.com --mailbox you
npm run mail:add-admin -- --email you@example.com # optional, for /admin
npm run mail:send-test -- you "Hello" # SES mail into your inboxOpen the SiteUrl output and sign in with the temporary password. To connect Claude Code:
TOKEN=$(npm run -s mcp:token) # opens Cognito sign-in; add -- --send for mail/send
claude mcp add --transport http mailmcp "$GATEWAY_URL" --header "Authorization: Bearer $TOKEN"Locally, npm run api:dev serves the API on port 4000 against the deployed table and npm run client:dev proxies to it.
Tests
npm run infra:test checks the template: 48 checks, including on-demand billing, everything deleting on destroy, 7-day logs, arm64 and Node 24, no wildcard on the table or bucket, the admin key-prefix condition, the Cedar policies, and the alarms and budget.
npm run backend:test runs 124 checks in 11 suites. Unit suites cover keys, the tokenizer, MIME parsing and sanitizing, notification parsing and tool validation. Live suites run against the deployed stack:
Suite | Checks | What it proves |
foundation | 6 | Domain verified with DKIM, MX and DMARC resolve, table and bucket |
receive | 12 | Real mail becomes items and objects; replaying the SQS message creates no duplicate |
inbox | 18 | Every read pattern through CloudFront, cookies, flags and moves keep counters right |
send | 11 | Local delivery skips SES; SES simulator success, bounce and complaint; one SQS job delivered 4 times sends once |
mcp | 16 | OAuth discovery, Cedar hides and denies |
admin | 14 | 1,000 messages purged after a delete (6.5 s, one invocation), recount repairs drift, admin role cannot read mail (IAM simulator), DLQ redrive |
Costs
No VPC, NAT gateway, OpenSearch, RDS or provisioned capacity. Idle cost is the Route 53 hosted zone ($0.50 a month); Gateway, Policy, Cognito, Lambda, SQS and DynamoDB on-demand bill only per use. AgentCore Gateway is $0.005 per 1,000 calls and Policy $0.025 per 1,000 authorizations. Logs keep 7 days, five alarms stay inside the CloudWatch free tier, and a $5 monthly budget on the project=mailmcp tag emails at 80% actual and 100% forecast. npm run cost:report prints the month's bill by service once Cost Explorer has the tag data.
API
Method | Path | Does |
POST, DELETE |
| Sign in (sets cookies), sign out |
POST |
| First-login password, silent refresh |
GET |
| Who am I and which mailboxes I can open |
GET |
| Folder counts |
GET, PATCH |
| List a folder; flag, mark or move up to 50 |
GET |
| Message with sanitized body |
GET |
| Presigned download |
GET |
| Thread |
GET |
| Word-prefix search |
POST |
| Presigned upload for an attachment |
POST, PUT |
| Create or edit a draft |
POST |
| Send a draft |
GET, PUT |
| Audit trail and MCP mode |
Known limits
Web Functions. The API and admin run as Lambda function URLs behind CloudFront OAC. A probe deployed Lambda Web Functions through CloudFormation, but the IAM action and OAC type CloudFront needs to call an
IamAuthendpoint are not documented yet. The Hono apps have no hosting-specific code, so moving needs one new entry file.POST through OAC needs a body hash. CloudFront signs requests to function URLs only if the client sends
x-amz-content-sha256. The web app computes it, and the admin console uses a 20-line script to submit its forms, so the console is not strictly script-free.MCP tokens last an hour. Cognito has no dynamic client registration, so clients cannot run the OAuth flow themselves;
get-token.shfetches a token by hand.Receipt rule recipients. The rule lists at most 100 addresses, and the admin console rewrites the list outside CloudFormation, so a change to the rule in CDK resets it until the next mailbox change or
npm run mail:seed.Search is basic: word prefixes, every word must match, no ranking, stemming or typo tolerance; very long mails index their first 300 distinct words.
Threads follow the first
Referencesheader, so replies from clients that drop it start a new thread.No IMAP or POP. Phone and desktop mail apps need a protocol layer.
Prompt injection is reduced, not solved. Drafts-only mode, the Cedar scope, the rate limit and the audit trail bound the damage.
Build log
Phase | What |
1 | Hosted zone, SES identity with Easy DKIM, MX, SPF, DMARC, the table and bucket |
2 | Receipt rule, SNS to SQS, |
3 | Cognito, CloudFront, Hono API, React inbox with folders, threads and search |
4 | Compose, local routing, |
5 | AgentCore Gateway, interceptor, Cedar policies, |
6 | Admin console, quotas, aliases, SMTP-time rejection, purge, recount, alarms, budget |
Tear down
npm run infra:destroy # removes everything, stored mail includedThen remove the NS records for the subdomain at the parent zone.
This server cannot be deployed
Maintenance
Related MCP Connectors
Give AI agents their own email inboxes: list, read and send mail via scoped, revocable OAuth grants.
Email for AI agents: read, search and send from your Gmail, Outlook, Yahoo, iCloud or IMAP inbox.
Email inboxes for AI agents: send, receive, search, drafts with owner approval, and webhooks.
AI email inbox and sending tools with attachments, search, live events, and webhooks.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to securely read, search, and send email across IMAP and Microsoft Graph accounts, with scoped tokens, approval flows, and an audit trail.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to search, retrieve, and send email across multiple ordinary IMAP, POP3, and SMTP mailboxes, with per-account permissions and safe sending checks.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to search, read, organize, draft, send, and schedule email through a scoped token, with actions journaled and reversible. It also supports proposal queues, approval workflows, and undo operations for safe mailbox automation.15 npm8Apache 2.0
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to read, search, send, reply, forward, schedule, organize, and analyze email across multiple IMAP/SMTP accounts, with provider-aware labels, drafts, templates, and bulk operations. Additional capabilities include real-time IMAP IDLE watching with AI triage, verification-code capture, calendar extraction, analytics, and optional Streamable HTTP or OAuth2 access.23 npmISC