/var/lib/azumakuniyuki

Sisimaiとか技術的なことはこっちに書いてみようかという試み

Gmailと米国Yahoo!のあれ(2024年2月)

メールシステム担当の人はもちろん、インフラ担当の人もDNSの設定とかで既に知ってはると思いますが、 10月にGoogleが発表した2024年2月から始まるGmailYahoo!(米国)におけるスパム対策強化のあれです。 海外では数年前から"No Auth, No Entry"って「代表なくして課税なし」みたいな感じで言われているアレです。 識者の方々がいろんなところで記事にしてはりますので、他のところであんまり書かれていない気がするとこだけ記します。

まずは公式情報

Google

Googleについては以下の二ヶ所を読んで理解して実践しておけば大丈夫そうです、たぶん。 パラメーターのhl=enhl=jaに変えると日本語版になりますが、更新されるのが遅いので最初に英語版を見ておくのが良いです。

Email Sender Guidelines(81126)

  • Email Sender Guidelines
  • 最も情報が多く更新されている公式のガイドライン
  • 通称81126
  • Gmail 81126」でググると出てくる
  • メール送信者について
    • 数字が独り歩きしているっぽい5000通/日以上の送信者
    • すべての送信者(5000通/日以下の人)に求める要件も書いている

Email Sender Guidelines FAQ(14229414)

米国Yahoo!

米国Yahoo!も「Googleのそれに準じる」といった発表 をしているのでGoogleの要求内容にしたがっていれば大丈夫ですが、念のため公式発表に目を通しておくのが無難です。

Yahoo! Sender Best Pracctices

Yahoo! Sender FAQ

FAQの方には以下の記述があります

Do these requirements apply to AOL too? What about Yahoo Japan? * These requirements apply for all domains and consumer email brands hosted by Yahoo Mail. * Yahoo Japan is a separate entity, and we cannot speak to their plans as we do not coordinate with them.

明示的に「米国Yahoo!」と書いておかないと@yahoo.co.jpも?って思うかもしれませんし、FAQにあるとおり別組織です。

対象となるドメイン

Google

Googleは発表から少し経って「Google Workspaceのドメインは対象外にする」と発表がありました。

A personal Gmail account is an account that ends in @gmail.com or @googlemail.com.

つまり対象なるドメイン@gmail.com@googlemail.comの2つです。Workspaceはいったん対象外になりましたが、 いずれWorkspaceのドメインに送信する場合も同じ要件が求められるようになると思います、たぶん。

米国Yahoo!

米国Yahoo!はどうかというと「それって@yahoo.comぐらいやん?」と思うところですが 「Yahoo Mailに収容しているドメインも対象になる」と書いてあるので、 例えば@aol.com@aol.jpも対象になるはずです。

$ dig -4 MX yahoo.com
...
;; QUESTION SECTION:
;yahoo.com.         IN  MX

;; ANSWER SECTION:
yahoo.com.      1485    IN  MX  1 mta5.am0.yahoodns.net.
yahoo.com.      1485    IN  MX  1 mta7.am0.yahoodns.net.
yahoo.com.      1485    IN  MX  1 mta6.am0.yahoodns.net.
$ dig -4 MX aol.com
...
;; QUESTION SECTION:
;aol.com.           IN  MX

;; ANSWER SECTION:
aol.com.        3367    IN  MX  10 mx-aol.mail.gm0.yahoodns.net.

上記のとおりMXレコードが.yahoodns.net.になっているドメインは対象になると考えたほうがよさそうです。 たとえばベライゾン@verizon.netもMXがYahoo Mailに向いているので、Google Workspaceと同じくドメインの見た目やスペルだけでは判断できません。

自社で送信している宛先ドメインのMXを調べて、Yahoo Mailに収容されているのがどの程度あるかは把握しておくのも必要でしょう。

サブドメインの扱い

5000通/日以上の送信者(Bulk senders)となるかどうかの計算方法がGoogleEmail Sender Guidelines FAQ /Bulk sendersに下記の記述があります。

Sending domains: When we calculate the 5,000-message limit, we count all messages sent from the same primary domain. For example, every day you send 2,500 messages from solarmora.com and 2,500 messages from promotions.solarmora.com to personal Gmail accounts. You’re considered a bulk sender because all 5,000 messages were sent from the same primary domain: solarmora.com. Learn about domain name basics.

ざっくり言うと「5000通/日になるかどうかはFrom:ヘッダーのドメイン単位になる、サブドメインも全て含めて送信数を計算しますよ」ってことです。

  • From: ***@solarmora.comから2500通のメールを送っている
  • From: ***@promotions.solarmora.comからも25000通のメールを送っている
  • 同じドメインとして計算されるのでsolarmora.comは5000通/日以上の送信者となる

たとえば以下のようにWEBサービスドメインのメールアドレスを使ってメール送っているけど、運営会社の企業アドレスも 同じドメインサブドメインで作っているなら影響を受けることになります。

  • ***@example.ne.jp は2000通/日ぐらいのメールを送っている
  • ***@mag.example.ne.jp は3000通/日ぐらいのメールマガジンを送っている
  • ***@corp.example.ne.jp は各所属社員の個人メールアドレスとか・送信量は少ない

上記の場合は***@corp.example.ne.jpから送るメールも同じドメインとして扱われて、 Googleから見て大量送信者ということになります。よって、サブドメインを含む全てのexample.ne.jpにおいて SPFDKIMの双方がPASSすることとDMARCレコードの定義*1が求められます。

ドメインをいきなり変えない

メールマガジンとかコンテンツの更新通知とか、大量に送っているメールのFrom:を変更すると、ドメインと 送信元IPアドレスのレピュテーションに影響が出ますし、ゆっくりじっくり暖機運転をする必要があります。

なので、5000通/日を超えるからメールで使っているFrom:ドメインを変えるぐらいなら、 大量送信者に求められる要件を満たすほうが健全です。

というか、今まで送っていたメールのFrom:を変えると、Googleからすれば「いきなり初めましてのドメインから 大量にメールが来るようになったで?」となり、機械的に「これは迷惑メール扱いするのが妥当」ってことに なりかねないので、思いつきの気まぐれで急なドメイン変更はやらないほうがマシです、たぶん。

List-Unsubscribeの実装

2024年6月1日まで?ホンマ?

大量配信しているところは1クリック購読解除の仕組みをRFC2369RFC8058にしたがって実装してねって言っています。 当初は2024年の2月までにってことでしたが、苦情や問い合わせが多かったのか、2024年6月までに実装してね と言い出したので四ヶ月の猶予が現れたと推測されます。

というのも、これについて言及されているのがEmail Sender Guidelines FAQ(14229414)の Unsubscribe links節なんですが、6月までってのは条件があるんですよね。

Senders that already include an unsubscribe link in their messages have until June 1, 2024 to implement one-click unsubscribe in all commercial, promotional messages.

「本文に購読解除リンクを入れている人は2024年6月1日までに1クリック購読解除を実装してね」とあるんですが、 この「メール本文の購読解除リンク」ってのが次のどっちを指しているのか?が不明瞭でして。

  1. 従来のリンクを開いた先でログインして配信の停止を行う(==1クリック購読解除ではない)
  2. クリックしただけで購読解除が完了するもの(==1クリック購読解除)

つまり説明の an unsubscribe link in their messageについて「1クリック購読解除リンク」とも 「1クリック購読解除ではない購読解除リンク」とも書いてない ので6月1日のつもりでいて 「え?二月ですよ?実装してないんですか?ほなスパムとして処理しますね」みたいなことになるのが心配で心配で心配です。

たぶん(1)の「従来の購読解除リンク」で良いとは思いますが確証がないので「これはどっち?1クリック購読解除リンクやなくてもOK?」 ってな問い合わせをフィードバックのところからGoogleに投げました。

(1)であることを願いますが、二月からビシャッと始められるように実装しておくのが安全策やと思います。

実装した

僕は主にインフラを担当しているのでSPFは当然、DKIMもDMARCも何年か前に実装してて今回の発表を受けても 「マァ設定状況の再確認はしとくか」と思っていたのですが、11月になって僕がアプリケーションにList-Unsubscribeの実装をやることになりました。

List-Unsubscribeのリンク有効期限

例えば配信から1年以上が経過したリンクは無効(404とか返す?)って仕様にするもの考えたのですが、有効期限に関する明確な定義が見つからず、でした。

  • List-Unsubscribe:ヘッダーのURLはいつまで1クリック購読解除できる必要があるのか?
    • 例えばURLの発行から1年とか?
    • 探したけど期限に関する記述や定義は無さそう
      • 目が節穴なので見逃している可能性はある
  • 分からんので永久に有効にしておくのが無難やろ
  • 1年前のメールにあるList-Unsubscribeでも購読解除できるべきと考える
    • 逆に古いメールで購読解除できないとユーザーが「スパム報告」ボタンを押すかも?
    • ↑これが一番こまる

実装した内容

実際に作ったのは以下のような構造です。

  • 購読解除した人を格納するテーブルを作る
    • From:のメールアドレスとTo:のメールアドレスを入れるカラム
    • 購読解除したタイムスタンプを入れるカラム
    • 購読解除リンクを開いたUserAgentやIPアドレスとかを入れるカラム
    • 他のテーブルと紐付けられるIDを入れるカラム(配信テーブルとかいろいろ)
    • 購読解除の取り消し関連のデータを入れるカラム
    • このテーブルは購読解除が発生した時のみINSERTする
    • 誰も購読解除しなければこのテーブルはずっと空
  • 配信するメールにList-Unsubscribe:ヘッダーを追加する(同-Post:も入れる)
    • 配信するメールごとの購読解除用リンクを作る
    • https://example.jp/email-delivery/unsubscribe/bmVrb2NoYW4Kみたいなの
    • bmVrb2NoYW4K は配信するメールごとに固有の文字列
      • この中身は購読解除に必要な情報を文字列にして暗号化してBASE64にしたもの
        • From:, To:, DBで追跡できる配信ID, 配信日時とかいろいろを文字列化
        • ↑暗号化してBASE64にする
        • ↑暗号化する鍵を入れ替えられるよう暗号化キー情報を文字列としてどこかに結合する
        • /etc/shadowのソルト的なやつ
      • 実際は配信時に負荷がかからない程度にもうちょいカッチリ細々といろいろ
    • bmVrb2NoYW4Kみたいな文字列が長くなりすぎないようにする(998文字の件)
  • この方法なら「どのメールで購読解除されたか」まで追跡できる

実装しなかった他の案・考察

A. 配信メールごとに購読解除用文字列をDBに入れる

これは配信量が多ければレコード数がとんでもないことになります、たぶん。

  • 購読解除文字列のセッションID的なテーブルにするか!
  • 永久に購読解除リンクとして機能させるなら無理かも
  • 例えば100万通/日の配信をしている
    • 配信するメールごとに購読解除文字列をテーブルに入れると1日で100万レコード増える)
    • 一ヶ月で3000万レコード
    • 一年後は3億6000万レコード!
    • ストレージが心配になってくる
    • もちろん1年で購読解除リンクを機能不全にしてよい確証がない
  • しかも配信する時に購読解除アドレスかどうかSELECTするし
    • 配信が終わる毎にINDEX更新?
    • それなら購読解除の状態をユーザーテーブルに追加したカラムに保存するか → Bの内容を検討
  • まぁ無理かな

B. ユーザーテーブルに購読解除用のカラムを作る

これも上手に設計すれば妥当な方法かもしれないです。

  • bmVrb2NoYW4Kみたいなユーザー毎に固有の購読解除用文字列をユーザーテーブルに入れる?
    • 1カラム増えるだけやし
    • From:アドレスとの組み合わせも要る
      • info@example.jpのメール要らないけどupdates@example.jpのは配信してほしい場合
      • もう1カラム増やすか
        • ローカルパートが増える毎にカラムを増やす未来が見える
      • 購読解除用テーブルを別個で作ってユーザーテーブルには1カラムだけ増やす
        • ユーザーテーブル側は整数値で記録してON/OFFが分かればOK
        • 32ビットなら32個のローカルパートまで網羅できる

僕が1クリック購読解除の仕組みを実装することになったアプリケーションは、永続的に存在するユーザーテーブル (To:のメールアドレスが入っているやつ)がないので、この方法は使えませんでした。 なので少し考えて却下したのですが、アプリケーションの構造によっては現実的な案かもしれません。

心配事

1クリック購読解除について少し心配事があります。

ウィルス検査とかで購読解除?

例えばウィルスチェックのプログラムとかMTAで、あるいはMTAからMilterで呼び出されると思います。で、ヘッダーも含めてメール内のURLは無害かどうかを確認する際に、 List-Unsubscribe: <https://https://example.jp/unsubscribe/bmVrb2NoYW4K> って書いているのを見つけて接続するはずです、たぶん。

この検査で購読解除されたりしない?か心配やったのですが、結論としてはリクエストの本文(Request Body)にList-Unsubscribe=One-Clickって文字列を入れろとRFCに書いているので、 アプライアンスとかがそう言った動作をしない限りは勝手に購読解除にならないはず*2です。

URLが知られた場合

List-Unsubscribe: <https://https://example.jp/unsubscribe/bmVrb2NoYW4K> ってヘッダーに書いているので、例えばメールをファイルとして添付したり保存したりして 他人に渡す*3と、購読解除用URLも知られてしまうことになります。 「POSTメソッドで上述のList-Unsubscribe=One-Clickを本文に入れてリクエストを送る」という仕様を知っていれば誰でも勝手に購読解除できてしまうので、これはどうするのが良いのかなぁというところです。

「気をつける」以外に何か良い策はあるのかないのかどうなのか、です。

DKIMの署名対象にする

メールヘッダーのDKIM-Signature:を見ると以下のようにいろいろ書いていますが、次の2つのヘッダーもDKIMの署名対象にする必要があります。

  • List-Unsubscribe
  • List-Unsubscribe-Post
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.jp; s=nyaan22;
  t=1702044222; bh=NekoNyaan/220xdIwNj3fxTbIOsg47Ind/wMNx++0/k=;
  h=Subject:From:To:List-Unsubscribe:List-Unsubscribe-Post:Message-Id:Date:From;
...

RFCにも書いてある

これはRFC8053で以下のように言及されています。

The List-Unsubscribe and List-Unsubscribe-Post headers MUST be covered by the signature and included in the "h=" tag of a valid DKIM-Signature header field.

List-UnsubscribeおよびList-Unsubscribe-Postヘッダーは署名でカバーされ、 有効なDKIM-Signatureヘッダーフィールドのh=タグに含まれている必要がありますってことです。

ARC

Google81126で「メールを転送するならARCも対応したほうがええで」 と推奨しています。ARCはざっくり言うと「うちのMTAで受信した段階でSPFDKIMの認証結果はこうやったで」って内容を ARC-Authentication-Results:ヘッダーに記録するものです。

MTAがメールを転送するケース

例えば次のようにMTAでメールを転送をしているケースが偏在していることでしょう。

転送先をGmailと書いていますが、ARCはGmailに限定した技術ではありません。上述のとおり転送前の段階で 「うちに来た時点での認証結果はこうやで」を記録しているので、ARCに対応している*4 転送先ならGmail以外でも有用です。

転送するとSPFはFAILする

そもそも上記に列挙したケースで、メールを転送した段階でSPFはほぼ確実にFAIL*5します。しかしGmailなど転送先が ARC-Authentication-Results:ヘッダーを見て(もちろんARC-Message-Signature:ヘッダーやARC-Seal:ヘッダーも見てちゃーんと検証している) 「なるほど、転送する前のドメイン認証はOKやったんやな」と判断することで、SMTP接続の段階で拒否されることを免れたり、 スパム扱いされるのを回避できたりするかもしれない*6効果があります。

なので、少しでも転送しているならARCも対応しているのが望ましいと言えます。

野良MTA

シャドーMTAって表現もあるのですが、つまりメールサーバー管理者が「うちの会社から送信するメールは必ず mx1.example.jpを通って出て行く」と思っているのに無断で外にメールを送っているWEBサーバーが居た!みたいな状況です。

勝手にメールを送ってるホストがある

例えばcrontabのジョブが失敗したとします。メールの設定が不完全であれば以下のようなことが起きると考えられます。

  1. www1.example.jpってホストでcrontabで実行したジョブが失敗・Permission deniedになった
  2. 実行ユーザーであるroot宛てにメールが飛ぶ
  3. rootは/etc/aliasesでadmin@example.orgって外部のアドレスへ行く
  4. / メールの発信者はFrom: <root@example.jp>になってる
  5. Envelope-Fromのアドレスも↑と同じ

(3)のところで、本来ならサイト全体のメールゲートウェイであるmx1.example.jpに渡して適切にヘッダーを書き換えて DKIM署名も入れてって流れで送るべきところを、こともあろうかadmin@example.orgのMXへ直接SMTPで繋いで送ってしまうことになります。

(4)にあるように、単なるウェブサーバーなのでメールにDKIM署名を入れる仕組みはないでしょう。

(5)のEnvelope-Fromに注目すると、example.jpのTXTレコードにwww1.example.jpIPアドレスが書いてあれば良いのですが、多くの場合そんなものは忘れられている、 というか誰もウェブサーバーが勝手に外へメールを送っていると思ってないので、当然ながらSPFはFAILする、DKIM署名も無いので結果としてメールはREJECTされることになります。

考えてみれば「ログのローテーションに失敗しました」とかLEの「証明書を更新しました」とかいろいろメールが飛んでくるもんです。 「お問い合わせフォームの確認メールをよく見たらEnvelope-Fromがapache@www1.example.jpになってるけどSPFレコード書いてないわ」とかもあるでしょう。 それにネットワーク機器が何かの通知でメールを送ることもありますし。

野良MTAの駆逐

なので、メールサーバー以外のホストで外行き25番を閉じる*7のも良いですが、DMARCをp=noneにしてレポートを受け取って、 野良MTAが居ないかどうか、居たらメールゲートウェイに任せるとかそういった調査と準備も二月までに必要となります。

変更を追いかけるリポジトリを作った

Googleの公式発表で特に重要な81126がちょいちょい更新される上に wgetで取ってdiffしても内容に関係のない埋め込まれた大量のスクリプトの差分が邪魔で邪魔で、 しかも難読化されて一行が異常に長いスクリプトは頻繁に更新されていて、肝心な本文で変更があった箇所を捉えるのが難しいので 最終的に取ってきたHTMLファイルを信頼と実績・歴史と伝統のテキストブラウザw3mで表示して、その出力で差分を追いかけることにしました。

なんかメニュー部分の順番とか微妙な変更で差分が出るのですが、とりあえずスクリプト部分の差分は排除できたし、 make updatesしてgit diffで重要な変更があるかどうかは分かるのでマァこれでいいかってとこです。

そもそも10月の発表後 すぐにテキスト化していれば良かったものの集めてたファイルを無くしたし残り一ヶ月でそんなデカい変更ってある? ってところです。

とは言え「Workspaceはやっぱり除外します〜」とか発表後一ヶ月ぐらいで方針転換したGoogleのこと、何か変更がありそうですし、自分がお客さんとかに説明する際に差分が分かる用の リポジトリがあってもええやんねでGitHubにも置いてきました。

github.com

もう変更は無いほうが良いですし、あっても緩める方向または実施時期の延長関係が望ましいとこです。

公式以外の詳しい情報

関係する情報へのリンクを並べたものです。とは言え彼らの匙加減一つで我々が振り回されるので先ずは公式情報を見る、そして関連する情報・発表内容の解釈はどうか、などを確認するのが定石です。 何か大きな変更があるとTwitterとかで詳しい人が言及してくれはりますので助かります、いつもありがとうございます。

まとめ

まとめというか、大雑把にやるべきことの列挙です。

  • 5000通/日を超える超えないにかかわらず要件を全て満たすのが望ましい
    • SPFDKIMとDMARCをビシャッと
    • DKIMの鍵が古いなら更新する
  • メールのFrom:に使っているサブドメインの調査
    • DKIM署名がないのを回避
    • SPFがFAILするのを回避
    • SPFレコードがないなら正しく設定
    • 安易にFrom:ドメインを変えない
  • 野良MTAを駆逐する
    • 全ホストで/var/log/maillogを調べるとか
    • アプライアンスが勝手に送ってるケースもあるし
  • Gmailとか外部への転送
    • 転送しているならARCにも対応する
    • 難しそうならGmailからPOP/IMAPで取りに来てもらう
  • 大量配信しているなら1クリック購読解除の実装
    • トランザクション系メールには実装不要
    • 登録完了メールとかパスワードリセットのメールとか
  • 本文では触れてないけどスパム率0.1%を維持
  • 本文では触れてないけどGoogle Postmaster Toolsの準備
    • 送信量が少ないと統計情報とかはでない
    • 少ないなら少ないで問題ないしドメイン認証をしっかりやる
  • いろいろあって大変そう

よいお年をお迎えください。

*1:Googleが言う他の要件も

*2:購読解除される側でそのように実装すべき

*3:MUAのメニューから転送した場合は大丈夫なはず

*4:ARC関連ヘッダーを読んでいる

*5:Envelope-Fromを書き換えて転送していな限りは

*6:ARCで署名していればスパム扱いが回避できるわではない

*7:外に飛ばすキューに溜まり続けて通知されるべき情報が来なくなる