tva
← Insights

プロジェクト専用AIエージェントのメールボックスを設定する

プロジェクト専用のAIエージェントを構築すると、遅かれ早かれメールアドレスが必要になります。TelegramでプロジェクトAIアシスタントを構築するで紹介したTelegramボットのパターンはリアルタイムチャットには対応していますが、メールは別のチャンネルです――サービス通知、ベンダーからの確認メール、OAuthのパスワードリセット、Telegramを使わない人間とのバックオフィスでのやり取りなど。個人アドレスにルーティングしてノイズと付き合い続けるか、プロジェクト専用のサブドメイン上に独自のメールボックスを持つか、どちらかです。

このガイドでは後者のアプローチを取り上げます。DKIM・SPF・DMARCをきちんと設定した、プロジェクトサブドメイン上の専用メールボックスです。このパターンでは、DNSに1つのプロバイダー(ここではCloudflare)、メールシステムに別のプロバイダー(Opalstack)を使います。これは小規模運用でよくある構成です――DNSはメインドメインが既にある場所に、メールはIMAP実績が良好で、APIアクセスがまともで、Gmailのフィルターをくぐり抜けられる評判を持つプロバイダーに置きます。DNSをメールのために移したり、メールをDNSのために移したりはしません。2つを分けて運用するコストは、ちょうど3つのDNSレコードと1つのメンタルモデルです:DNSプロバイダーは世界にメールの届け先を伝え、メールプロバイダーはそれを受信・保管・署名する。

セットアップ全体は4回のAPI呼び出しと3つのDNSレコードで完結します。事前準備さえ整っていれば、エンドツーエンドで約10分です。API呼び出しの内容、検証手順、そして最初にハマったエラーのパターンも紹介します。

このガイドで解決すること

  • メールインフラが紐づいていないプロジェクトサブドメイン(system.example.com
  • MXレコードがないためにバウンスするプロジェクト専用アドレス宛のメール
  • プロジェクトからの送信メールがGmailでDKIMチェックに失敗し、迷惑メールに入る問題
  • 存在するが認証対象のない、SPFとDMARCレコード
  • 個人アカウントを経由せずにメールチャンネルが必要なAIエージェント

必要なもの

  • 管理下のメインドメイン(APIを公開しているDNSプロバイダー:Cloudflare、Route53、DigitalOcean DNSなど)
  • APIからドメインを追加でき、ドメイン作成時にDKIMキーを生成し、IMAP/SMTPエンドポイントを提供するメールプロバイダー(Opalstack、Mailcow、Migadu、Fastmailなど)
  • 両プロバイダーのAPIトークン(コマンドラインから実行できる場所に保管)
  • 検証用のdigopenssl s_client、Pythonインタープリター
  • 約15分

プロバイダーを分ける理由

「すべてを1つのプロバイダーで」というデフォルトの発想があります――ウェブサイト、DNS、メールボックスを同じ場所にまとめるというものです。それは3つのいずれかを独立して変える必要が生じるまでは機能します。バンドルサービスと専門サービスを使い分ける判断基準についてはセルフホスティングの意思決定:SaaSが自社インフラより高くつくときで詳しく説明しましたが、同じロジックがここでも小さなスケールで当てはまります。

私たちの場合、メインドメインのDNSはCloudflareに置いています。AnyCast解決、公開サイトのプロキシ、自動レコード管理のためのAPIが必要だからです。メールシステムはOpalstackにしています。メールボックスあたりのコストが微々たるもので、IMAPサーバーの評判が健全で、EUのメールホスティング界隈でAPIの使い勝手が比較的良いためです。DNSをメールのために移したり、メールをDNSのために移したりはしません。2つを分けて運用するコストは、ちょうど3つのDNSレコードと1つのメンタルモデルです:DNSプロバイダーは世界にメールの届け先を伝え、メールプロバイダーはそれを受信・保管・署名する。

この分離は運用上の影響範囲を限定するという意味でも重要です。DNSプロバイダーで障害が起きても、すでに経路上にあるメールはMXホストに届き続けます。メールプロバイダーで障害が起きても、DNSの名前解決は機能し続けます。すべてを1つのプロバイダーに積み上げていたら、両レイヤーが同時に落ちていたでしょう。同じ考え方はリバースプロキシを積み重ねる本番アーキテクチャにも共通します――関心の分離、各レイヤーを独立して入れ替え可能に。

専用メールボックス・転送エイリアス・SaaSインボックス

何かを作る前に、適切な選択肢を選んでください。3つは等価ではありません。

専用メールボックスとは、メールを保持する実際のインボックスで、IMAPの認証情報を持ち、送受信の両方ができます。コスト:セルフホストのメールプロバイダーで月1〜3ユーロ程度、APIセットアップの作業も伴います。AIエージェントが実際にメールを読む必要がある場合――受信ベンダーの確認メールを解析したり、サービス通知を処理したり、人間の相手先と複数ターンにわたるやり取りを維持したりする場合――これが選択肢です。

転送エイリアスとは、既存のインボックスにリダイレクトするだけの仮想アドレスです。コスト:ゼロ、設定のみ。AIエージェントがプロジェクトブランドのアドレスで受信するだけでよく、実際の読み取りと処理は既存の人間のインボックスで行う場合に適しています。トレードオフは、エージェントのIMAPアクセスがない点、宛先側でプロジェクト単位のフィルタリングができない点、プロジェクト終了時のロールバックが面倒な点です――エイリアスの削除は忘れがちです。

SaaSインボックスとは、Postmark・Mailgun Inbound・AWS SESのような受信ルール付きサービスです。コスト:メッセージ単位の課金+AIエージェントをWebhookレシーバーに繋ぐエンジニアリング工数。高ボリューム処理、ヘッダールールによるメール振り分け、各メッセージで自動ワークフローをトリガーする必要がある場合に適しています。月に数十〜数百通しか扱わないプロジェクト専用エージェントには過剰です。

TelegramのAIアシスタント記事のユースケース――1名のオペレーター、2名のプロジェクト協力者、Claudeを搭載したボット――には、専用メールボックスが最適です。エージェントはIMAPで読み取り、SMTPで送信できる実際のアドレスを持ち、プロジェクトと共に明確に生まれ、終わります。

ステップ1:メールシステムにサブドメインを登録する

メールプロバイダーは、プロジェクトサブドメインが自分の管理ドメインであることを知って初めて、そこへのメールを受け入れます。この呼び出しがDKIMキーペアも生成します。

POST https://my.mailhost.example/api/v1/domain/create/
Authorization: Token <YOUR_MAIL_API_TOKEN>
Content-Type: application/json

[{"name": "system.example.com"}]

レスポンスには3つの重要フィールドが含まれます:ドメインのUUID(後のクリーンアップとアドレスマッピングに必要)、state(通常は数秒間PENDING_CREATE、その後READYになる)、そしてDNSにそのまま貼り付けられる形式になったdkim_recordフィールドのTXT値です:

{
  "id": "<DOMAIN_UUID>",
  "state": "PENDING_CREATE",
  "name": "system.example.com",
  "dkim_record": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4G..."
}

stateREADYに変わるまでポーリングし、dkim_recordの値を取得してください。次のステップでDNSプロバイダーにそのまま貼り付けます。作成呼び出しがinvalid hostnameエラーで失敗する場合、最もよくある原因はメールプロバイダーが2レベルドメインのみに対応しており、3レベルのサブドメインを受け付けないケースです。サポートに問い合わせてください――実際に拒否されたプロバイダーにはまだ出会ったことはありません。

先に進む前にDKIMキーのサイズを確認してください。dkim_recordの値が約250バイト未満であれば1024ビットのRSAキーで、2048ビットキーはおおよそ380〜420バイトになります。NISTは2013年に1024ビットRSAを非推奨とし、Gmail・Microsoft・Yahoo・AWS SESはいずれも2026年のベースラインとして2048ビットを要求しています――1024ビットキーでも認証は通りますが、エンタープライズのスパムフィルターでは弱いシグナルとして扱われます。サイズを選べる場合は2048を選んでください。1024ビットがデフォルトのプロバイダーはプロバイダー選定のシグナルです。

ステップ2:DNSレコードを設定する

プロジェクトサブドメイン向けに、すべてDNSプロバイダーで作成する3つのレコードです。Cloudflareのエッジを通してプロキシすることはできません――MXとTXTレコードはDNS専用にする必要があります。proxied: trueに設定しても、CloudflareのAPIは暗黙のうちにDNS専用にフォールバックしますが、明示的に設定する方がすっきりします。

2つのMXレコードは、送信者にメールの届け先を伝えます。ほとんどのメールプロバイダーは、ラウンドロビンによるフェイルオーバーのために、同じ優先度の2つ以上のMXホストを運用しています:

POST https://api.dnsprovider.example/zones/<ZONE_ID>/dns_records

{"type":"MX","name":"system.example.com",
 "content":"mx1.de.mailhost.example","priority":10,
 "ttl":3600,"proxied":false}

{"type":"MX","name":"system.example.com",
 "content":"mx2.de.mailhost.example","priority":10,
 "ttl":3600,"proxied":false}

DKIM TXTレコードはステップ1のdkim_record値を使い、dkim._domainkey.<subdomain>に配置します:

{"type":"TXT",
 "name":"dkim._domainkey.system.example.com",
 "content":"v=DKIM1; k=rsa; p=MIGfMA0G...",
 "ttl":3600,"proxied":false}

DKIMの公開鍵が255バイトを超える場合(2048ビットRSAキーではそうなります)、複数のクォートされたチャンクに分割する必要があります:"chunk1" "chunk2"。ほとんどのDNSプロバイダーはAPIで自動的に分割してくれますが、対応していない場合は手動で分割する必要があります。

SPFDMARCレコードはサブドメイン自体と_dmarc.<subdomain>に一度だけ設定します。SPFは受信者に許可送信IPを伝え、DMARCはSPFまたはDKIMが失敗した際の対処を伝えます:

{"type":"TXT","name":"system.example.com",
 "content":"v=spf1 include:spf.mailhost.example ~all",
 "ttl":3600,"proxied":false}

{"type":"TXT","name":"_dmarc.system.example.com",
 "content":"v=DMARC1; p=none; rua=mailto:[email protected]; aspf=r; adkim=r",
 "ttl":3600,"proxied":false}

DMARCはp=noneから始めてください。これは監視モードです――受信者はアグリゲートレポートをruaアドレスに送りますが、ポリシーに基づいてメールが拒否されることはありません。標準的なロールアウトは、アグリゲートレポートで正規メールのコンプライアンスが98%以上になるまで2〜6週間p=noneを維持し、その後pct=タグを使って段階的に引き上げていくものです――例えば2週間p=quarantine; pct=25、次にpct=75pct=100、最終的にp=reject。段階を飛ばして新規ドメインでいきなりp=rejectにすると、DKIMの署名パスを検証する前に自分の正規メールを落とす確実な方法になります。

ステップ3:メールボックスとアドレスマッピングを作成する

メールプロバイダーのデータモデルは通常、2つの別々のリソースを持ちます:mailuser(認証情報とクォータを持つIMAPアカウント)とaddress(mailuserにメールをルーティングするlocal@domain文字列)です。同じmailuserが同じまたは異なるドメインの複数のアドレスの宛先になれるため、この2つは分離されています。

強力なパスワードを生成してmailuserを作成します。命名規則はプロバイダーによって異なりますが、一般的なパターンは<local>_<アンダースコアで連結したdomain>です。文字数制限に注意してください――ほとんどのプロバイダーはmailuser名を32文字までに制限しており、長いサブドメインでは容易に超過します:

import secrets, string
alphabet = string.ascii_letters + string.digits + "!@#%^&*-_=+"
password = ''.join(secrets.choice(alphabet) for _ in range(32))

# その後:
POST /api/v1/mailuser/create/
[{"name": "bot_system_example_com",
  "imap_server": "<IMAP_SERVER_UUID>",
  "password": password}]

mailuserがREADYになったら、local@subdomainをそれに紐づけるアドレスマッピングを作成します:

POST /api/v1/address/create/
[{"source": "[email protected]",
  "destinations": ["<MAILUSER_UUID>"],
  "forwards": []}]

forwards配列は、コピーを受け取る追加の外部アドレス用です。実際にメールボックスを並行して人間のインボックスに転送したい場合以外は空のままにしてください――プロジェクト引き継ぎ段階では便利なパターンですが、長期的には煩雑になります。

ステップ4:エンドツーエンドで動作を確認する

4つの確認を順番に実施してください。自己ループバックのテストは省略しないでください――受信ルーティングが実際にメールボックスに届くことを確認できる唯一のテストです。

DNS伝播の確認。CloudflareクラスのAnyCastプロバイダーは通常1分以内に伝播します。両方のレコードが現れるまでループします:

for i in $(seq 1 36); do
  mx=$(dig +short system.example.com MX)
  dkim=$(dig +short dkim._domainkey.system.example.com TXT)
  if [ -n "$mx" ] && [ -n "$dkim" ]; then break; fi
  sleep 5
done
echo "MX: $mx"
echo "DKIM: $dkim"

IMAPログイン。認証情報が機能し、メールボックスが実際のインボックスを持つことを確認します:

openssl s_client -crlf -connect imap.mailhost.example:993 -quiet
a1 LOGIN bot_system_example_com <password>
a2 SELECT INBOX

期待されるレスポンス:a1 OK Logged in、その後SELECTが新規メールボックスでEXISTS 0を確認します。

自己ループバック。最良のエンドツーエンドテスト:新しいアドレスから自分自身にメールを送り、IMAPに届くことを確認します。これにより、外部メールプロバイダーのスパムフィルターの挙動に依存せずに、SMTP認証・DKIM署名・MX解決・メール受理・メールボックス配信・IMAP表示という全経路を検証できます:

import smtplib, ssl, imaplib, time
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "[email protected]"
msg["To"]   = "[email protected]"
msg["Subject"] = "loopback test"
msg.set_content("hello self")

with smtplib.SMTP("smtp.mailhost.example", 587) as s:
    s.starttls(context=ssl.create_default_context())
    s.login("bot_system_example_com", "<password>")
    s.send_message(msg)

time.sleep(10)
imap = imaplib.IMAP4_SSL("imap.mailhost.example", 993)
imap.login("bot_system_example_com", "<password>")
imap.select("INBOX")
typ, search = imap.search(None, "ALL")
print("inbox count:", len(search[0].split()))

配信されたメールのヘッダーでAuthentication-Results行を確認してください。正しく設定されていれば、適切なsmtp.mailfromを持つspf=passと、d=<your subdomain>およびs=dkimを持つDKIM-Signatureヘッダーが表示されます。これらが存在すれば、チェーン全体が機能しています。残る変数は外部受信者の評価だけで、それはDMARCレポートが数日かけて教えてくれます。

外部への送信確認。システム外で確認できる外部アドレスにメールを1通送ります――個人のGmail、別の仕事用インボックスなど。迷惑メールではなく受信トレイに届くことを確認してください。新しいドメインから最初の送信で迷惑メールに入る場合、最も一般的な原因は送信ドメインの評判がまだ存在しないことです。評判の蓄積には時間がかかりますが、最初のテストメールは、受信者がDKIMとSPFを独自に評価するため、過去の送信量とは関係なく通るはずです。

よくある落とし穴

32文字のmailuser名制限は、サブドメインとローカル部の組み合わせが予想より長い場合にひっかかります。backoffice_dashboard_system_example_comは39文字で拒否されますが、bo_dashboard_system_example_comは31文字で収まります。デプロイ途中でAPIに強制される前に、意識して略称を選んでおきましょう。

Cloudflareプロキシのわな。CloudflareのUIでは、どのレコードでもオレンジ色のプロキシアイコンを切り替えられます。ウェブオリジンのA/AAAAレコードにはほぼ常にオンにしたいものですが、MXレコードは常にオフです――Cloudflareはメールをプロキシしません。わなは、スクリプトで一括プロキシを有効にした際にMXレコードも含めてしまう場合です。Cloudflareはサポートされていない型のプロキシフラグを暗黙に削除するため、成功したように見えますが、何も問題ないのになぜか動かないと数分間悩むことになります。

最初の受信時のグレイリスト。メールシステムが未知の送信者からの最初のメッセージをグレイリストに入れることがあります――一時的な4xxレスポンスで、数分後に再試行するよう要求します。Gmailは自動的に再試行します。最初の受信スモークテストが失敗したように見える場合は、問題と断定する前に15分待ってください。

SPFのDNS参照上限。SPFは評価中のDNS参照に10回という厳しい制限があります(RFC 7208 §4.6.4)。include:spf.mailhost.exampleを1つ記述すると、そのincludeが内部的に何十ものIPを解決しても1回と数えられます。上限に達するのは、後から別の送信者を追加し始めた場合です――例えばマーケティングパイプライン用にinclude:_spf.resend.comを追加する場合など。SPFレコードは最初の送信者だけでなく、長期的な送信者リストを想定して設計してください。

メールプロバイダー側のdkim_privkeyが空。ドメイン作成エンドポイントが成功を返すが実際にはDKIMキーを生成しておらず、別途「DKIMを有効にする」呼び出しが必要なプロバイダーがあります。作成レスポンスのdkim_recordフィールドが空でないことを先に進む前に確認してください。空の場合はプロバイダーのドキュメントで追加の呼び出しを確認してください。

デプロイの監視が甘い。数十のドメインを運用している場合、DKIMレコードの不具合やDMARCレポートの宛先期限切れは、能動的に確認しなければ日常監視では浮かび上がりません。私たちのセルフホストサービスの月次ヘルスチェックルーティンにメールレコードの監査ステップが含まれているのはまさにこの理由です。

利用可能な場所でMTA-STSをスキップする。MTA-STSは、送信者がMXホストへのTLSの使用と証明書の検証を強制するポリシーを公開します。Gmail・Microsoft 365・Yahooはこれを確認しており、インターネット全体での採用率はまだ1%未満なので、有効なポリシーは小さな評判シグナルになります。トレードオフは運用上のものです――TLS検証に失敗するMXホストを指すポリシーは正規の受信メールをブロックします。プロバイダーがMTA-STSを提供している場合は、DKIMとDMARCが安定した後に有効にしてください。

ロールバック手順

検証が失敗した場合、またはプロジェクトに専用メールボックスが不要と判断した場合、クリーンアップはべき等で5分で完了します:

  1. アドレスのUUIDでPOST /api/v1/address/delete/
  2. mailuserのUUIDでPOST /api/v1/mailuser/delete/
  3. メールシステムのドメインUUIDでPOST /api/v1/domain/delete/(これでDKIMキーも消去されます)
  4. 3つのDNSレコードそれぞれにDELETE /zones/<zone>/dns_records/<id>

順序が重要です。アドレスが残ったままではドメインの削除を拒否するプロバイダーがあります。逆順にすることで、途中で止まって確認できる明確なポイントが生まれます――ステップ1が、忘れていたメールがメールボックスにあるために失敗しても、まだDNSレベルのルーティングを壊していません。

ロールバック後もSPFとDMARCレコードをサブドメインに残しておくのは無害です。認証対象がなくても、このサブドメインは正規メールを送らないと将来の受信者に明示する効果があり、小さなアンチスプーフィングのメリットになります。私たちは一度作成したら原則として残します。この「無害な残存物」という考え方は、セルフホストサービスのディザスタリカバリで適用しているロジックと同じです:アンインストールのパターンはインストールのパターンと同じくらい重要です。

まとめ

プロジェクト専用のメールボックスはインフラというよりも、AIエージェント――あるいは他のあらゆる自動化プロセス――がメールベースのワークフローに一人前の参加者として加わるための小さな運用プリミティブです。上記の4APIコールパターンはメールプロバイダー間でそのまま移植できます。DNSレコードはDNSプロバイダー間で移植できます。しっかり理解しておく価値があるのは、2つのシステムの境界です:どのレコードがどこに置かれるか、障害モードはどう見えるか、そして外部の相手のスパムフィルターの振る舞いに依存せずにエンドツーエンドで検証する方法。

Telegramエージェント、メールボックス、ドキュメント取り込みなど、プロジェクト固有のスタックの設計についてお手伝いが必要な場合は、tvaにご相談ください


関連インサイト

関連記事