皆さんこんにちは、バックエンドエンジニアをしている永井です。Gmailの「メール送信者のガイドライン」が更新され、ユーザーがワンクリックで解除できる機能を必要とする旨が示されました。これに対応するために一部メールでワンクリック解除機能を導入しましたので、今回はその実装方法とリリース前後で苦労した話について書いていきます。
ワンクリック解除機能の実装について
ワンクリック解除機能を実現するにあたって、メール送信時に新たに付与したList-UnsubscribeヘッダーとList-IDヘッダーについて詳しく説明します。
List-Unsubscribeヘッダーについて
ワンクリック解除を行うためには、以下のヘッダーが必要となります。
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Unsubscribe: <{ワンクリック登録解除をするAPIへのurl}>
List-UnsubscribeヘッダーとList-Unsubscribe-Postヘッダーをメールに付与することで、Gmailなどのメールクライアントで以下のようにボタンが表示されるようになります(以下の画像はGmailです)。

上記のボタンを押すと、メールクライアントからList-Unsubscribeヘッダーに指定したURLに対してPOSTリクエストが送られます。しかし、リクエストボディが空のPOSTリクエストが送られてくるため、URLやクエリパラメータを使って「どのユーザー」が「どのメール」をワンクリック解除するかなどの情報を含めるようにしています。
ワンクリック解除前後のPR TIMESでの処理
上記の対応をすることで、ユーザーがメールクライアントからメールの解除ができるようになります。ここで、ワンクリック解除前後のPR TIMESでの処理について書いていきます。
ユーザーがワンクリック解除を行うとList-Unsubscribeヘッダーに指定されているURLにPOSTリクエストされますが、このURLにはJWTを含むようにしています。PR TIMESのバックエンドはPHPで作られているのでJWTのライブラリはphp-jwtを使っています。JWTを生成する時にはどのユーザーがワンクリック解除したか分かるようにユーザーについての情報を含めています。以下のようなコードでエンコードしたJWTをList-UnsubscribeヘッダーのURLに付与します。
use Firebase\JWT\JWT;
$payload = [
'sub' => "{ユーザー種類}::{ユーザーの識別子}",
'exp' => "{トークンの有効期限}",
];
JWT::encode($payload, {秘密鍵}, {鍵のアルゴリズム});そしてワンクリック解除のリクエストが来ると、付与されているJWTよりPR TIMESから送信したメールからのリクエストかどうか以下のようなコードで検証を行ない、どのユーザーがワンクリック解除したか情報を取得しています。
use Firebase\JWT\JWT;
$payload = JWT::decode("{リクエストで付与されていたJWT}", "{(encode時に使った秘密鍵と対になっている)公開鍵}", ["{鍵のアルゴリズム}"]);
//ここでユーザー情報を取得しています
$sub = explode('::', $payload->sub);取得したユーザー情報からワンクリック解除を行ったことがDB上に記録されます。これにより、ユーザーごとの解除状況を把握することができます。次に、プレスリリースメールの送信処理です。メールを送信する前にDBを確認しワンクリック解除を行ったユーザーについてはメールの送信対象から除外します。
このようにして、ユーザーがGmail等のメールクライアントでワンクリック解除を行い、PR TIMESがそれをどのように処理し、最終的にどのユーザーに対してメールが送信されるのか変更しました。この辺りの全体の流れについては、以下に図表も掲載します。

List-IDヘッダーについて
List-IDとは
List-IDは、一意のメーリングリストを識別するために使用される識別子です。活用例として、GmailではこのList-IDをメールのフィルタリング(MLフィルタ)の設定に活用することが可能です。MLフィルタ設定を選択すると、メールに付与された特定のList-IDヘッダーの値でフィルタリングを作成できます。具体的な作成方法は割愛しますが、主にはList-IDヘッダーの ‘<>’ 内に指定された文字列で作成されます。
Gmailの登録解除モーダルの表示について
List-Unsubscribeヘッダーに対応すると、ワンクリック解除ボタンが表示されこのボタンをクリックすると、登録解除の確認を求めるモーダルが表示されます。

このモーダルには「今後〜からメールを受け取らないようにしますか?」というメッセージが表示されます。「〜」の部分には、メールに付与されたList-IDヘッダーの値が表示されます。List-IDを設定していない場合は「〜」の部分がunknownと表示されてしまいます。適切に表示されないと、メールの発信源の判断がしにくくなることや不信感を与えるため、正しく表示されるようにしました。また、List-IDの指定の仕方についてはRFCでフォーマットが書かれていますのでこちらに従うようにしました。
[phrase] "<" list-id ">"
(例)
List-Id: "An internal CMU List" <0Jks9449.list-id.cmu.edu>
詳細な情報は RFC2919 に記載されています。'<>’で囲まれた部分が主に識別子として使用され、左側の文字列は自由に記述できます。ただし、'<>’内は常に一意であることが求められます。そのため、先述のMLフィルタの作成もここで指定された値に基づいてフィルタリングを行います。
以上がPR TIMESでワンクリック解除を対応するにあたっての実装についての話でした。次はリリース前後で苦労した話をしていこうと思います。
リリース前後で苦労した話
リリースするまでやリリースした後でメールの仕様やPR TIMES固有の問題により色々苦労しましたが、その中でチームメンバーと協力して乗り越えた部分や学んだことがあるので書いていきます。
メーリングリストのメールアドレスではList-UnsubscribeヘッダーやList-IDヘッダーが書き換えられてしまった
メーリングリストのメールアドレスでは、メールクライアント側でList-UnsubscribeヘッダーやList-IDヘッダーが書き換えられてしまう問題が生じました。List-Unsubscribeヘッダーは自動的にmailto:スキーマへと書き換えられ、それによりユーザーがワンクリック解除ボタンを押すとメーリングリストから抜ける形となります。
# mailinglist@prtimes.jpの場合
List-Unsubscribe: <mailto:googlegroups-manage+545376180+unsubscribe@googlegroups.com>, <https://groups.google.com/a/prtimes.jp/group/release/subscribe>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-ID: <mailinglist.prtimes.jp>
その結果、私たちが用意しているAPIにリクエストが届かず、そのユーザーに対するメールの送信停止が管理できなくなりました。結果的には、ワンクリック解除することでメーリングリストからは抜けるのでメールは送信されなくなります。この問題についてはチーム内で話し合ってメールの仕様と理解して受け入れることとしました。また、QAの時はメーリングリストではないメールアドレスでテストしたり、「メールソース表示」からList-Unsubscribeヘッダーに指定されてるURLからPostmanというツールやターミナルからcurlコマンドの実行でリクエストして確認するようにしました。
テストケースが大量になってしまった
「メール送信部分がレガシーコードにより正確な影響範囲を把握するのが難しい」かつ「単純にワンクリック解除に対応するメールが多い」ことからQAテストケースが増大しました。QAエンジニアだけでは対応は困難であったため、チームメンバー全員でテストケースの実施を行う方針を立てました。さらに、エンジニアもテストデータ作成やテストケースの作成を通じてテストに参加し、チームで力を合わせてQAを進めることができました。
影響範囲が大きいリリースが多かった
PR TIMESではプレスリリースをメディアへ配信する性質上メール送信がとても重要になってきます。もしバグなどでメールが送信されなくなるとユーザーへの影響がとても大きくなってしまいます。そのようなリリースが今回のワンクリック解除の対応では多かったです。そういったリリースの中には、リリース後の本番環境でQAを行うことのリスクが高いものもありました。しかし、QAせず経過観察にして遅れてバグが発見されるリスクの方が許容できないケースもありました。本番環境でQAを行うことのリスクとは何か具体的に書いていきます。
本番環境でQAを行うことのリスク
例えば、テスト用にプレスリリースを配信してその時に送信されるメールを確認する必要がありました。この際、一般公開やユーザーへのメール送信を避ける設定が必須となります。これに失敗するとインシデントに直結するリスクがありました。また、その際には混み入ったDB操作もして意図した通りにQAができるようにしなければならず、DBの誤操作のリスクもありました。しかし、このリスクよりプレスリリースを配信した時にメールが万が一送信されない場合の方が重大なインシデントになるため経過観察ではなくリリース後すぐにQAを行うことになりました。
そこで本番環境でQAを行うことのリスクを減らすために、QAの手順書を作りリリース前に開発環境でうまくいくことを確認して、実施する時は毎回チームメンバーとペアを組んで実施しました。また、万が一の時に影響が大きいので事前に開発本部や他部署にテスト用プレスリリース配信について行うことを伝えるようにしました。
他部署と連携する時について
他の場面ではQAを行う関係上で他部署の方に協力を求めることも必要でした。私は今までそのようなことをあまりしたことがなかったのですが、今回のワンクリック解除の対応は他部署の人に自分たちが行なっていることを伝える機会が多かったです。その時に、先輩から他部署の人に伝える時は「シンプルに伝えた方が良いよ」、「具体的にどんなリスクがあるのか伝えるだけだと不安にさせたままになるから、開発環境で確認してできる限り安全であることも確認していると伝えると良いよ」など色々教えてもらいとても勉強になりました。
最後に
全体的にワンクリック解除の対応はメールの仕様であったりPR TIMES固有の問題があったりなどで大変でしたが、チームが一つになってワンクリック解除をリリースするという目標に向かって走れたのがとても良かったなと思っています!
ここまで読んで頂きありがとうございました、少しでも参考になることがあれば幸いです。

