MCP Email Server
A Dockerized MCP (Model Context Protocol) email assistant for law firms and legal teams.
Integrates:
- Multiple IMAP/SMTP email accounts
- Shared/public folder support
- New-message tracking and flagging
- Conflict check search
- Triage guidance using personnel/chain-of-command
Designed to be consumed by LLMs via MCP (Streamable HTTP) in Open WebUI.
Quick start (single account)
-
Build:
docker build -t mcp-email-server /home/user/wall-o/mcp-email-server
-
Run (example):
docker run -d
-p 8000:8000
-e API_KEY="your_api_key_here"
-e LOG_LEVEL="DEBUG"
-e IMAP_HOST=imap.example.com
-e IMAP_PORT=993
-e IMAP_USE_SSL=true
-e IMAP_USERNAME="user@example.com"
-e IMAP_PASSWORD="secret"
-e SMTP_HOST=smtp.example.com
-e SMTP_PORT=587
-e SMTP_USE_TLS=true
-e SMTP_USE_SSL=false
-e SMTP_USERNAME="user@example.com"
-e SMTP_PASSWORD="secret"
-e SMTP_FROM="user@example.com"
-e DEFAULT_CC="supervisor@example.com"
-e BUSINESS_DESCRIPTION="A law firm handling corporate and litigation matters."
-e PERSONNEL_JSON='[{"name":"Jane Doe","role":"Managing Partner","email":"jane@example.com","escalates_to":null},{"name":"John Smith","role":"Associate","email":"john@example.com","escalates_to":"Jane Doe"}]'
mcp-email-server -
Health check:
-
MCP endpoint:
- POST http://localhost:8000/mcp
- Uses JSON-RPC 2.0 (methods: initialize, tools/list, tools/call)
-
Integrate with Open WebUI (MCP Streamable HTTP):
- Admin Settings → External Tools → Add Server
- Type: MCP (Streamable HTTP)
- Server URL: http://:8000/mcp
- Auth: Bearer
- Key: your_api_key_here
- Save, then enable tools in a chat.
Multi-account configuration
The server can manage multiple IMAP/SMTP accounts. Tools accept an optional "account" parameter; if omitted, the default account is used.
You have two main options:
- Inline JSON (ACCOUNTS_JSON)
Set ACCOUNTS_JSON with a JSON array of accounts.
Copy-paste template (single line, for docker run):
-e ACCOUNTS_JSON='[{"id":"account1","name":"Primary Firm Inbox","imap_host":"imap.primary.com","imap_port":993,"imap_use_ssl":true,"imap_username":"user@primary.com","imap_password":"CHANGE_ME_1","smtp_host":"smtp.primary.com","smtp_port":587,"smtp_use_tls":true,"smtp_use_ssl":false,"smtp_username":"user@primary.com","smtp_password":"CHANGE_ME_1","smtp_from":"user@primary.com"},{"id":"account2","name":"Secondary Inbox","imap_host":"imap.secondary.com","imap_port":993,"imap_use_ssl":true,"imap_username":"user@secondary.com","imap_password":"CHANGE_ME_2","smtp_host":"smtp.secondary.com","smtp_port":587,"smtp_use_tls":true,"smtp_use_ssl":false,"smtp_username":"user@secondary.com","smtp_password":"CHANGE_ME_2","smtp_from":"user@secondary.com"}]'
Human-readable equivalent (for reference):
ACCOUNTS_JSON='[ { "id": "account1", "name": "Primary Firm Inbox", "imap_host": "imap.primary.com", "imap_port": 993, "imap_use_ssl": true, "imap_username": "user@primary.com", "imap_password": "CHANGE_ME_1", "smtp_host": "smtp.primary.com", "smtp_port": 587, "smtp_use_tls": true, "smtp_use_ssl": false, "smtp_username": "user@primary.com", "smtp_password": "CHANGE_ME_1", "smtp_from": "user@primary.com" }, { "id": "account2", "name": "Secondary Inbox", "imap_host": "imap.secondary.com", "imap_port": 993, "imap_use_ssl": true, "imap_username": "user@secondary.com", "imap_password": "CHANGE_ME_2", "smtp_host": "smtp.secondary.com", "smtp_port": 587, "smtp_use_tls": true, "smtp_use_ssl": false, "smtp_username": "user@secondary.com", "smtp_password": "CHANGE_ME_2", "smtp_from": "user@secondary.com" } ]'
- JSON file (ACCOUNTS_CONFIG_PATH)
Mount a JSON file and set ACCOUNTS_CONFIG_PATH. Example:
docker run -d
-p 8000:8000
-v /path/to/accounts.json:/etc/mcp-email/accounts.json:ro
-e API_KEY="your_api_key_here"
-e ACCOUNTS_CONFIG_PATH="/etc/mcp-email/accounts.json"
mcp-email-server
Use the same structure as ACCOUNTS_JSON.
Default account:
- Use DEFAULT_ACCOUNT="firm_main" to set the default.
- If not set:
- If only one account exists, it is default.
- Otherwise, an account with id "default" is preferred, or the first one is used.
Tool usage with multiple accounts:
- Most tools accept an "account" field. Examples:
- list_folders: { "account": "firm_main" }
- search_messages: { "account": "secondary", "folder": "INBOX", "query": "conflict" }
- send_email: { "account": "firm_main", "to": ["client@example.com"], "subject": "...", "body": "..." }
- If "account" is omitted, the default account is used.
A new tool is available:
- list_accounts:
- Lists all configured accounts and shows which is default.
Environment variables
See env.example for a full list.
Core:
-
API_KEY:
- If set, all /mcp requests must include: Authorization: Bearer <API_KEY>
- Leave empty to disable auth (not recommended in production).
-
LOG_LEVEL:
- e.g., INFO, DEBUG, ERROR (default: INFO)
Single-account IMAP (only if not using ACCOUNTS_JSON/ACCOUNTS_CONFIG_PATH):
- IMAP_HOST
- IMAP_PORT
- IMAP_USE_SSL
- IMAP_USERNAME
- IMAP_PASSWORD
Single-account SMTP:
- SMTP_HOST
- SMTP_PORT
- SMTP_USE_TLS
- SMTP_USE_SSL
- SMTP_USERNAME
- SMTP_PASSWORD
- SMTP_FROM
Optional email:
- DEFAULT_CC: Comma-separated CC addresses.
- DEFAULT_BCC: Comma-separated BCC addresses.
- BUSINESS_DESCRIPTION: Context for the LLM (e.g., firm description).
- PERSONNEL_JSON: JSON array of personnel/chain-of-command:
- Example: [ { "name": "Jane Doe", "role": "Managing Partner", "email": "jane@example.com", "escalates_to": null }, { "name": "John Smith", "role": "Associate", "email": "john@example.com", "escalates_to": "Jane Doe" } ]
- SCHEDULED_SEND_INTERVAL: Seconds between checks for scheduled emails (default: 10).
HTML, signature, and LLM identity:
-
Emails (send_email, reply_to_message, forward_message, schedule_send) default to HTML (html=true) unless explicitly set to false.
-
EMAIL_HTML_SIGNATURE: Optional HTML signature appended to HTML emails.
- Inline HTML example:
-e EMAIL_HTML_SIGNATURE='Best regards,'
Jane Doe
Associate - Template file example:
- Mount the file: -v /path/to/signature.html:/etc/mcp-email/signature.html:ro
- Set env: -e EMAIL_HTML_SIGNATURE='/etc/mcp-email/signature.html' If the value is an existing file path, its contents are used as the signature.
- Inline HTML example:
-e EMAIL_HTML_SIGNATURE='
-
Signature template variables:
- The signature can include placeholders:
- {{NAME}}
- {{TITLE}}
- {{PHONE}}
- {{ADDRESS}}
- {{EMAIL}}
- These are filled in using:
- Per-call LLM identity (signature_vars), or
- Fallback environment variables: LLM_NAME, LLM_TITLE, LLM_PHONE, LLM_ADDRESS, LLM_EMAIL
- Example env-based defaults: -e LLM_NAME='Adam P. Strömbergsson-DeNora' -e LLM_TITLE='Barrister & Solicitor' -e LLM_PHONE='1 613 699 2127' -e LLM_ADDRESS='7th floor - 1 Rideau St. Ottawa, ON. K1N 8S7' -e LLM_EMAIL='adam@apstrom.ca'
- The signature can include placeholders:
-
LLM identity via MCP (signature_vars):
- When sending, replying, forwarding, or scheduling an email, the LLM should include its identity so the signature is populated correctly.
- Example (send_email):
{
"name": "send_email",
"arguments": {
"to": ["client@example.com"],
"subject": "Your subject",
"body": "
Hello,
...
", "html": true, "signature_vars": { "NAME": "Adam P. Strömbergsson-DeNora", "TITLE": "Barrister & Solicitor", "PHONE": "1 613 699 2127", "ADDRESS": "7th floor - 1 Rideau St. Ottawa, ON. K1N 8S7", "EMAIL": "adam@apstrom.ca" } } } - The LLM must provide accurate identity details; do not leave these fields blank or generic.
MCP tools
All tools are exposed via MCP (Streamable HTTP) at POST /mcp.
General:
- list_accounts
Email (listing, reading, and triage):
- list_folders
- search_messages
- get_new_messages
- sync_seen
- get_unread_summary
- list_unread_emails
- get_email
- summarize_email
- get_thread
Email (actions):
- mark_as_read
- flag_message
- move_message
- copy_message
- apply_label
- remove_label
- list_labels
Email (send/reply/forward):
- send_email
- reply_to_message
- forward_message
- save_draft
- schedule_send
Attachments and threads:
- list_attachments
- download_attachment
- search_attachments
- export_conversation
Legal-specific:
- conflict_check_search
- get_triage_config
Notes:
- IMAP operations use UIDs for stability.
- Conflict check search scans subject lines across specified folders for given terms.
- New-message tracking is in-memory per container instance.
- For production:
- Always set API_KEY.
- Use HTTPS and secure IMAP/SMTP settings.
New tools for efficient email triage
These tools are designed to help LLMs triage many emails without overloading the context window.
-
list_unread_emails
- Purpose: List unread emails with minimal content (metadata + short snippet).
- Use this first when scanning a large inbox.
- Parameters:
- account: optional (uses default if omitted)
- folder: IMAP folder (default: INBOX)
- max_results: max emails to return (default: 50)
- include_body: if true, include full body (default: false)
- since: optional SINCE date, e.g. "01-Jan-2024"
- Returns:
- For each email: uid, subject, from, to, date, flags, snippet, has_attachments
- Plus total_unread and returned counts
-
get_email
- Purpose: Fetch the full content of a single email by UID.
- Use this after scanning with list_unread_emails or get_unread_summary.
- Parameters:
- folder: IMAP folder (default: INBOX)
- uid: message UID
- include_html: if true, include HTML body (default: false)
- Returns:
- subject, from, to, cc, date, body_plain, attachments
- body_html only if include_html=true
-
summarize_email
- Purpose: Get a concise summary of an email via an external model (if configured), instead of loading the full text into the main LLM context.
- Use this when you need to understand an email quickly without reading its full content.
- Parameters:
- folder: IMAP folder (default: INBOX)
- uid: message UID
- max_length: max summary length in words (default: 150)
- Behavior:
- If external summary model is configured, it sends the email to that model and returns its summary.
- If not configured or the call fails, it falls back to the first N words of the email.
- Returns:
- uid, subject, from, date, summary (including attachment note if present)
-
get_thread
- Purpose: Get a concise view of the conversation thread for a given email.
- Use this to understand context without loading all message bodies.
- Parameters:
- folder: IMAP folder (default: INBOX)
- uid: message UID to use as the anchor
- max_messages: max thread messages to return (default: 10)
- Returns:
- subject, participants, message_count
- For each message: uid, from, date, subject, snippet (short)
Suggested LLM workflow:
- Call list_unread_emails(max_results=100) to scan metadata.
- For interesting emails:
- Use summarize_email(uid) for a quick summary, or
- Use get_email(uid) if you need the full content.
- For email threads:
- Use get_thread(uid) to see the conversation outline.
- Then selectively call get_email on key messages.
External summary model configuration
Optional: configure an external LLM to summarize emails via summarize_email. This keeps long emails out of the main model’s context.
Environment variables:
- EXTERNAL_SUMMARY_MODEL_ENDPOINT:
- OpenAI-compatible /v1/chat/completions endpoint.
- Example: http://localhost:8080/v1
- EXTERNAL_SUMMARY_MODEL_API_KEY:
- API key for the external model.
- EXTERNAL_SUMMARY_MODEL_NAME:
- Model name (default: gpt-4o-mini).
- EXTERNAL_SUMMARY_MAX_TOKENS:
- Max tokens for each summary (default: 400).
Example (docker run):
-e EXTERNAL_SUMMARY_MODEL_ENDPOINT='http://localhost:8080/v1'
-e EXTERNAL_SUMMARY_MODEL_API_KEY='your_summary_model_key'
-e EXTERNAL_SUMMARY_MODEL_NAME='gpt-4o-mini'
-e EXTERNAL_SUMMARY_MAX_TOKENS='400'
If these are not set:
- The summarize_email tool will still work, but will fall back to returning the first N words of the email instead of using an external model.