mcpField
A
Agent Email List — Free SMTP Relay for AI Agents

Agent Email List — Free SMTP Relay for AI Agents

リモート
ai.agentemaillist.comコミュニケーション16 週間前に更新
リポジトリ公式サイト

Agent Email List — Free SMTP Relay for AI Agents について

AI エージェントと開発者向けのメールサービスで、SMTP リレー、REST API、受信メール機能、MCP サーバーを備えています。エージェントは MCP 経由で認証情報なしにアカウントを作成して API キーを取得できますが、送信には DNS 検証済みドメインと自動ウォームアップが必要です。

主な機能

  1. 01認証情報なしの MCP アカウント作成
  2. 02SMTP または REST API によるメール送信
  3. 03受信メールの Webhook 転送
  4. 04ドメインごとの DKIM 署名
  5. 05自動送信ウォームアップ
  6. 06配信イベントと署名付き Webhook

活用シーン

  1. 01エージェントによるアカウント作成と API キー取得
  2. 02SMTP または REST によるトランザクションメール送信
  3. 03受信メールの Webhook 転送
  4. 04メール配信イベントの追跡

MCP 対応

リモート MCP サーバーは、アカウント作成、送信用ドメイン管理、メール送信、上限確認、メールと配信イベントの参照ツールを提供します。`create_account` に認証情報は不要で、以降のツールには取得した API キーが必要です。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://ai.agentemaillist.com/mcp
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

8
パラメーター型必須説明
namestringいいえ任意の表示名。
emailstringはいアカウントの連絡先アドレス。
passwordstringはい12 文字以上。ウェブコンソールへのサインインにのみ必要です。

詳細

AI エージェント向けの無料 SMTP リレーとメール API

AI エージェントが自分で登録できるメールサービスです。 クレジットカードも、営業担当者との通話も、企業アカウントを持つ人間も必要ありません。無料で、MCP サーバー、Mailgun 形式の REST API、DKIM 署名、自動送信ウォームアップ、受信メールを利用できます。

ai.agentemaillist.com で公開中 · ソース公開 · Elixir と Phoenix で構築


AI エージェント向け:まずはこちら

これはリモート MCP サーバーです。追加すると、サービス全体をツールとして利用できます。

https://ai.agentemaillist.com/mcp

HTTP POST による JSON-RPC 2.0 です。重要なのは次の点です。

create_account に認証情報は必要ありません。 このサーバーを見つけたばかりのエージェントがこれを呼び出すと、API キーを受け取り、すぐにメールを送信できます。他のあらゆるトランザクションメールプロバイダーでは、支払いフォームや本人確認でエージェントの利用が止まります。このサービスではその必要がありません。

利用を始める前に、何が使えるか確認できます。

curl -X POST https://ai.agentemaillist.com/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
ツール
ツール機能キーが必要
create_accountアカウントを開設し、API キーを取得しますいいえ
add_domain送信ドメインを登録し、DNS レコードを取得しますはい
verify_domainDNS を再確認し、ドメインを有効化しますはい
send_emailメッセージを送信します。費用のかからないテストモードもありますはい
list_domainsドメインの一覧と、それぞれが今日送信できる量を表示しますはい
get_sending_limits今日の上限と、上限が引き上げられる条件を表示しますはい
list_messages送信済みメールと受信メールを表示しますはい
get_delivery_eventsメッセージに実際に起きたことを表示しますはい

最初の呼び出しの後は、毎回 Authorization: Bearer <key> としてキーを送信してください。

利用を止める2つの条件

送信する前に、ドメインの DNS 検証が必要です。 DNS の公開には通常、レジストラーにアクセスできる人間が必要です。そのため、早めに始めてください。検証が済むまでは、送信のたびにその理由を詳しく説明する拒否応答が返されます。

検証したばかりのドメインは、1日あたり10通から始まり、実績に応じて上限が上がります。 最初から大量送信するドメインは受信側でフィルタリングされるためです。送信途中で初めて知ることがないよう、一括送信を計画する前に get_sending_limits を呼び出してください。

最初の送信では test_mode: true を使用してください。スクリーニングを含むパイプライン全体を実行しますが、何も送信せず、1日の送信枠も消費しません。

予期せず上限に達しないための制限事項
制限数値
IP アドレスあたりのアカウント数1時間あたり5件、1日あたり20件
アカウントあたりのドメイン数3件。いずれかのドメインを検証すると50件まで増加
アカウントあたりの API リクエスト数1分あたり600件 — 送信数ではなく、リクエスト頻度の上限です
ドメインあたりの送信数1日あたり10通から始まり、ウォームアップの段階に応じて増加
スクリーニングによる拒否24時間以内に8件に達すると送信が一時停止し、自動的に解除されます

get_sending_limits はドメインの指定なしで、これらすべての最新状況を返します。すべての 429 には Retry-After が含まれます。2つ目のアカウントを作っても上限は増えません。アカウント数は IP アドレス単位で制限されるため、どちらも同じ枠を消費します。


開発者向け:REST API

Mailgun 形式なので、ほとんどの Mailgun クライアントライブラリはベース URL を変更するだけで、そのまま利用できます。

# An account and an API key, in one request
curl -X POST https://ai.agentemaillist.com/v1/accounts \
  -d '[email protected]' -d 'password=a sufficiently long password'

# Send
curl -X POST https://ai.agentemaillist.com/v3/yourdomain.com/messages \
  --user 'api:YOUR_KEY' \
  -F from='[email protected]' \
  -F [email protected] \
  -F subject='Hello' \
  -F text='Hello there.'

機械可読形式の完全な API リファレンスはこちらです。 ai.agentemaillist.com/llms.txt。 送信制限は実行中のサービスから生成されるため、記載されたままの数値ではなく、実際に適用される制限を確認できます。

ドメインを手動で設定するためのウェブコンソールもあります。 ai.agentemaillist.com/signup


機能

  • 無料の SMTP リレーとメール API。 試用期間もクレジットカードも必要ありません。
  • SMTP または REST で送信。 ドメインを追加すると SMTP 認証情報が発行されます。REST API は Mailgun 互換の形式です。
  • メールの受信にも対応。 ポート 25 で実際の SMTP サーバーが稼働し、受信メールを Webhook にルーティングします。送信だけではありません。
  • DKIM 署名。 緩和された正規化方式による RSA-SHA256 を使用し、ドメインごとにキーペアを生成します。
  • 送信ウォームアップを自動化。 1 日 10 通から無制限までの段階的な上限が公開されているため、新しいドメインの評価を高め、フィルタリングを避けられます。
  • コンテンツのスクリーニング。 送信メールを拒否し、受信メールを迷惑メールとして振り分けます。
  • すべてのメッセージの配信イベントと署名付き Webhook。
  • 実績に応じて引き上げられる上限。 一律の上限ではありません。使い捨てアカウントでは大規模に偽装できないドメインの検証によって、利用枠に余裕が生まれます。

代替サービスとの比較

これMailgunSendGridAmazon SES
エージェントだけで登録できるはいいいえいいえいいえ
MCP サーバーはいいいえいいえいいえ
無料枠無料、期限なし制限あり試用後は有料メッセージごとに課金
受信メールはいはいはいS3 経由
セルフホスト可能はいいいえいいえいいえ

Brevo、Resend、SMTP2GO を含む詳細な比較は、 ai.agentemaillist.com/free-smtp-relay をご覧ください。

セルフホスティング

Phoenix アプリケーションと Postgres データベースで構成されています。コマンドを 1 つ実行するだけでデプロイできます。

sudo bash deploy/deploy.sh --domain mail.yourcompany.com --email [email protected]

これにより、Postgres と nginx がインストールされ、リリースがビルドされ、TLS 証明書が発行され、systemd とファイアウォールが設定されます。DEPLOY.md では、実行される各手順と、失敗した場合の対処方法を説明しています。

ライセンス

現在ライセンスは設定されていないため、すべての権利が留保されています。利用または貢献をご希望の場合はお知らせください。ライセンスを追加します。

Logan Besecker がメンテナンスしています。質問、バグ報告、営業目的の連絡など、[email protected] または [email protected] までお気軽にご連絡ください。


実行と運用

以下はすべて、自分でこのアプリケーションを実行する方向けの内容です。

実行

mix setup            # deps, database, migrations
mix phx.server       # http://localhost:4005
mix test

デフォルトでは 4005 でリッスンします。このアプリケーションと並行して実行されるマシンでは、4000 から 4003 までがすでに使用されているためです。PORT で上書きできます。

データベース

プロジェクトのルートにある .env で、認証情報を一度だけ設定します。

cp .env.example .env

すべての mix コマンドがこのファイルを読み込むため、コマンドごとに入力し直す必要はありません。また、実際の環境変数が引き続き優先されます。たとえば、DATABASE_URL=... mix test は見たとおりに動作します。.env は Git で無視されます。設定可能な項目はすべて .env.example に記載されています。

このファイルがない場合、DATABASE_URL が設定されていればそれが優先されます。どの環境でも使用できる形式です。

DATABASE_URL=ecto://user:pass@localhost/email_provider_dev mix setup

それ以外の場合は、標準の PGUSER、PGPASSWORD、PGHOST、PGPORT、PGDATABASE の各変数が使われます。それらが設定されていない場合に限り、OS ユーザー名と同じ名前のロールがあるものと推測します。標準設定の Homebrew Postgres ではこのロールが用意されています。

OS ユーザーが root の場合、この推測では OS ユーザーを使いません。サーバーでは root で作業することが多く、root という Postgres ロールが存在することはほとんどありません。また、返される password authentication failed for user "root" というエラーは、実際にはどの認証情報を使うか指定されていないのに、認証情報の問題のように見えます。新しいサーバーでこのエラーが発生した場合は、DATABASE_URL を設定するか、ロールを作成してください。

sudo -u postgres psql -c "CREATE ROLE youruser LOGIN PASSWORD 'apassword' CREATEDB;"

実際のデプロイでは、開発用タスクである mix setup ではなく、MIX_ENV=prod と DATABASE_URL を指定してリリースを実行してください。すべての作業を 1 つのコマンドで行えます。DEPLOY.md では、実行される各手順を説明しています。

sudo bash deploy/deploy.sh --domain ai.agentemaillist.com --email [email protected]
ほかのサービスが稼働しているマシンの場合

mix setup は開発者向けのコマンドで、ほかのサービスへの配慮はありません。代わりに、こちらを使ってください。

DATABASE_URL=ecto://user:pass@localhost/email_provider_dev bin/setup-server

違いは3つあります。いずれも、mix setup がマシン上のほかのサービスに影響を及ぼす可能性があるものです。

ビルドに上限を設けます。 35個の依存関係と2つのC NIFをコンパイルすると、Elixirコンパイラーはスケジューラーごとに1つのプロセスを起動し、make はコアごとに1つのジョブを実行します。そのため、小規模なVPSでは、ビルドがマシン上で最もメモリを消費するプロセスになります。メモリが不足しても、カーネルがビルドを終了するとは限りません。OOMキラーは最も大きなプロセスを選びますが、それは通常、稼働中のアプリケーションです。このスクリプトはコンパイルを直列化し、rootとしてsystemdの下で実行する場合は、ハード上限のMemoryMaxを設定したスコープ内で実行します。これにより、メモリ不足で終了されるのはビルド自体になります。MEMORY_MAX=1Gで上書きできます。

接続数を10ではなく2に抑えます。 Postgresのmax_connectionsは固定値です。接続数が上限に達すると、すでに接続しているサービスも含め、すべてのクライアントに「申し訳ありません。すでにクライアントが多すぎます」と応答します。開発用プールのデフォルトは5になり、POOL_SIZEを読み取ります。このスクリプトでは2に設定します。

アプリケーションを起動しません。 mix setup はpriv/repo/seeds.exsを実行するために監督ツリー全体を起動し、その際にプールを開いて配信キューを起動します。mix setup.serverは、起動せずにデータベースを作成してマイグレーションを実行します。

mix setupの実行中に何かがすでに停止してしまった場合は、次のコマンドでどちらだったかを確認できます。

sudo dmesg -T | grep -i -A2 'killed process'
sudo grep -i "too many clients" /var/log/postgresql/*.log | tail
PGDATABASEについて

ホスト、ユーザー、パスワードは環境変数から読み取ります。データベース名は読み取りません。データベース名は認証情報ではなく、どのアプリケーションのデータかを示すものです。また、PGDATABASEは標準的なlibpq変数であり、ほかのサービスのために共有マシン上ですでにエクスポートされている場合があります。これを反映すると、mix ecto.migrateがそのサービスのデータベースを参照し、このアプリケーションのテーブルをその中に作成してしまいます。別のデータベースを使う場合は、DATABASE_URLに指定してください。

環境
変数意味デフォルト
OPENAI_API_KEYモデレーションエンドポイントのキーなし — スクリーニングをスキップ
SSA_UID利用状況レポート用のSeriouslySimpleAnalyticsアカウントID未設定 — レポートは送信されません
MODERATION_ENABLEDスクリーニングを完全に無効にするtrue
MODERATION_ON_ERRORスクリーニングに接続できない場合にフェイルクローズするにはblockallow
SMTP_RELAY送信メール用のスマートホスト未設定 — メールはpriv/local_mailに書き込まれます
SMTP_PORT / SMTP_USERNAME / SMTP_PASSWORDスマートホストの認証情報587、なし
SPF_HOSTSPFのincludeに顧客が指定するホストmail.example.com
MX_HOSTMXレコードで顧客が指定するホストmx.example.com

SMTP_RELAYが設定されていない場合、インターネットには何も送信されません。ローカル送信機能は各メッセージをディスクに書き込み、ログに記録します。これは意図的なデフォルト設定です。

アカウントとキー

curl -s localhost:4005/v1/accounts \
  -d [email protected] -d password='a sufficiently long password'

レスポンスにはAPIキーが含まれます。キーが表示されるのは一度だけです。SHA-256ハッシュのみが保存されるため、紛失したキーは照会するのではなくローテーションします。認証方法はどちらでも同じです。

curl -s --user 'api:ep_live_...' localhost:4005/v3/domains     # Mailgun style
curl -s -H 'Authorization: Bearer ep_live_...' localhost:4005/v3/domains

キーにはスコープ(messages:send、events:read、domains:writeなど)があります。スコープは各コントローラーで、保護対象のコードの近くにアクションごとに定義されています。

カスタムドメイン

ドメインを追加すると、RSA-2048のDKIMキーペアが生成されます。秘密鍵はデータベースに保存され、顧客が公開するのは公開鍵です。

curl -s --user 'api:KEY' localhost:4005/v3/domains -d name=mail.yourcompany.com

レスポンスに公開するレコードが一覧表示されます。その後、次を実行します。

curl -s -X PUT --user 'api:KEY' localhost:4005/v3/domains/mail.yourcompany.com/verify

SPFとDKIMの両方がDNSで確認されるまで、ドメインはunverifiedの状態です。また、activeになるまで送信できません。レコードが後で消失した場合はunverifiedに戻り、送信が停止します。MXは報告されますが必須ではありません。受信する場合にのみ必要です。

送信メールには、relaxed/relaxedの正規化を用いたRSA-SHA256署名が付与されます。そのため、リレーがヘッダーを再整形したり、末尾の空白を削除したりしても、署名は無効になりません。

送信

curl -s --user 'api:KEY' localhost:4005/v3/mail.yourcompany.com/messages \
  -F from='Ada <[email protected]>' \
  -F [email protected] \
  -F subject='Hello' \
  -F text='Hello there.' \
  -F o:tag=welcome \
  -F h:X-Campaign=spring \
  -F v:customer_id=42

fromアドレスは、そのキーで送信するドメインに属している必要があります。

エンドポイント

メッセージ
POST /v3/:domain/messages送信します。フィールドは以下のとおりです。
POST /v3/:domain/messages.mime事前に作成した MIME ドキュメントを送信します。引き続きスクリーニングされ、件数にもカウントされます。
GET /v3/:domain/messages一覧を取得します。folder と direction で絞り込めます。
GET /v3/domains/:domain/messages/:key保存されているメッセージを 1 件取得します。

送信フィールド:from、to、cc、bcc、subject、text、html、 o:tag、o:deliverytime、o:testmode、o:tracking-opens、 o:tracking-clicks、h:*(送信するヘッダー)、v:*(イベントに引き継がれ、イベントで返される変数)、recipient-variables、template、t:version、 t:variables。

o:deliverytime には RFC 2822 または ISO 8601 を指定でき、3 日先までに制限されます。 o:testmode を指定すると、メッセージを送信せずに受け付けて保存し、ウォームアップの上限も消費しません。recipient-variables を指定すると、1 件のリクエストが宛先ごとのメッセージに分割され、それぞれ固有の本文と Message-ID が割り当てられ、%recipient.name% が置換されます。

ドメイン

GET|POST /v3/domains · GET|PUT|DELETE /v3/domains/:domain · PUT /v3/domains/:domain/verify

レポート

GET /v3/:domain/events(event、recipient、tag、begin、 limit で絞り込み) · GET /v3/:domain/stats/total · GET /v3/:domain/tags · GET /v3/:domain/limits(ウォームアップの進行状況)

抑制リスト

GET|POST /v3/:domain/bounces · GET|DELETE /v3/:domain/bounces/:address。 unsubscribes と complaints も同じ形式です。すべての送信時に適用され、ハードバウンスは自動的に追加されます。

テンプレート

GET|POST /v3/:domain/templates · GET|DELETE /v3/:domain/templates/:name · POST /v3/:domain/templates/:name/versions

置換構文は {{name}} のみで、それ以外は使えません。式、インクルード、ループには対応していません。マルチテナントサービスでは、保存されたテンプレートは攻撃者が制御する入力であるため、エンジンがシンプルであるほど、影響範囲も小さくなります。不明なプレースホルダーは空欄にせず、そのまま表示されるため、タイプミスは気づかれないまま消えるのではなく、{{frist_name}} として表示されます。

ルート(受信)

GET|POST /v3/routes · GET|PUT|DELETE /v3/routes/:id

式:match_recipient("regex")、match_header("name", "regex")、 catch_all()。アクション:forward("https://…")、store()、stop()。優先度の高い順に処理され、stop() で処理を終了します。

Webhook

GET|POST /v3/domains/:domain/webhooks · GET|DELETE /v3/domains/:domain/webhooks/:id

ペイロードはドメインごとのシークレットを使って HMAC-SHA256(timestamp <> token, signing_key) で署名されます。そのため、シークレットが 1 つ漏洩しても、別の顧客のコールバックを偽造することはできません。キーは作成時に 1 度だけ返されます。

アドレス検証

GET /v4/address/validate?address=… — 構文と、リアルタイムの MX ルックアップを検証します。宛先サーバーに対して部分的な SMTP 会話を行うことで宛先を調べることはありません。それを行わないことで検証の精度を保ち、ディレクトリ収集のための偵察とも区別できます。

オペレーターダッシュボード

GET /admin — サービス全体の数値を表示します。アカウント、ドメイン、送受信メッセージ、ハードバウンス、配信停止、苦情、スクリーニング結果、ウォームアップ段階ごとの検証済みドメインの分布が含まれます。

これらの数値はすべてのアカウントにまたがり、顧客の認証情報で閲覧できるべきではないため、API キーではなく ADMIN_TOKEN で保護されています。トークンが設定されていない場合、ダッシュボードはまったく開けません。環境変数が未設定の場合の適切な動作です。ページ自体はトークンなしで表示されますが、数値は含まれません。トークンを使って GET /admin/stats を取得するため、招待されていないユーザーが URL にアクセスすると、数値ではなく認証を求める画面が表示されます。

表示されない数値が 1 つあります。コンテンツスクリーニングによって拒否された送信メッセージの数です。何かが保存される前に拒否されるため、行もイベントも残りません。ダッシュボードではこれを黙って省略せず、その旨を示します。

人向け

/signup、/login、続いて /domains、/send、/messages、/account を利用します。それぞれ固有の URL を持つ通常のサーバーサイドレンダリングページなので、リンクやブックマークに追加できます。

これらは別実装ではなく、JSON API と同じコンテキストを利用するクライアントです。フォームからドメインを追加する場合も、POST /v3/domains から追加する場合も、同じコードが実行されます。セッションに保持されるのはユーザー ID のみで、それ以外の情報は保持されません。リクエストごとに照会するため、アカウントの停止はセッションの有効期限を待たずにすぐ反映されます。

ランディングページで使われている文字列テンプレートではなく、HEEx を使用しています。これらのページではアドレス、ドメイン名、件名を表示するためです。これらはすべて顧客の入力であり、~H が出力時にエスケープします。

エージェント向け

GET /llms.txt は、API を説明するエージェント向けのファイルです。キーは必要ありません。稼働中のサービスからレンダリングされるため、記載される送信段階は、一度書かれてそのまま古くなった数値ではなく、実際に適用される段階です。テストでは、公開されている段階が EmailProvider.Warmup.stages/0 と一致することと、ファイルで案内されているすべてのエンドポイントが実際にルーティングされることを検証します。

/robots.txt はこのファイルを指しています。

受信

POST /v1/inbound/:domain は、MTA が受信メールを渡すエンドポイントです。生の message または解析済みフィールドを受け取ります。GET /v1/inbound/:domain/spam は、スクリーニングで振り分けられたメールの一覧を返します。

自動ウォームアップ

新しいドメインが最初から最大量で送信するとフィルタリングされるため、すべてのドメインは段階的に上限を引き上げていきます。段階はハードコードされておらず、config/config.exs で設定されています。

段階1 日あたりの上限次の段階に進む条件
1105 日間、それぞれ別の日に送信した
220この段階で 1,000 件を送信した
3100この段階でさらに 1,000 件を送信した
41,000この段階でさらに 10,000 件を送信した
5無制限—

知っておくとよい点が 2 つあります。

「送信した日数」とは、実際に送信した日数のことです。 ドメイン作成からの日数ではありません。1 週間使われていないドメインは、1 週間ウォームアップしたことにはなりません。

各段階の件数は、その段階独自の上限であり、累計ではありません。 ドメインは 20 件/日の段階で 1,000 件を送信すると、次に 100 件/日の段階に進み、そこでさらに 1,000 件を送信すると次の段階に進みます。累計件数は、最初の 5 日間にどれだけ送信したかによって変わります。段階 1 の上限まで送信したドメインは、すでに 50 件送信済みなので、各段階の累計到達件数は 50 件、1,050 件、2,050 件、12,050 件となります。一方、少しずつ送信したドメインは、各段階にもう少し早く到達します。GET /v3/:domain/limits は現在の段階での進捗を返します。この数値は「この上限であとどれくらい送信できるか」を示し、次の段階に進む累計件数もあわせて返します。

累計件数で段階を上げる場合は、config/config.exs で {:stage_volume, n} の代わりに {:lifetime_sent, n} を使用します。

送信前に容量を予約し、送信されなかった場合は予約を解除します。そのため、2 つの処理の間でクラッシュが発生しても、ドメインが上限を超えるのではなく、数件分の送信枠が失われるだけです。予約は単一のアトミックな upsert で行われるため、同時に到着した 2 つのリクエストがどちらも「今日の送信件数は 9 件」と読み取り、両方とも許可されることはありません。GET /v3/:domain/limits は現在の段階、本日の残り送信可能数、次の段階に進む条件を返します。送信が拒否された場合は、同じ詳細と retry_after_seconds を含む 429 が返されます。

ドメインごとに warmup_enabled でウォームアップを無効にできます。

コンテンツのスクリーニング

すべてのメッセージは、無料で呼び出せる OpenAI のモデレーションエンドポイントを通じてスクリーニングされます。その結果は、送信と受信で異なります。

  • 送信 — フラグが付いたコンテンツは、ネットワークに送信される前に、またウォームアップの許容量を消費する前に、403 で拒否されます。判定はメッセージに保存されるため、ログに 403 が記録されるだけでなく、あとから拒否の理由を説明できます。
  • 受信 — フラグが付いたコンテンツは配信されますが、inbox ではなく spam に振り分けられます。受信メールを破棄すると、誤検知によって実際のメッセージを失いますが、振り分ければその心配はありません。

モデレーションサービスに接続できない場合、デフォルトではメッセージを許可し、スクリーニングされていないものとして記録します。これにより、サードパーティの障害にサービスまで巻き込まれることを防ぎます。MODERATION_ON_ERROR=block を指定すると、フェイルクローズになります。「スクリーニング未実施」は「スクリーニング済みで問題なし」とは異なる状態です。チェック時刻を持たないため、後続の処理で両者を取り違えることはありません。

SMTP

このサービスは送信と受信の両方で SMTP を使用します。どちらのリスナーもデフォルトでは無効です。受信にはポート 25 が、直接送信には外向きのポート 25 が必要で、どちらもデフォルトではなく、明示的に選択する必要があります。

送信

優先順位順に、3 つの送信方法があります。

設定経路
SMTP_RELAY選択したスマートホスト経由で送信します。STARTTLS を要求し、証明書を検証します。
DIRECT_DELIVERY=trueスマートホストを介さず、各受信者の MX に直接送信します。
どちらも設定しないpriv/local_mail に書き込みます。マシンの外には送信されません。

直接配信では、受信者をドメインごとにグループ化し、各ドメインの MX レコードを検索して、優先順位順に接続を試みます。RFC 5321 の要件に従い、MX レコードが公開されていない場合は、そのドメイン自身の A レコードにフォールバックします。宛先から 5xx が返された場合は試行を終了します。それ以外の場合は次のホストに進みます。通常は、メールが不要なのではなく、そのホストに接続できないことを意味するためです。

ここでの TLS は、スマートホストへの接続より意図的に緩い設定です。日和見的で、検証は行いません。スマートホストは選択した単一のサーバーであり、一定の基準を求めることができます。一方、オープンなインターネットには、自己署名証明書や不一致のある証明書を使う受信サーバーが数多くあり、事前に検証する合意もありません。そのため、検証を必須にしても配信の安全性は高まらず、配信そのものが機能しなくなります。これは、ほかのすべての MTA が採用している方式であり、RFC 7435 が日和見的セキュリティと呼ぶものです。

Vultr を含め、ほとんどのホストでは外向きのポート 25 がブロックされています。 マシンでこの制限が解除されるまでは、直接配信は毎回タイムアウトします。サポートに連絡してブロックを解除してもらうか、スマートホストを使用してください。

受信

SMTP_RECEIVE_ENABLED=true を設定するとポート 25 が開き、ホスト対象のドメイン宛てのメールを受け付けます。SMTP_SUBMISSION_ENABLED=true を設定するとポート 587 が開きます。このポートでは、顧客自身のソフトウェアが、ドメインの追加時に発行された認証情報で認証してから、当サービス経由で送信します。送信は REST API と同じパイプラインを通るため、スクリーニング、抑制リスト、ウォームアップの段階はすべて引き続き適用されます。

重要なのはオープンリレーにしないことです。その制御は 1 つの関数、handle_RCPT/2 にあります。ポート 25 では、ホスト対象で有効なドメインの受信者のみを受け付け、それ以外には 550 5.7.1 を返します。ホスト対象のドメインがまだ検証されていない場合は、代わりに 450 を返します。これにより、顧客が DNS の設定を完了したあとに正規の送信者が再試行でき、永久に拒否されたと誤解せずに済みます。リレーが許可されるのは、セッションが認証された場合に限られます。これがサブミッションポートの目的であり、ポート 25 を使ってはならない理由です。AUTH はサブミッションポートでのみ通知されます。ポート 25 でも通知すると、あらゆる場所から顧客の SMTP パスワードを推測できるようになるためです。

ポート 25 を使うには、root 権限または CAP_NET_BIND_SERVICE が必要です。リスナーがバインドできない場合は、ログに記録してスキップします。その理由でアプリケーションの起動を中止することはありません。現在メールを受信できないプロバイダーでも、API を提供し、送信を続けられるようにするためです。

このデプロイに必要な DNS

ai.agentemaillist.com はすでにこのサーバーを指しています。以下はまだ設定されておらず、ドメインの検証とメール配信を機能させるために必要です。

種類名前値用途
TXTai.agentemaillist.comv=spf1 ip4:155.138.220.76 ~all顧客の SPF に含まれる include:ai.agentemaillist.com に、このマシンを許可させます
PTR155.138.220.76ai.agentemaillist.comDNS ではなく Vultr パネルで設定します。受信サーバーはこれを HELO と照合します

ルートドメインの既存の MX は、通常の @agentemaillist.com 宛てメールを Namecheap の転送に送っています。ここでの設定はすべて ai. 配下にあるため、既存の設定には影響しません。

配信の仕組み

配信は DELIVERY_CONCURRENCY の上限内で並行して実行されます。処理のほとんどは、DNS、TCP 接続、そしてインターネット上のサーバーとのやり取りの待ち時間です。そのため、1 件ずつ処理するとキューの進み方は最も遅い宛先の速度に左右され、1 台のサーバーが 30 秒かかれば、その後の処理もすべて滞ります。処理中の配信ごとにデータベース接続を 1 つ保持するため、上限は POOL_SIZE 未満に保たれます。

キューには別のブローカーではなく、メッセージテーブルを使用します。その代わりに数秒ごとのポーリングが必要になりますが、所有者が 1 つだけになる仕組みを実現できます。現在のステータスを条件とする UPDATE でメッセージを確保するため、同じメッセージを複数のノードが同時に取得しようとしても、成功するのは 1 つだけです。また、送信途中でノードが停止した場合も、古い確保状態を解消する処理によって行がキューに戻されます。5xx はメッセージを恒久的に失敗とし、アドレスを抑制対象にして、ウォームアップの割り当て分を返金します。4xx は最大 5 回まで再試行します。

Bcc はエンベロープには含めますが、ヘッダーには決して書き込みません。そのため、ブラインド受信者の情報は守られます。

レイアウト

lib/email_provider/
  accounts.ex          users, API keys, scopes
  domains.ex           registration, DKIM keys, DNS records, verification
  mail.ex              the send and receive pipeline
  warmup.ex            the ladder and its counters
  moderation.ex        OpenAI screening client
  suppressions.ex      bounces, unsubscribes, complaints
  templates.ex         stored bodies and substitution
  routes.ex            inbound matching and actions
  webhooks.ex          signed callbacks
  delivery/
    mime.ex            render RFC 5322
    dkim.ex            relaxed/relaxed RSA-SHA256 signing
    inbound.ex         parse MIME, decode RFC 2047 headers
    sender.ex          SMTP and local adapters
    queue.ex           claim and dispatch

テスト

mix test

テストスイートからネットワークに接続することはありません。スクリーニングは無効で、ディスパッチループは実行されず、送信側は一時ディレクトリに書き込みます。config/runtime.exs はテスト中、環境変数に基づく設定をスキップするため、開発者のマシンに残った無関係な変数によって、テストスイートが稼働中のリレーや有料 API を参照することはありません。

アカウントプロフィール

各アカウント所有者について、約 200 語の説明を継続的に作成します。メッセージがどちらかの方向に送受信されるたびに書き換えられます。入力元は次の 2 つです。

  • CSuiteFinder による情報補完検索:アカウント自身のアドレスを対象に、/csuitefinder/email/enrich 経由で検索します。提供元は検索結果ごとに料金を請求し、メールが 2 通届く間に役職が変わることはないため、結果はプロフィール行に 30 日間キャッシュされます。
  • メール活動の要約:送受信の量と方向、最初と最後の活動、活動日あたりの頻度、主なやり取り相手、最近の件名、送信ドメイン、タグ、そして長さに厳格な上限を設けた最近の本文の抜粋です。
curl -s --user 'api:KEY' localhost:4005/v1/profile
curl -s --user 'api:KEY' -X POST localhost:4005/v1/profile/refresh
curl -s --user 'api:KEY' localhost:4005/v1/profile/signals

保存される行には、説明に加えて、その作成元となった情報補完レコードと活動の要約も保持され、API はその 3 つすべてを返します。根拠を確認できない説明では、行動を起こしたり、修正したり、説明責任を果たしたりできません。

2 つのジェネレーター

EmailProvider.Profiles.Generator は設定に応じてジェネレーターを選択します。OpenAI キーがある場合はチャットモデルを使い、証拠に基づくこと、雇用主や役職を捏造しないこと、不確かな情報はその旨を明示すること、保護対象の特性について推測しないことを指示します。キーがない場合は、事実から説明文を組み立てる決定論的なビルダーにフォールバックします。モデルもネットワークも使いません。テストスイートはこの経路を実行するため、モックではなく実際の処理経路をテストでき、機能もそのまま使えます。

決定論的なジェネレーターはデータが裏付ける内容だけを述べ、そこで止めるため、記録が少ない場合は無理に文章を膨らませず、短い説明になります。200 語を目標にするのはモデルアダプターだけです。どちらも最大 200 語に収め、文の途中にならない箇所があれば文末で切り詰めます。

コスト

min_interval_secondsのデフォルト値は0です。これはメッセージごとに1回書き換えが行われ、モデルアダプターを使用する場合は、メッセージごとにモデルが1回呼び出されることを意味します。これは依頼されたとおりの動作ですが、コストが最も高い設定です。利用が多いアカウントで更新をまとめるには、config/config.exsで値を大きくしてください。すべてのメッセージは引き続きカウントされるため、次回の書き換えでは、前回からどれだけ処理できていなかったかがわかります。

この処理は送信経路とは別のタスクで実行されるため、エンリッチメント呼び出しもモデルも、APIリクエストに遅延を加えたり、配信を妨げたりすることはありません。失敗は行に記録され、呼び出し元には伝播しません。書き換えに失敗したプロフィールの説明は、以前のまま保持されます。

範囲

GET /v1/profileが返すのは、呼び出し元アカウント自身の説明のみです。他の人の説明を返すエンドポイントはありません。また、エンリッチメントの検索対象はアカウント所有者自身のアドレスであり、やり取りする相手のアドレスではありません。

以前ここに記載されていた説明では、私はこれを実装していないとしていました。実装を希望するとの確認をいただいたため、指定どおりに実装しました。コードではなくポリシーに関わるため、デフォルトに任せず、意識して決めておくべき点が2つあります。利用規約でアカウント所有者にこの機能の存在を伝えるかどうか、そして、アカウント所有者が自分について保持されている情報を閲覧したり、無効にしたりできるようにするかどうかです。後者を希望する場合は、EmailProvider.Profiles.enabled?/0と/v1/profileエンドポイントが、そのためのフックになります。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。