Email (IMAP/SMTP)

io.github.boutquinPublished by GitHub account boutquinv1.0.1Updated Oct 25, 2026

Multi-account IMAP/SMTP email server with 22 tools for reading, sending, searching, and organizing.

Overview

AI-generated overview

A local MCP server that gives an assistant full IMAP/SMTP email access across multiple accounts: reading, searching, sending, and organizing mail.

What you can do
List accounts, folders and messages, and read unread mail or full message bodies with pagination and thread views. Search subject and body with sender, recipient and date filters. Send, reply, forward, create and send drafts, with file attachments from the host. Move, copy, delete, mark read or unread, flag, and batch these operations. List and download attachments.
When to use it
Use it when you want an assistant to work with real mailboxes over standard IMAP/SMTP, including well-known providers like Gmail, Outlook, Yahoo, iCloud, Fastmail and Zoho, or custom mail servers, across several accounts at once.
Requirements
Runs as a local process (Go install, Homebrew, prebuilt binary, Docker image, or Claude Desktop bundle). Needs one of EMAIL_CONFIG_FILE (path to a JSON account file) or EMAIL_ACCOUNTS (inline JSON), each account with id, email, username and password or OAuth2 credentials. Network access to the mail servers; optional OAuth2 client ID and secret for Gmail and Outlook.
Before you install
The config holds account passwords or OAuth2 secrets; the README advises restricting it with chmod 600. The assistant can send, reply, forward, move, copy and permanently delete mail, so writes are real. Attachments are read from absolute paths on the host and downloads can be saved to disk. OAuth2 tokens are stored locally. The insecure_skip_verify option disables TLS certificate checks.

Installation

In SourceWeft

  1. Open Email (IMAP/SMTP) in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

mcp-server-email

[CI] [codecov] [Go] [License: MIT] [MCP]

Multi-account email server for the Model Context Protocol. Gives LLMs full email access — read, send, search, organize — over IMAP/SMTP with connection pooling, rate limiting, and retry. Designed as the remote-email counterpart to apple-bridge (local Mail.app access).

Quick Start

  1. Install

    bash
    go install github.com/boutquin/mcp-server-email/cmd/mcp-server-email@latest
  2. Create a config file (~/.config/mcp-email/accounts.json)

    For well-known providers (Gmail, Outlook, Yahoo, iCloud, Fastmail, Zoho), host and port are auto-detected from the email domain — just provide credentials:

    json
    [  {    "id": "hello",    "email": "[email protected]",    "username": "[email protected]",    "password": "app-password-here"  }]

    For custom mail servers, specify host and port explicitly:

    json
    [  {    "id": "work",    "email": "[email protected]",    "imap_host": "mail.example.com",    "imap_port": 993,    "smtp_host": "mail.example.com",    "smtp_port": 465,    "username": "[email protected]",    "password": "app-password-here"  }]
    bash
    chmod 600 ~/.config/mcp-email/accounts.json
  3. Add to Claude Code (~/.claude.json)

    json
    {  "mcpServers": {    "email": {      "command": "mcp-server-email",      "env": {        "EMAIL_CONFIG_FILE": "~/.config/mcp-email/accounts.json"      }    }  }}
  4. Restart Claude Code — the email_* tools are now available.

Installation

Go Install (recommended for Go developers)

bash
go install github.com/boutquin/mcp-server-email/cmd/mcp-server-email@latest

Homebrew (macOS/Linux)

bash
brew install boutquin/tap/mcp-server-email

Binary Download

Download pre-built binaries for your platform from GitHub Releases.

Available for: Linux (amd64, arm64), macOS (amd64, arm64), Windows (amd64, arm64).

Docker

bash
docker run --rm \  -e EMAIL_ACCOUNTS='[{"id":"main","email":"[email protected]","imap_host":"mail.example.com","imap_port":993,"smtp_host":"mail.example.com","smtp_port":465,"username":"[email protected]","password":"app-password"}]' \  ghcr.io/boutquin/mcp-server-email:latest

MCP Bundle (Claude Desktop)

Download the .mcpb file from Releases and open in Claude Desktop.

Build from source

bash
git clone https://github.com/boutquin/mcp-server-email.gitcd mcp-server-emailgo build -o mcp-server-email ./cmd/mcp-server-email

Configuration

Accounts are loaded once at startup. Changes require a server restart.

Config file vs environment variable

ApproachBest for
EMAIL_CONFIG_FILE — path to a JSON fileProduction use. File can be permission-locked (chmod 600)
EMAIL_ACCOUNTS — inline JSON in env varTesting, CI, or containerized deployments

If both are set, EMAIL_ACCOUNTS takes precedence.

Account JSON schema

json
[  {    "id": "hello",    "email": "[email protected]",    "imap_host": "mail.example.com",    "imap_port": 993,    "smtp_host": "mail.example.com",    "smtp_port": 465,    "username": "[email protected]",    "password": "app-password-here"  }]
FieldRequiredDescription
idYesUnique account identifier
emailYesEmail address
imap_hostNo*IMAP server hostname
imap_portNo*IMAP port (993 = implicit TLS, 143 = STARTTLS)
smtp_hostNo*SMTP server hostname
smtp_portNo*SMTP port (465 = implicit TLS, 587 = STARTTLS)
usernameYesLogin username
passwordYes**App password or account password
use_starttlsNoOverride TLS auto-detection (true/false)
insecure_skip_verifyNoSkip TLS certificate verification (dev/testing)
auth_methodNo"password" (default) or "oauth2"
oauth_client_idNoOAuth2 client ID (required when auth_method is "oauth2")
oauth_client_secretNoOAuth2 client secret
oauth_token_fileNoOverride token file path

*Host and port are auto-detected for well-known providers (see below). Required for custom servers. **Not required when using OAuth2 authentication.

Provider auto-detection

When imap_host/smtp_host are omitted, the server detects settings from the email domain:

ProviderDomainsIMAPSMTP
Gmailgmail.com, googlemail.comimap.gmail.com:993smtp.gmail.com:587
Outlookoutlook.com, hotmail.com, live.comoutlook.office365.com:993smtp.office365.com:587
Yahooyahoo.comimap.mail.yahoo.com:993smtp.mail.yahoo.com:587
iCloudicloud.com, me.com, mac.comimap.mail.me.com:993smtp.mail.me.com:587
Fastmailfastmail.com, fastmail.fmimap.fastmail.com:993smtp.fastmail.com:587
Zohozoho.com, zohomail.comimap.zoho.com:993smtp.zoho.com:587

Explicit host/port in the config always takes precedence over auto-detection.

TLS modes

TLS mode is auto-detected from port:

PortProtocolMode
993IMAPImplicit TLS
143IMAPSTARTTLS
465SMTPImplicit TLS
587SMTPSTARTTLS

Override with "use_starttls": true or "use_starttls": false in the account object. Omit for auto-detection (recommended).

OAuth2 authentication

For providers that support it (Gmail, Outlook), you can use OAuth2 instead of app passwords. This uses the device code flow (RFC 8628) — no browser redirect needed.

  1. Create OAuth2 credentials in the provider's developer console (Google Cloud Console or Azure AD)

  2. Configure the account with auth_method: "oauth2":

    json
    [  {    "id": "gmail",    "email": "[email protected]",    "username": "[email protected]",    "auth_method": "oauth2",    "oauth_client_id": "your-client-id.apps.googleusercontent.com",    "oauth_client_secret": "your-client-secret"  }]
  3. On first connection, the server initiates the device code flow — printing a verification URL and code to stderr. Visit the URL and enter the code to authorize.

  4. Tokens are persisted in ~/.config/mcp-email/tokens/ and automatically refreshed. Subsequent connections reuse the stored token without re-authorization.

Supported OAuth2 providers: Gmail (gmail.com, googlemail.com) and Outlook (outlook.com, hotmail.com, live.com).

Environment variables

VariableRequiredDefaultDescription
EMAIL_CONFIG_FILEYes*—Path to JSON config file
EMAIL_ACCOUNTSYes*—JSON array of account configs (inline)
EMAIL_DEFAULT_ACCOUNTNoFirst accountDefault account ID
EMAIL_IMAP_TIMEOUT_MSNo30000IMAP operation timeout (ms)
EMAIL_SMTP_TIMEOUT_MSNo30000SMTP operation timeout (ms)
EMAIL_IMAP_RATE_LIMITNo60IMAP requests/minute/account
EMAIL_SMTP_RATE_LIMITNo100SMTP sends/hour/account
MAX_ATTACHMENT_SIZE_MBNo18Max size per attachment (MB)
MAX_TOTAL_ATTACHMENT_SIZE_MBNo18Max total attachment size per message (MB)
MAX_DOWNLOAD_SIZE_MBNo25Max attachment download size (MB)
EMAIL_POOL_CLOSE_TIMEOUT_MSNo5000Pool close timeout (ms)
EMAIL_DEBUGNofalseDebug logging to stderr
LOG_LEVELNoinfoLog level: debug, info, warn, error
LOG_FORMATNojsonLog format: json or text

*One of EMAIL_CONFIG_FILE or EMAIL_ACCOUNTS is required.

Config file permissions

The config file contains account passwords. Always restrict access:

bash
chmod 600 ~/.config/mcp-email/accounts.json

Tools (22)

Account & folder tools

ToolDescriptionKey params
email_accountsList configured accounts with connection status—
email_foldersList all folders with unread/total countsaccount?
email_folder_createCreate new foldername, account?

Message listing

ToolDescriptionKey params
email_listList messages in folderfolder?, limit?, offset?, includeBody?, account?
email_unreadList unread messagesfolder?, limit?, includeBody?, account?
email_searchSearch subject and bodyquery, from?, to?, since?, before?, folder?, limit?, includeBody?, account?

Message operations

ToolDescriptionKey params
email_getGet full message by IDid
email_read_bodyRead email body with paginationid, offset?, limit?, format?
email_moveMove message to folderid, destination
email_copyCopy message to folderid, destination
email_deleteDelete message (trash or permanent expunge)id, permanent?
email_mark_readMark as read/unreadid, read
email_flagFlag/unflag messageid, flagged
email_replyReply to a message (sets In-Reply-To, References, quotes body)id, body, all?, cc?, bcc?, isHtml?, account?
email_forwardForward a message (re-attaches original attachments)id, to, body?, cc?, bcc?, isHtml?, account?
email_batchBatch operations on multiple messagesaction, ids, destination?, permanent?, read?, flagged?

Attachments & threads

ToolDescriptionKey params
email_attachment_listList attachments on a messageid
email_attachment_getDownload attachment by indexid, index, saveTo?
email_threadGet conversation thread (searches across INBOX, Sent, Archive, and All Mail)id

Send & drafts

ToolDescriptionKey params
email_sendSend via SMTP with optional attachmentsto, subject, body, cc?, bcc?, replyTo?, isHtml?, attachments?, account?
email_draft_createSave draft with optional attachmentsto?, subject?, body?, cc?, bcc?, isHtml?, attachments?, account?
email_draft_sendSend existing draftid

All optional account params default to the configured default account.

Search

email_search searches both subject and body using IMAP SEARCH OR (SUBJECT "q") (BODY "q").

Optional filters narrow the candidate set server-side before body scanning:

FilterFormatExample
fromEmail address or name"[email protected]"
toEmail address or name"[email protected]"
sinceYYYY-MM-DD"2026-01-01"
beforeYYYY-MM-DD"2026-02-01"

The existing operation timeout (default 30s) prevents hung body searches on large mailboxes.

Attachments

email_send and email_draft_create accept an attachments parameter — an array of file references on the server host:

json
{  "attachments": [    {"path": "/tmp/report.pdf"},    {"path": "/tmp/data.csv", "filename": "Q1-data.csv", "content_type": "text/csv"}  ]}
ParameterRequiredDescription
pathYesAbsolute file path on the server host
filenameNoOverride display filename (defaults to basename of path)
content_typeNoMIME type (auto-detected from file extension if omitted)

Limits (defaults): 18 MB per file, 18 MB total (pre-base64 encoding; stays under 25 MB SMTP cap after encoding). Configurable via MAX_ATTACHMENT_SIZE_MB and MAX_TOTAL_ATTACHMENT_SIZE_MB environment variables.

Download limit: Attachment downloads (email_attachment_get) are capped at 25 MB by default, configurable via MAX_DOWNLOAD_SIZE_MB.

Validation failures (missing file, non-absolute path, size exceeded) return INVALID_ARGUMENT.

Message IDs

Message IDs are composite strings encoding account, mailbox, and UID:

{account}:{mailbox}:{uid}

Example: hello:INBOX:12345

All CRUD tools (email_get, email_move, email_copy, email_delete, email_mark_read, email_flag, email_draft_send) extract the account and folder from the ID — no separate params needed.

Error Codes

All errors are returned as MCP tool errors with a structured code prefix:

CodeMeaning
AUTH_FAILEDIMAP/SMTP authentication failed
CONNECTION_FAILEDCannot connect to server
ACCOUNT_NOT_FOUNDUnknown account ID
FOLDER_NOT_FOUNDMailbox doesn't exist
MESSAGE_NOT_FOUNDUID not found in mailbox
INVALID_ARGUMENTMissing/invalid parameter (including attachment validation)
TIMEOUTOperation timed out
INTERNALUnexpected server error

Resources

URIDescription
email://statusServer version, account connection state, rate limit configuration

Comparison with apple-bridge

This server and apple-bridge share an Email model and parameter semantics (limit, includeBody, folder, query) so LLMs can work with both interchangeably. Key differences:

Aspectmcp-server-emailapple-bridge
TransportIMAP/SMTP (remote)Mail.app (local)
Tool prefixemail_*mail_*
Message ID{account}:{mailbox}:{uid}RFC 5322 Message-ID
Folder createSupportedNot supported (Mail.app requires UI)
Copy messageSupportedNot supported
Draft sendSupportedNot supported (Mail.app uses compose UI)
Attachments (send)File path on server hostNot yet supported

Development

Prerequisites

Build

bash
go build ./...

Unit tests

bash
make test# or: go test -race -count=1 ./...

Unit tests use mock implementations of the imap.Operations and smtp.Operations interfaces — no live mail server needed.

Benchmarks

bash
go test -bench=. -benchmem ./...
BenchmarkPackageWhat it measures
BenchmarkPoolGetReleaseimapConnection pool acquire/release cycle
BenchmarkExtractAttachmentsimapMIME attachment extraction
BenchmarkHtmlToTexttoolsHTML-to-plain-text conversion
BenchmarkLimiterAllowretryRate limiter (sequential)
BenchmarkLimiterAllow_ParallelretryRate limiter (concurrent)

Fuzz testing

Fuzz targets ship with seed corpora in testdata/fuzz/ directories. Run a specific target:

bash
go test -fuzz=FuzzParseMessageID ./internal/models/ -fuzztime=30s
TargetPackageWhat it fuzzes
FuzzBuildSearchCriteriaimapIMAP search query builder
FuzzExtractAttachmentByIndeximapAttachment index boundary handling
FuzzExtractContentTypeimapMIME content-type parser
FuzzParseMessageIDmodelsComposite message ID codec
FuzzHtmlToTexttoolsHTML-to-text sanitizer
FuzzSplitAddressestoolsEmail address list splitter

Lint

bash
make lint# or: golangci-lint run ./...

Integration tests

Integration tests exercise the full IMAP/SMTP stack against a real mail server. They are isolated behind the integration build tag and never run during go test ./....

Mail server: Greenmail

Tests use Greenmail, a lightweight Java mail server packaged as a Docker image. Key details that affect how you run it:

SettingValueWhy it matters
IMAPS port3993Greenmail's SSL IMAP port (not 993). The code auto-detects TLS from port number, so tests explicitly set UseStartTLS=false to force implicit TLS on this non-standard port.
SMTPS port3465Greenmail's SSL SMTP port (not 465). Same UseStartTLS=false override.
Bind address0.0.0.0Greenmail defaults to 127.0.0.1 inside the container, which makes Docker port-mapping silently fail (connections get EOF). You must pass -Dgreenmail.hostname=0.0.0.0.
UsernametestGreenmail uses the local part only (before @) as the login username — not the full email address. If the user is [email protected], the IMAP/SMTP username is test.
TLS certificatesSelf-signedGreenmail generates self-signed certs. Tests set InsecureSkipVerify: true in the account config to accept them.
Quick start (one command)
bash
make test-integration

This starts a Greenmail container, runs all integration tests, then tears down the container — regardless of pass/fail.

Manual step-by-step

If you need to iterate on tests without restarting the container each time:

  1. Start Greenmail

    bash
    docker run -d --name greenmail \  -p 3465:3465 -p 3993:3993 \  -e "GREENMAIL_OPTS=-Dgreenmail.setup.test.all -Dgreenmail.users=test:[email protected] -Dgreenmail.hostname=0.0.0.0" \  greenmail/standalone:2.1.0

    Wait ~3 seconds for the JVM to start.

  2. Run integration tests

    bash
    TEST_IMAP_HOST=localhost TEST_IMAP_PORT=3993 \TEST_SMTP_HOST=localhost TEST_SMTP_PORT=3465 \[email protected] TEST_PASSWORD=password \  go test -tags=integration -race -v ./...
  3. Tear down when done

    bash
    docker stop greenmail && docker rm greenmail
Test environment variables
VariableRequiredDefaultDescription
TEST_IMAP_HOSTYes*—IMAP server hostname. Tests skip if unset.
TEST_IMAP_PORTNo3993IMAPS port
TEST_SMTP_HOSTYes*—SMTP server hostname. Tests skip if unset.
TEST_SMTP_PORTNo3465SMTPS port
TEST_EMAILNo[email protected]Email address for the test account
TEST_USERNAMENoLocal part of TEST_EMAILIMAP/SMTP login username (Greenmail uses local part only)
TEST_PASSWORDNopasswordAccount password

*If the corresponding HOST variable is unset, that test file's tests are skipped with a message (not failed).

What the tests cover

IMAP (internal/imap/integration_test.go — 7 tests):

TestWhat it verifies
ConnectAndListFoldersTLS connection, authentication, folder listing, INBOX exists
SendAndListMessagesSMTP send → IMAP receive round-trip, body content match
SearchBySubjectIMAP SEARCH by subject string
DeleteMessagePermanentFlag as deleted + expunge, verify message is gone
MoveMessageIMAP MOVE to another folder (skips if server lacks MOVE extension)
DraftWorkflowSaveDraft → GetDraft → DeleteDraft lifecycle (skips if no APPENDUID)
MarkReadAndFlagSet read/flagged flags, verify via GetMessage

SMTP (internal/smtp/integration_test.go — 4 tests):

TestWhat it verifies
SendPlainTextPlain-text email delivery, body content verified via IMAP
SendHTMLHTML email delivery, Content-Type verified as text/html
SendWithAttachmentMultipart MIME with attachment, filename verified in metadata
RateLimitTokenConsumptionSending consumes a rate-limit token
Troubleshooting
SymptomCauseFix
EOF or connection reset on connectGreenmail bound to 127.0.0.1 inside containerAdd -Dgreenmail.hostname=0.0.0.0 to GREENMAIL_OPTS
TLS handshake failure / certificate errorSelf-signed certs rejectedTest configs already set InsecureSkipVerify: true — if writing new tests, do the same
Invalid login/passwordUsing full email as usernameGreenmail expects the local part only (test, not [email protected]). Set TEST_USERNAME or let it default.
STARTTLS error on port 3993Using STARTTLS on an implicit-TLS portTest configs set UseStartTLS=false. Don't use ports 3143/3025 (plain, no TLS).
Tests skip with "not set"TEST_IMAP_HOST / TEST_SMTP_HOST not exportedExport the env vars or use the make test-integration target
MoveMessage test skipsGreenmail may not support MOVEExpected — test uses t.Skip()
CI

Integration tests run automatically in GitHub Actions via the integration job in .github/workflows/ci.yml. The job uses a Greenmail service container — no manual Docker setup needed. See the workflow file for the exact configuration.

Coverage

To generate a combined unit + integration coverage report:

bash
# With Greenmail running (see above):TEST_IMAP_HOST=localhost TEST_IMAP_PORT=3993 \TEST_SMTP_HOST=localhost TEST_SMTP_PORT=3465 \[email protected] TEST_PASSWORD=password \  go test -tags=integration -race -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | tail -1   # total percentagego tool cover -html=coverage.out             # open in browser

Architecture

mcp-server-email/├── cmd/mcp-server-email/     # Entry point└── internal/    ├── auth/                 # OAuth2 device code flow, XOAUTH2 SASL, token store    ├── config/               # Multi-account configuration, provider auto-detection    ├── imap/                 # IMAP client (split by concern), connection pool, Operations interface    │   ├── client.go         # Client struct, lifecycle, shared helpers    │   ├── client_messages.go # List, search, get, attachments    │   ├── client_folders.go  # Folder ops, role cache    │   ├── client_drafts.go   # Draft save/get/delete    │   ├── client_flags.go    # Flags, move, copy, delete    │   └── pool.go           # Connection pool with configurable close timeout    ├── log/                  # Structured logging (slog) initialization    ├── models/               # Email model, message ID codec, error types    ├── resources/            # email://status resource    ├── retry/                # Token-bucket rate limiter    ├── smtp/                 # SMTP client, Operations interface    └── tools/                # 22 MCP tool handlers + registration

Tool handlers are decoupled from IMAP/SMTP clients via the imap.Operations and smtp.Operations interfaces, enabling comprehensive unit testing with mocks.

Dependencies

This project uses go-imap v2 (currently v2.0.0-beta.8). The v2 API is not yet stable — breaking changes may occur before the v2.0.0 release. We pin the exact version in go.mod and will upgrade promptly when stable is released.

License

MIT

Source: README.md at commit 0ba695c

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

1
  1. v1.0.1LatestSep 16, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts