Skip to content

Core のメール送信 ​

第1版作成 最終更新 (日本時間)

VehicleVision.PleasanterTools.Core.Mail は、ツールが通知や自動返信のメールを送るための共通部品です。設定の読み取り、3 つの送信経路、1 通ぶんの形と検証、失敗の分類を持ちます。対象は 0.1.0-preview.9 です。メールの送信待ち(アウトボックス)の保管先は Core.Data 側にあります(共有ストア)。

プレビュー版・未確認の範囲

プレビュー版の間は API が変わり得ます(概要)。Core.Mail の機能は preview.1 から変わっていません(変更履歴)。このページはソースの読み取りに基づきます。SMTP サーバ・Amazon SES・Azure Communication Services に実際に送って確かめた内容ではありません。

設定(MailOptions) ​

MailOptions.FromConfiguration(configuration, prefix) が、IConfiguration(環境変数など)から設定を読みます。環境変数の接頭辞はアプリごとに決めて渡します。 規約は <ID>_MAIL_(<ID> はアプリ ID の大文字でハイフンは下線)で、たとえばアプリ ID が sample-app なら接頭辞は SAMPLE_APP_MAIL_ です。次の表の名前は、この接頭辞を付けたものを指します。

設定名(接頭辞の後)既定内容
ENABLED無効true で有効。既定は無効で、設定しなければメールは 1 通も出ません。無効なら他の値は読まない(使わない設定の不備で起動を止めない)
TRANSPORTSmtp送信経路。Smtp / AmazonSes / AzureCommunicationServices
SMTP_HOST-SMTP サーバ(経路が Smtp のとき必須)
SMTP_PORT587ポート(1〜65535)
SMTP_SECURITYStartTlsStartTls(587 番向け)/ ImplicitTls(465 番向け)/ None(暗号化しない。検証用で、本番では使わない)
SMTP_USER空認証のユーザ名。空なら認証しない
SMTP_PASSWORD空認証のパスワード。ログにも画面にも出さない
SES_REGION-Amazon SES の地域(経路が AmazonSes のとき必須。例 ap-northeast-1)
ACS_ENDPOINT-Azure Communication Services の窓口の URL(経路が AzureCommunicationServices のとき必須。https の絶対 URL)
FROM_ADDRESS-差出人のアドレス(有効にするとき必須)
FROM_NAME空差出人の表示名
REPLYTO_ADDRESS空返信先。空なら差出人と同じ
BASEURL空メールに載せる URL の起点(例 https://survey.example.jp)。http:// か https:// の絶対 URL
TIMEOUT_SECONDS301 通あたりの待ち時間の上限(1 以上)

検証の挙動 ​

  • 読めない値を黙って既定へ落としません。 ENABLED が true / false でない、SMTP_PORT が整数でない、TRANSPORT や SMTP_SECURITY が定義外の値、といった場合は InvalidOperationException で止まります。設定したつもりが効いていない状態を作らないためです。
  • 有効にしたのに必須の値が欠けているときも、起動時に止まります(SMTP_HOST・SES_REGION・ACS_ENDPOINT・FROM_ADDRESS のうち、経路に必要なもの)。
  • BASEURL が http:// か https:// の絶対 URL でなければ、止まります。
  • MailOptions.IsReady は、有効で、差出人があり、経路に必要な値が揃っているかを返します。送信経路は、IsReady でなければ「メールの設定が有効になっていない」(再送しても直らない失敗)として送りません。
  • BASEURL は、リンクを載せるメールの組み立てに使う値です。 要求の Host ヘッダから URL を組み立てると、偽の宛先を載せたメールをこちらの名前で送らされる(host header injection)ため、設定から取ります。設定が無ければ、リンクを載せるメールは送らない設計です(MailOptions.Link は起点が無ければ null を返します)。

送信経路 ​

どの経路でも、同じ 1 通が出ます。違うのは資格情報をどこに置くかです。

経路資格情報備考
SMTP(SmtpMailTransport、既定)SMTP_USER / SMTP_PASSWORD を保管する認証付きリレーの 587 番は Azure でブロックされないので、SendGrid・Amazon SES・Azure Communication Services・Microsoft 365 など、どの送信元にも届く。1 通ごとに接続して切る
Amazon SES(SesMailTransport)設定に持たない地域だけを設定から取り、資格情報は AWS SDK の既定の探索順(マネージド ID や IAM ロール、環境変数)に任せる。組み立てた生のメールをそのまま渡す
Azure Communication Services(AcsMailTransport)設定に持たない窓口の URL だけを設定から取り、Entra ID のマネージド ID(DefaultAzureCredential)で通る。接続文字列は受け取らない。送信の完了を待たず、受け付けられた時点で送れたとする(届かなかったことは ACS 側の記録で追う)

SES と ACS の経路は、鍵を設定へ書ける口を作らないために加えられたものです(ソースのコメント)。SMTP は、必ずパスワードを 1 つ保管します。

SMTP で既定を 587 番にしているのは、25 番の外向き接続が Azure で塞がれる場合があるためです(サブスクリプションの種別による。ソースのコメント)。

3 つの経路を使い分ける ​

運用の観点では、次のように選びます(ソースの設計意図に沿った整理で、このマニュアルが推奨を決めるものではありません)。

  • 秘密を保管したくなければ、SES か ACS(マネージド ID)。
  • 送信元を問わず届かせたければ SMTP。パスワードを環境変数か Key Vault で守る必要があります。

1 通の形(OutgoingMail)と守っていること ​

OutgoingMail は、宛先・件名・本文と、任意の差出人の表示名・返信先・BCC を持つ record です。

  • 本文は平文(text/plain)だけです。管理者が書いた文面と回答者自身の回答が入るため、HTML にすると逃がし漏れがそのまま差し込みになるからです。
  • 1 通 1 宛先です。複数宛先は作りません(誰に届いたかを混ぜない)。
  • 件名・宛先・差出人の表示名・返信先・BCC に改行を含められません(Validate が弾く)。ヘッダの途中で改行を差し込まれると、ヘッダを 1 本増やせてしまう(メールヘッダインジェクション)ためです。
  • 全通に Auto-Submitted: auto-generated ヘッダ(RFC 3834)を付けます。 自動返信に不在通知が返ると無限に往復するため、まともな不在通知がこのヘッダで返信を止められるようにします。
  • アドレスの形が壊れているときは、再送しても直らない失敗として扱います(失敗の文に、アドレスは入れません)。
  • 差出人の表示名・返信先は、1 通ごとの指定があればそれを使い、無ければ全体設定を使います。BCC は 1 通ごとの指定だけです。

MailMessageFactory が、通信を含まずに MIME メッセージを組み立てます。網が無くても試験できる作りです。

送信待ちへ置くための形(MailPayload・IMailPayloadProtector) ​

メールの本文には回答者のメールアドレスなど、個人を指す値が入ります。送信待ちの表(共有ストア の MailOutbox)へ置くときは、次の部品を使います。

  • MailPayload.ToJson / FromJson は、1 通ぶんを 1 つの文字列(JSON)にまとめます。宛先・件名・本文を別の列に持つと、暗号化の掛け忘れが列ごとに起き得るため、まとめて守る形です。壊れた文字列や宛先が空のものは、例外ではなく null を返します(呼ぶ側が読めない行をデッドレターへ回します)。
  • IMailPayloadProtector は、1 通を暗号化して保存する形にする(Protect)・戻す(Unprotect。鍵が違う・壊れている・読めないなら null)ための口です。実装は Core には無く、アプリが用意します(鍵の管理はアプリの責務)。

失敗の分類(MailDeliveryException) ​

送信経路は、失敗を MailDeliveryException にして投げます。IsTransient が、時間を置けば通る見込みがあるかを表します。送信ワーカーが SMTP の応答コードなどを解釈しなくて済むようにするためです。再送は送信経路ではしません。 「1 回試して、駄目なら理由を付けて投げる」だけで、再送は送信待ちの表とワーカーの役割です。

経路一時(再送する)恒久(再送しても同じ)
SMTP繋がらない・時間切れ・4xx の一時拒否・手順の不一致5xx の恒久拒否・認証が通らない・TLS を確立できない
Amazon SES流量の上限(TooManyRequests / LimitExceeded)・5xx・繋がらない・時間切れ送信ドメインが未検証・アカウント停止・送信停止・不正な要求・その他の 4xx
ACS429(流量の上限)・5xx・繋がらない・時間切れ401・403(認証)・資格情報を取得できない・その他の 4xx

停止の合図(CancellationToken による中止)は失敗として扱わず、そのまま投げ直します(次回また拾わせるため)。判定できない失敗は、一時として扱います。

失敗の文に宛先を入れない

MailDeliveryException の文は、デッドレターの行や管理画面に残ります。宛先と本文を入れない作りです。ログにも、宛先・本文・パスワードは出さず、送れたことだけを残します。

関連ページ ​

変更履歴

第1版共通ライブラリ Core のマニュアルを追加する (#35)