mailindex-mcp について
mailindex-mcpは、AIエージェント向けのセルフホスト型メールサービスで、メールをローカルにインデックス化して検索や操作に利用できます。添付ファイル内のテキストも検索対象にし、アカウントごとの検索範囲を示します。
主な機能
- 01PostgreSQLによる全文メール検索
- 02添付ファイルのテキストをインデックス化
- 03メールの移動、フラグ付け、削除、下書き
- 04アカウントごとの検索範囲表示
- 05メールボックスのバックグラウンド同期
- 06所有者単位で分離されたマルチテナント
活用シーン
- 01古いメールの添付ファイル内の語句を検索
- 02条件に合うニュースレターを一括削除
- 03検索条件に基づいてメールを移動またはフラグ付け
- 04インデックス済みメールの範囲を確認
MCP 対応
ストリーミング可能なHTTP経由で、MCPクライアントにメールアカウント管理、検索、操作のツールを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm -e EMAILSERVER_DATABASE_URL -e EMAILSERVER_AUTH_MODE -e EMAILSERVER_API_TOKEN -e CREDENTIAL_ENCRYPTION_KEY ghcr.io/alexanderkrauck/mailindex-mcp:0.3.1EMAILSERVER_DATABASE_URLPostgreSQL connection URL. This server keeps its own index and does not run without a database; the repository ships docker-compose files that start one for you.EMAILSERVER_AUTH_MODEシークレットHow callers are authenticated: development (no auth, loopback only), single_user (static bearer token), or google (multi-user OAuth).EMAILSERVER_API_TOKENシークレットBearer token required in single_user mode. At least 32 characters, or the server refuses to start.CREDENTIAL_ENCRYPTION_KEYシークレットKey used to encrypt stored mailbox credentials at rest. Generate with: openssl rand -base64 32ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/alexanderkrauck/mailindex-mcp:0.3.1Streamable HTTPツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mailindex-mcp
AI エージェント向けのメールクライアントです。Thunderbird でできること、つまり検索、移動、マーク、削除、下書き作成を、自分のマシン上の自分専用インデックスを使って行えます。
多くのメール MCP サーバーは、質問のたびに IMAP へそのまま転送します。「直近 10 件のメッセージを表示して」という問いには答えられても、それ以外ではうまく機能しません。SEARCH の挙動はプロバイダーごとに一貫せず、添付ファイルの中身を確認できず、検索対象をすべて検索したかどうかをモデルに伝えることもなく、見つかったメールに対して何も操作できません。
そのため、受信トレイから 8,000 件のニュースレターを削除するようアシスタントに頼むと、ツール呼び出しが 8,000 回必要だと言われ、ウェブインターフェイスで手作業するよう勧められます。
この MCP サーバーはメールのコピーを独自に保持し、メールに対して操作できます。
delete_mail(account_id=3, participants=["[email protected]", ...])
→ matched: 4147, affected: 4147, to: "[Google Mail]/Bin"
1 回の呼び出し、1 つの接続、6 秒です。検索ボックスとメールクライアントの違いはここにあります。

6 つのアカウントにまたがる約 59,000 件のメッセージを含む、稼働中のインデックスに対する実際の質問です。ぼかしてある文字列は、実際の申請に関する事件番号です。
他のメール MCP サーバーではなく、これを選ぶ理由
| このプロジェクト | 一般的なメール MCP | |
|---|---|---|
| メールに変更を加えられるか | 検索に基づいて、移動、マーク、削除、下書き作成、フォルダー管理を一括で実行 | 読み取り専用、または呼び出しごとに 1 件のメッセージ |
| 検索 | 独自の PostgreSQL インデックス、GIN 全文検索、多言語対応の語幹化 | 呼び出しごとに IMAP の SEARCH を実行 |
| 結果の完全性 | 正確な total_count、署名付きカーソル、アカウントごとの網羅状況 | フォルダーから返された内容のみ |
| 添付ファイル | 同期時にテキストを抽出してインデックス化(PDF、DOCX、XLSX、PPTX、OCR) | Base64 形式でモデルのコンテキストに含める、またはまったく扱わない |
| 添付ファイルのバイナリ | 保存せず、5 分間有効な署名付き URL を通じて再取得 | ディスクに保存、または本文に埋め込み |
| トランスポート | リモート HTTP エンドポイント | マシン上のローカル stdio プロセス |
| クライアント側のセットアップ | URL を貼り付けるだけ | ランタイムをインストールし、設定 JSON を編集して、認証情報をローカルに保存 |
| ユーザー | マルチテナント対応で、すべての行を所有者の範囲に限定 | 単一ユーザー |
| ツールの構成 | 22 個のツール、すべてにアノテーション付き | 40 個以上の場合が多い |
必要なリソースについて、最初に明示します。 PostgreSQL とコンテナを実行します。インデックスのサイズはメッセージ 1,000 件あたり約 28 MBで、50,000 件のアーカイブは約 1.5 GB になります。画像のサイズは 1.25 GB です。OCR 言語データが含まれているためです。初回同期ではすべてのメッセージを一度ダウンロードします。残りがバックグラウンドで処理されている間も、到着済みのメールを検索できます。また、各アカウントの網羅状況が個別に報告されるため、モデルはまだ確認できていない範囲を把握できます。
その代わり、添付ファイル内のテキストも含め、何年も前のメールについてアシスタントに質問し、その回答に基づいて操作できます。データがマシンの外に出ることはありません。
クイックスタート
5分で完了します。ローカルのみで、アカウントを作成する必要もありません。
git clone https://github.com/alexanderkrauck/mailindex-mcp.git
cd mailindex-mcp
cp .env.example .env
docker compose up -d
ここにあるすべての compose ファイルは
ghcr.io/alexanderkrauck/mailindex-mcp
を取得します。amd64 と arm64 に対応しているため、Raspberry Pi でも Apple Silicon 搭載マシンでも同じように動作します。
代わりに自分でコンパイルするには、いずれかのファイルに --build を追加してください。イメージには OCR 言語データが含まれているため、数分かかります。
重要な用途ではバージョンを固定してください。これは 1.0 より前のバージョンです。リリース間でスキーマが変更され、起動時にマイグレーションが自動実行されるため、latest を固定せずに使うと、再起動したときにデータベースがマイグレーションされる可能性があります。
MAILINDEX_IMAGE=ghcr.io/alexanderkrauck/mailindex-mcp:0.1.0 docker compose up -d
MAILINDEX_IMAGE は3つの compose ファイルすべてで使用でき、コマンドラインではなく .env に設定します。イメージタグに v がないことに注意してください。git タグは v0.1.0、イメージは 0.1.0 で、0.1 はそのマイナーバージョンの最新パッチに追従します。
起動したことを確認します。
curl http://localhost:8002/api/v1/health
デフォルトの development モードは認証なしで、Docker は 127.0.0.1 のみにバインドします。自分のマシンで実際に試すことだけを目的とした設定です。
AI クライアントを接続する
メールボックスを接続する前に、まずこれを行ってください。メールボックスを接続する方法もこれです。
claude mcp add --transport http mail http://localhost:8002/mcp
その他のクライアントでは、ストリーミング可能な HTTP 経由で http://localhost:8002/mcp に接続してください。その後、受信トレイの検索では見つけにくそうな内容を尋ねてみてください。3年前に届いたメールに含まれる PDF 内の語句などがよいでしょう。
メールボックスを接続する
クライアントに頼むだけです。add_mail_account はツールの1つです。
メールボックス [email protected] を接続してください。IMAP は imap.example.com、SMTP は smtp.example.com です
パスワードは渡さないでください。 パスワードなしで依頼すると、パスワードだけを尋ねるフォームへの有効期限付き URL が返されます。ブラウザーからサーバーに直接送信されるため、モデルを経由することも、AI プロバイダーが保存する会話の記録に残ることもありません。秘密情報の送信を拒否する ChatGPT などのクライアントで使える唯一の方法です。
プロバイダーのセキュリティ設定からアプリパスワードを取得して使ってください。アカウントのログインパスワードは絶対に使わないでください。Gmail の場合は、代わりに Gmail OAuth フローを開始するよう依頼してください。Gmail API を使用するため、ラベルの変更にもより適切に対応できます。Microsoft 365 と Exchange Online ではパスワードは使えないため、Microsoft メールボックスへの接続を依頼してください。Gmail と同様に、Microsoft にサインインして、サーバーに許可する操作を承認します(セットアップ)。
または、シェルを使う場合は HTTP 経由で接続
curl -X POST http://localhost:8002/api/v1/accounts \
-H 'Content-Type: application/json' \
-d '{
"name": "personal",
"account_name": "[email protected]",
"username": "[email protected]",
"password": "your-app-password",
"host": "imap.example.com",
"port": 993,
"smtp_host": "smtp.example.com",
"smtp_port": 465
}'
同期は自動的に開始され、バックグラウンドで実行されます。検索はすでに取り込まれたデータを対象に動作します。**「これまでにメールをどのくらいインデックス化しましたか?」**と尋ねれば、アカウントごとに正確な状況を教えてくれます。各検索で対象範囲を報告するため、完了したかのように装うことはありません。
どのセットアップを選べばよいですか?
このツールを使い始める人の状況は4通りあります。それぞれに最も手短で正直な方法をご紹介します。
「本当に使えるか確かめたい」— 5分、ノートパソコンで
すべてローカルで動作し、登録は不要です。
git clone https://github.com/alexanderkrauck/mailindex-mcp.git
cd mailindex-mcp && cp .env.example .env
docker compose up -d # pulls the published image
claude mcp add --transport http mail http://localhost:8002/mcp
アプリパスワードを使ってメールボックスを追加し(下記参照)、同期を待ってから、受信トレイの検索では見つけにくそうな内容をクライアントに尋ねてください。必要なもの: Docker と、プロバイダーのアプリパスワード。所要時間の目安: 初回のビルドでは psycopg2 をコンパイルし、約130 MBの OCR 言語データを取得するため、数秒ではなく数分かかります。認証は無効で、Docker は 127.0.0.1 のみにバインドします。この用途では問題ありませんが、絶対に外部へ公開しないでください。
「スマートフォンとノートパソコンで、毎日使いたい」— 1人、1台のサーバー
小規模な VPS とドメインが必要です。Caddy が TLS 証明書を取得します。
cat > .env <<'ENV'
EMAILSERVER_DOMAIN=mail.example.com
POSTGRES_PASSWORD=...
EMAILSERVER_API_TOKEN=...
CREDENTIAL_ENCRYPTION_KEY=...
SESSION_SECRET=...
ENV
docker compose -f docker-compose.single-user.yml up -d
すべての値は openssl rand -base64 32 で生成してください。API トークンは 32 文字以上でなければならず、そうでない場合、サーバーは起動を拒否します。必要なもの: 約 1,000 件のメッセージにつき約 25 MB のディスク容量と約 2 GB の RAM を備えた VPS、ドメイン、メールボックスごとのアプリパスワードです。期待できること: Authorization ヘッダーを送信するものであれば何でも動作します。Claude Code、Cursor、mcp-remote などが該当します。OAuth をネゴシエートし、静的トークンを送信できない claude.ai や ChatGPT のウェブコネクターでは動作しません。これらを使いたい場合は、次の構成を選んでください。
「家族/チームのメンバーがそれぞれ自分専用の環境を持てるようにしたい」— 数人
Google OAuth を使うため、各ユーザーは自分のアカウントでサインインし、自分のメールボックスだけを参照できます。
docker compose -f docker-compose.production.yml up -d
必要なもの: 上記すべてに加えて、デプロイモードに記載されている 2 つのリダイレクト URI を設定した Google Cloud OAuth のウェブアプリケーションです。期待できること: 同意画面を申請するまでは未確認アプリの警告が表示され、未確認の間はユーザー数が 100 人に制限されます。この規模であれば、どちらも問題にはなりません。
REGISTRATION_MODE=allowlistを設定してください。openにすると、世界中のどの Google アカウントでもサーバーに登録してメールボックスを接続できるようになり、ALLOWED_GOOGLE_EMAILSは読み込まれません。テナント分離によって、他人があなたのメールを見られない状態は保たれますが、その人たちはあなたのサーバーにアカウントを作成できます。公開ホストで最も間違えやすい設定です。
「社内インフラとして使えないか?」— 500 人規模の企業
率直に言うと、まだです。何が足りないのかを曖昧にせず、正確に説明します。
現時点でできること。 上記の Google の設定を使い、1 チーム向けにパイロットとして運用できます。マルチテナント機能は実際に動作し、クエリレベルで強制されます。すべての行が所有者ごとに分離され、所有者は呼び出し元が指定する値ではなく、認証済みトークンから決まります。メールボックスの認証情報は保存時に暗号化され、返されることはありません。添付ファイルのバイナリデータは保存されません。
全社展開の妨げとなる点。
| 課題 | 重要な理由 |
|---|---|
| Google OIDC のみでログイン可能 | Entra ID、Okta、汎用 OIDC、SAML には対応していません。ディレクトリが Google でなければ、誰もサインインできません。 |
| プロビジョニングやデプロビジョニングがない | ユーザーは自分で登録します。SCIM やグループマッピングはなく、退職者のインデックスを無効にする方法もありません。 |
| 監査ログのエクスポートがない | 送信は監査されますが、読み取り、検索、メールボックスへの書き込みは監査されません。ほとんどのコンプライアンス審査で求められる項目です。 |
| Postgres が 1 台、コンテナが 1 つ | HA、リードレプリカ、水平スケールする同期ワーカーはありません。バックアップと復元は pg_dump で行います。 |
| 添付ファイルのテキストがデータベースに保存される | 保持期間やリーガルホールドは、自分で構築した仕組みに依存します。 |
これに該当する方には、パイロット運用は本当に価値があります。社内には「自社のハードウェア上にある自社メールの検索インデックスを、アシスタントが利用できる」と説明するのが誠実です。「承認済みプラットフォーム」と説明するのは適切ではありません。
どの構成を選んでも、メールボックスの認証情報は別途必要です
このサーバーにサインインしても、何かへのアクセス権が付与されるわけではありません。メールボックスはそれぞれ個別に接続します。
- Gmail — IMAP 経由のアプリパスワード、または Gmail API を利用する
begin_gmail_connectionによる Gmail OAuth フローを使用します。後者のほうがラベルの変更にも適切に対応できます。 - Microsoft 365/Exchange Online —
begin_microsoft_connectionによる OAuth のみです。Microsoft はこれらのメールボックスに対して、パスワードもアプリパスワードも受け付けません。Microsoft 365 への接続を参照してください。 - その他すべて — プロバイダーのセキュリティ設定で発行したアプリパスワードを使用してください。ログインパスワードは絶対に使わないでください。
Microsoft 365/Exchange Online への接続
Microsoft のメールボックスも Gmail と同じ方法で接続します。Microsoft にサインインして、このサーバーに許可する操作を承認すると、サーバーはその結果として発行されるリフレッシュトークンのみを暗号化して保存します。サーバーごとに一度、アプリ登録を作成する必要があります。
1. アプリを登録します。 Microsoft Entra 管理センターで、アプリの登録 → 新規登録を選択します。
- サポートされているアカウントの種類: 任意の組織ディレクトリ内のアカウントおよび個人用 Microsoft アカウントを選択すると、どの Microsoft 365 メールボックスでも接続できます。自組織のアカウントのみを対象とする場合は、この組織ディレクトリのみを選び、以下の
MICROSOFT_TENANTにテナント ID を設定してください。この種類のアプリではcommonは使用できません。 - リダイレクト URI、プラットフォームは Web:
https://mail.example.com/api/v1/accounts/microsoft/callback(ローカルで試す場合は、http://localhost:8002/api/v1/accounts/microsoft/callbackも追加してください。)
2. API のアクセス許可 → アクセス許可の追加で、次の委任されたアクセス許可を追加します。
| API | アクセス許可 | 用途 |
|---|---|---|
| Office 365 Exchange Online(組織で使用している API 内) | IMAP.AccessAsUser.All | IMAP 経由でのメールの読み取り、検索、移動、フラグ設定 |
| Microsoft Graph | Mail.Send | メールの送信 |
| Microsoft Graph | User.Read, openid, profile, email, offline_access | サインインしたメールボックスの特定と、サインイン状態の維持 |
ユーザーが自分でこれらを承認できるかどうかは、テナントのユーザーの同意設定によります。制限されている場合、接続ページに、まず管理者がアプリを承認する必要があると表示されます。このページの管理者の同意を付与を選択すると、全員に対して承認できます。
3. クライアントシークレットを作成します。 証明書とシークレットで作成し、値をすぐにコピーしてください。Microsoft で値が表示されるのは一度だけです。シークレットには有効期限があり、最長でも 24 か月です。有効期限をカレンダーに登録してください。環境変数のシークレットを置き換えてサーバーを再起動するだけで更新できます。保存済みの認証情報にシークレットは含まれず、メールボックスを再接続する必要もありません。
4. 登録情報をサーバーに設定してから、再起動します。
MICROSOFT_CLIENT_ID=<Application (client) ID>
MICROSOFT_CLIENT_SECRET=<the secret's Value>
MICROSOFT_TENANT=common # or your tenant ID
(docker-compose.yml では、同じ 3 つの設定を EMAILSERVER_ プレフィックス付きで読み込みます。)
5. メールボックスを接続します。 アシスタントに Microsoft のメールボックスへの接続を依頼し、返されたリンクを開いてアカウントを選択し、承認します。続いて表示されるページで、メールボックスが利用可能かどうか、利用できない場合はその理由を確認できます。
知っておくべきこと:
- 送信には SMTP ではなく Microsoft Graph を使用します。 Exchange Online では、組織全体で SMTP AUTH が標準で無効になっています。有効にするかどうかは、その組織の管理者が判断するセキュリティ上の決定事項です。Graph ではユーザー本人の同意だけで済みます。Exchange は送信したメッセージを自動的に 送信済みアイテム に保存し、その
Message-IDも保持するため、送信済みメッセージが二重にインデックス化されることはありません。返信はスレッド化され、Bccの受信者は他の人から見えません。 - Exchange では、メッセージごとに本文は 1 つだけ保持されます。 テキストと HTML を送信すると、受信されるのは HTML です。検索には影響ありません。インデックス作成時に HTML からテキストが抽出されます。
- カレンダー、連絡先、タスク、ノート、ジャーナルは、Exchange ではメールフォルダーとして表示されます。英語、ドイツ語、フランス語、スペイン語、イタリア語、オランダ語、ポルトガル語のメールボックスでは、これらは同期対象と
list_mail_foldersから除外されます。それ以外の言語では、サーバーがログにその旨を記録し、すべてのフォルダーをインデックス化します。除外するフォルダーはEMAILSERVER_EXCLUDED_SYNC_FOLDERS='["Kalendarz","Kontakty"]'で指定してください。 - IMAP はメールボックスごとに無効にできます。 無効の場合は接続ページにその旨が表示されます。管理者は
Set-CASMailbox -Identity [email protected] -ImapEnabled $trueを実行して有効にできます。 - アクセスの取り消し。 ここでメールボックスを削除すると、保存済みトークンは削除されますが、Microsoft 側でのアクセス許可は取り消されません。取り消すには、Entra のエンタープライズ アプリケーションでアプリの同意を削除してください。
- 個人用の Outlook.com アカウントでも同じ手順を使用できますが、動作確認済みなのは Exchange Online の職場または学校のメールボックスのみです。
デプロイモード
EMAILSERVER_AUTH_MODE で、呼び出し元の認証方法を選択します。メールボックスの認証情報は、常にこれとは別です。
| モード | 呼び出せるユーザー | 必要なもの | 対応するクライアント |
|---|---|---|---|
development | ループバック上のすべてのユーザー | なし | ローカルクライアントのみ |
single_user | 静的ベアラートークンを持つ1人の所有者 | トークン、ドメイン | Claude Code、Cursor、mcp-remote、ヘッダーを送信できるもの |
google | Google OAuth + 動的クライアント登録を介した複数のユーザー | Google Cloud OAuth クライアント、ドメイン | Claude.ai と ChatGPT のウェブコネクター、および上記すべて |
本番環境、シングルユーザー、Google プロジェクトなし
実際にデプロイするための最短ルートです。Caddy が TLS を取得し、更新します。
cat > .env <<'ENV'
EMAILSERVER_DOMAIN=mail.example.com
POSTGRES_PASSWORD=...
EMAILSERVER_API_TOKEN=...
CREDENTIAL_ENCRYPTION_KEY=...
SESSION_SECRET=...
ENV
docker compose -f docker-compose.single-user.yml up -d
各シークレットは openssl rand -base64 32 で生成してください。EMAILSERVER_API_TOKEN は
32 文字以上である必要があります。満たさない場合、サーバーは起動を拒否します。
claude mcp add --transport http mail https://mail.example.com/mcp \
--header "Authorization: Bearer $EMAILSERVER_API_TOKEN"
本番環境、複数ユーザー、Google OAuth
OAuth をネゴシエートし、静的ヘッダーを送信できない Claude.ai や ChatGPT のウェブコネクターを利用する場合に必要です。
Google OAuth のウェブアプリケーションを作成し、次のように設定します。
- 承認済み JavaScript オリジン:
https://mail.example.com - 承認済みリダイレクト URI:
https://mail.example.com/auth/callbackhttps://mail.example.com/api/v1/accounts/gmail/callback
AI ベンダーのコールバックを Google クライアントに追加しないでください。MCP サーバーは
https://claude.ai/api/mcp/auth_callback と https://chatgpt.com/connector/oauth/*
を自身で許可し、認証完了後、クライアントにリダイレクトします。
cat > .env <<'ENV'
EMAILSERVER_DOMAIN=mail.example.com
POSTGRES_PASSWORD=...
GOOGLE_CLIENT_ID=123.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=...
JWT_SIGNING_KEY=...
CREDENTIAL_ENCRYPTION_KEY=...
SESSION_SECRET=...
REGISTRATION_MODE=allowlist
ALLOWED_GOOGLE_EMAILS=["[email protected]"]
ENV
docker compose -f docker-compose.production.yml up -d
クライアントに https://mail.example.com/mcp を追加します。OAuth チャレンジを受け取り、認証メタデータを検出してコールバックを登録し、ユーザーを Google の認証フローに進ませます。
Google について、次の2点に注意してください。審査のために同意画面を送信するまでは未確認アプリの警告が表示されること、また、未確認の間はユーザー数が100人に制限されることです。個人または家族でのデプロイでは、いずれも問題になりません。
既存のシングルオーナー環境をアップグレードする場合:CLAIM_LEGACY_ACCOUNTS_ON_FIRST_LOGIN=true を設定し、意図した所有者だけを許可リストに登録します。そのユーザーに一度ログインして既存のアカウントを引き継いでもらい、その後 false に戻します。
セキュリティモデル
- Google OpenID Connect は、安定した
subクレームを使ってアプリケーションユーザーを識別します。 - 各ユーザーは、複数の Gmail、Microsoft 365、Zoho、または汎用 IMAP/SMTP アカウントを所有します。
- メールボックスの認証情報はログイン ID とは別に管理され、保存時に暗号化されます。
- MCP は認証済みトークンから所有者を特定します。呼び出し元が所有者 ID を指定することはありません。
- メールボックスのパスワードは、ツールおよび API への書き込み専用入力であり、返されることはありません。
- 添付ファイルの元のバイナリは保存されません。署名付き URL を使って、必要に応じて再取得します。
- 開発モードは認証なしであり、ループバックにバインドしたままにする必要があります。
サインインしても、メールボックスへのアクセス権は付与されません。Gmail と Microsoft 365 は、それぞれ別の OAuth 同意フローで接続します。Zoho と汎用 IMAP では、プロバイダーのアプリパスワードを使用します。Microsoft のメールボックスで保存される認証情報は、リフレッシュトークンのみです。アプリ登録のシークレットはサーバーの環境内に保持されます。
MCP ツール
読み取り専用、破壊的操作、オープンワールドに関するヒントがそれぞれ付与された22個のツールを提供します。これは検索ボックスではなく、メールクライアントです。Thunderbird でできることはすべて、フォルダー一覧の代わりにインデックスを使って、ここでも実行できます。
| ツール | |
|---|---|
list_mail_accounts | アカウント、機密情報を含まない設定、保存済みメッセージ数の正確な値 |
add_mail_account | IMAP/SMTP メールボックスを追加します |
update_mail_account | 1 つのメールボックスの設定を変更します |
delete_mail_account | アカウントと、そこからインデックス化されたすべてのデータを削除します。プロバイダー側には何も残りません |
begin_mail_account_password_setup | 有効期間の短い、パスワード専用のブラウザーフォーム |
begin_gmail_connection | Google の同意取得に使用する、5 分間有効な署名付き URL |
begin_microsoft_connection | Microsoft の同意取得に使用する、5 分間有効な署名付き URL |
search_mail | 網羅的な語句検索 |
search_mail_regex | 範囲を限定した正規表現検索 |
get_mail | 1 件のメッセージと、範囲を限定した本文 |
get_thread | 信頼度付きの再構成されたスレッド |
get_attachment | メタデータ、抽出テキスト、有効期限付きのダウンロード URL |
begin_attachment_upload | 新しいファイルデータ用の署名付き領域。添付またはインライン埋め込みが可能です |
send_mail | 所有する添付ファイルを含めて、メールを送信または返信します |
list_mail_folders | 1 つのメールボックスのフォルダー、宣言された役割、インデックス化された件数 |
create_mail_folder | フォルダーを作成して購読します |
rename_mail_folder | メールと子フォルダーを保持したまま、フォルダー名を変更します |
delete_mail_folder | フォルダーを削除します。最初に中身をゴミ箱へ移動します |
mark_mail | 既読状態とフラグ状態を一括で設定または解除します |
move_mail | メールを別のフォルダーへ一括で移動します |
delete_mail | メールを一括でゴミ箱へ移動します。permanent はゴミ箱内のメールにのみ使用できます |
save_draft | メールボックスの下書きフォルダーに下書きを保存します |
単独の接続テストと手動同期は、意図的に MCP の対象外としています。
一括操作。 mark_mail、move_mail、delete_mail は search_mail と同じ方法でメッセージを選択します。email_ids を渡すか、同じフィルターを渡して一致するすべてのメッセージを操作します。1 回の呼び出しで接続を 1 つ開き、フォルダーごとにコマンドを 1 つ実行するため、8,000 件のニュースレターを処理する場合も、8,000 回ではなく 1 回の呼び出しで済みます。各応答では matched と affected が報告され、上限に達して処理が途中で終了した場合は truncated が設定されるため、一部だけ処理されたバッチを完了済みと誤認することはありません。ID もフィルターも指定しない呼び出しは、「メールボックス全体」として扱うのではなく、拒否されます。
Gmail。 Gmail にはフォルダーではなくラベルがあるため、優先順位(TRASH > SPAM > DRAFT > INBOX > SENT)に基づいて、ラベルから単一の保存場所を割り当てます。これらのいずれのラベルも持たないメッセージには ARCHIVE を割り当てます。この割り当てにより、フォルダー単位の検索、ゴミ箱内メッセージの除外、書き込みをプロバイダー間で同じように行えます。Gmail のメッセージは UID ではなくプロバイダー ID で指定し、移動する際は移動元のラベルを 1 つ削除して別のラベルを 1 つ追加します。SENT と DRAFT は Gmail が割り当てるラベルであり、削除されることはありません。フォルダーの作成、名前変更、削除は拒否され、その理由も示されます。
書き込み。 書き込みはすべて、まずメールボックスに対して実行し、サーバーの確認後にのみローカルへ記録されます。そのため、インデックスに実行されていない変更が記録されることはありません。書き込みは、アカウント行ではなく (host, port, username) をキーとして、同期処理と同じリースを保持します。これは、2 つのアカウントが同じ物理メールボックスを指す場合があり、フォルダーの調査中に、フォルダー名が付いていない EXPUNGE が発生すると、調査対象のシーケンス番号が変わるためです。書き込みでは、ゴミ箱内のメッセージではなく、現在のメッセージを対象にします。delete_mail はメールをゴミ箱へ移動し、完全に削除するには、明示的な呼び出しをもう 1 回行う必要があります。
パスワード。 add_mail_account と update_mail_account では、書き込み専用のパスワードを任意で指定できます。クライアントが機密情報を送信しない場合は、パスワードを省略して返されたセットアップ URL を開くか、begin_mail_account_password_setup を呼び出してください。リンク先のフォームでは、パスワードだけを入力します。接続テストに失敗しても、設定と暗号化された認証情報は保持されるため、再入力せずに設定を修正できます。
アカウントの削除。 delete_mail_account は2回呼び出します。最初の呼び出しで account_id のみを指定すると、削除される内容が表示されます。2回目は、confirm_name がアカウント名と完全に一致する場合に限り削除を実行します。古いパスワードアカウントと新しい OAuth 接続で同じメールボックスを共有できるため、照合するのはアドレスではなく名前です。削除されるのは、保存された認証情報、設定、インデックス化されたすべてのメッセージと抽出済みの添付ファイルのテキスト、同期状態です。プロバイダー側では何も変更されないため、後からメールボックスを再接続でき、その場合は最初からインデックス化されます。付与したアクセス権の取り消し(Google アカウントの権限、Microsoft My Apps)は、引き続き所有者が行う必要があります。このアカウントから送信されたメールの記録は、アカウントとの関連付けを解除して保持されます。アカウントへの書き込み中の同期ステップがあれば完了を待ちます(最大30秒)。その同期の残りの処理はブロックされます。ステップがまだ書き込み中の場合、またはアカウントから送信処理が開始され、その記録が完了していない場合、応答は retry_after を含む CONFLICT となり、何も削除されません。削除処理に先を越された送信は、何も送信せずに失敗します。DELETE /api/v1/accounts/{id} はこれとは異なり、アカウントを無効化してデータを保持します。
検索。 search_mail と search_mail_regex は、total_count、raw_count、returned_count、has_more、署名付きの next_cursor を返します。すべての結果を取得するには、has_more が false になるまで同じフィルターと next_cursor を使い続けます。重複排除のデフォルトは exact です。RFC Message-ID の値が同じメールをグループ化しつつ、すべてのソースアカウントとメッセージ ID を保持します。mirror は正規化された本文の複製もグループ化し、none は保存されているすべての行を返します。応答には、一致したフィールド、参加者ドメインのファセット、アカウントごとの同期範囲も含まれます。
語幹処理。 match のデフォルトは stemmed で、単語の活用形も検索します。実際の52,000件のメールボックスでは、invoices のヒット数は104件から1,016件に、Verträge は133件から1,168件に増加します。代償は精度です。たとえば meeting は meet にも一致します。そのため、語幹処理で範囲が広がる注文番号、識別子、姓には match="exact" を指定してください。どちらのモードもインデックス化されており、すべての応答で実行されたモードを報告します。
既読状態。 is_unread、is_flagged、is_answered は、プロバイダーから同期されたフラグで絞り込みます。これらは三値で扱われます。プロバイダーからフラグが一度も報告されていないメッセージは、true にも false にも一致しません。検索では、該当するメッセージが何件除外されたかを示す FLAG_STATE_UNKNOWN 警告が返されるため、それらを既読として黙って報告することはありません。Gmail は返信済みラベルを公開していないため、Gmail OAuth アカウントでは is_answered は常に不明です。フラグは定期的なリコンサイラーで更新されるため、メールボックスへの反映には最大で EMAILSERVER_DELETION_RECONCILE_INTERVAL の遅れが生じます。
get_mail と get_thread はデフォルトで長さを制限したプレーンテキストを返します。HTML は明示的に要求する必要があります。send_mail は所有する attachment_ids を受け付け、送信前にプロバイダーから各添付ファイルの元のバイナリを再取得します。
HTTP API
GET /api/v1/docs では OpenAPI ブラウザーを表示します。アカウント、メッセージ、添付ファイル、同期、送信のすべての参照は、所有者のスコープに限定されます。
GET /api/v1/me
GET /api/v1/accounts
POST /api/v1/accounts
GET /api/v1/accounts/{id}
PATCH /api/v1/accounts/{id}
DELETE /api/v1/accounts/{id}
POST /api/v1/accounts/{id}/test
POST /api/v1/accounts/{id}/sync
GET /api/v1/accounts/gmail/connect
GET /api/v1/accounts/microsoft/connect
GET /api/v1/emails/search
GET /api/v1/emails/search/regex
GET /api/v1/emails/{id}
GET /api/v1/attachments/{id}
POST /api/v1/send
同期の仕組み
検索をベストエフォートではなく、信頼できるものにする仕組み:
-
Alembic は、起動時にバージョン管理された、データを保持するマイグレーションを適用します。
-
メッセージの識別には、メッセージとともに受け渡される正規化済みの RFC
Message-IDを使用します。保存場所はmessage_placementsに別途格納し、フォルダーごとに1行を作成します。そのため、上流でメッセージが移動すると、削除して再作成するのではなく、保存場所が移動します。Gmail API のメッセージでは、ラベルの変更後も維持されるプロバイダー固有の ID を使用します。 -
IMAP カーソルには UID、UIDVALIDITY、フォルダーを永続化します。バックフィルは古いものから順に実行し、各サイクルの処理件数に上限を設けます。カーソルはバッチのコミット後にのみ進み、UIDVALIDITY が変更された場合は、そのフォルダーだけをリセットします。
-
Gmail OAuth アカウントでは、再開可能なバックフィルに
messages.list/getを、差分変更の取得にhistory.listを使用します。履歴 ID が期限切れになった場合は、上流での削除を照合する前に、世代マーカー付きの完全同期を実行します。 -
Microsoft 365 アカウントは、ほかの IMAP アカウントと同様に OAuth トークンを使って IMAP 経由で同期し、Microsoft Graph 経由で送信します。「送信済みアイテム」にあるメッセージのコピーと INBOX に配信されたメッセージは、Exchange では MIME の構成が異なりますが、
Message-IDによって同一のメッセージとして扱われます。そのため、添付ファイルは位置ではなくチェックサムを使って再特定します。 -
メタデータのみを定期的に照合し、フラグと上流での削除を反映します。チェックポイントを永続化するため、再起動後に全件を再スキャンする必要はありません。
-
アカウントの処理にはグローバルな同時実行数の上限を設け、データベースの有効期限付きリースで保護します。そのため、2つのワーカーが同じメールボックスを同期することはなく、ワーカーがクラッシュしてもロックが永久に残ることはありません。
-
PostgreSQL の GIN インデックスは、本文と添付ファイルの語彙検索を支えます。同じテキストを、設定された各言語ごとに1回ずつインデックス化し、結合した1つのベクトルとして保存します。そのため、同じメールボックス内のドイツ語の請求書と英語のニュースレターを、どちらも正しく語幹処理できます。同一の語彙素は統合されるため、インデックスの容量は、隣接する語幹処理なしのインデックスとほぼ同じです。
EMAILSERVER_SEARCH_TEXT_CONFIGSで言語を選択します。デフォルトは["simple", "english", "german"]です。これを変更する場合は対応するインデックスが必要で、マイグレーションでは次のインデックスを1回だけ構築します。CREATE INDEX CONCURRENTLY ix_email_logs_search_fts_simple_english_french ON email_logs USING gin (( to_tsvector('simple', coalesce(sender,'')||' '||coalesce(recipient,'')||' '||coalesce(subject,'')||' '||coalesce(body_plain,'')) || to_tsvector('english', coalesce(sender,'')||' '||coalesce(recipient,'')||' '||coalesce(subject,'')||' '||coalesce(body_plain,'')) || to_tsvector('french', coalesce(sender,'')||' '||coalesce(recipient,'')||' '||coalesce(subject,'')||' '||coalesce(body_plain,'')) ));このインデックスがなくても、保存されているすべての本文をスキャンすることで、検索結果は同じです。
-
プロバイダーのフラグは、同期時に
is_unread、is_flagged、is_answeredへ正規化します。IMAP はメッセージが既読だったことを報告し、Gmail API はメッセージが未読だったことを報告しますが、その形式は異なります。どちらの形式も、そのままではフィルターに使用できません。プロバイダーからフラグが一度も報告されていないメッセージは、既読にするのではなく null のままにします。 -
添付ファイルのテキストは同期時に抽出します。画像の添付ファイルは、画像内にインストールされているすべての言語で OCR を実行します。tesseract に言語を指定しないと英語を前提として、アクセント付き文字のスクリプトを誤って読み取るためです。ドイツ語のウムラウトは
dürfen Fußbodenではなくdirfen Fubodenと認識されてしまいます。そのため、テキストはインデックス化されても、ページ上の単語を検索して見つけることはできません。イメージにはeng deu fra ita spa nld porが含まれます。docker build --build-arg TESSERACT_LANGS="eng deu jpn"を使って追加または削減し、実行時にEMAILSERVER_OCR_LANGUAGES=deu+engを指定して対象を絞り、網羅性と速度を調整できます。 -
年が2611年である場合のように、解析できても妥当でない
Dateヘッダーは保存せず破棄します。検索ではこの列を使って並べ替えとページ分割を行うためです。Gmail はプロバイダーのタイムスタンプを代わりに使用し、IMAP では null のままにします。scripts/repair_email_dates.pyは、このチェックが導入される前に書き込まれた値をクリアします。 -
正規表現検索には、対象範囲、パターン、結果数、ステートメントの実行時間にそれぞれ上限を設けています。
-
送信では冪等性キーをサポートし、所有者の範囲に限定した監査レコードを追加します。
開発
テストスイートは単独で実行できます。インメモリの SQLite と一時データディレクトリを使用するため、PostgreSQL、Docker、ネットワークは必要ありません。
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
pytest -q
ruff check .
テナントの分離、ツールの公開とアノテーション、静的トークンと OAuth による認証、暗号化、トークンの改ざん、添付ファイルの上限、IMAP のカーソル動作、Gmail の履歴動作、OAuth チャレンジのメタデータを網羅しています。
scripts/verify_postgres_search.py は別のもので、データが投入済みのデプロイに対して実行する読み取り専用のスモークテストです。実データで網羅的な検索、重複排除、ファセット、正規表現が正しく動作することを確認します。
docker compose exec email-server python -m scripts.verify_postgres_search
ライセンス
MIT です。LICENSE を参照してください。
コントリビューションとセキュリティ
- CONTRIBUTING.md — 実行方法と、レビューで意見を交わすほどこのプロジェクトが重視していることについて説明しています。
- SECURITY.md — 保護対象と対象外、および脆弱性を非公開で報告する方法について説明しています。
- CHANGELOG.md — リリース間の変更点を記載しています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。