php mb_send_mail から送信したメールがGmailに弾かれてしまう場合の対処

久しぶりにPHPで業務処理を書いていてメールを送信する必要があったので何の迷いもなく `mb_send_mail` で送信していたところ、案件関係者から「メールが届かない」との報告があり調査することに。
私のアカウント（独自ドメイン）には問題なく届いていたので、この時点では個別アカウントの問題か、ドメインの問題か、はたまた送信処理が途中でコケたのか判断が付きませんでした。
なお、今回のシステムはAWS EC2上のWebサーバーで稼働しており、タイトルの通りバックエンドはPHPです。

### 調査
何はともあれ、事実確認のためにログを探ってみることに。

WebサーバーにSSHして `/var/log/maillog`を探ってみます。
はい、すぐに見つかった。

``` 
Dec 19 11:16:44 localhost postfix/smtp[18306]: 
53A007B4C9: to=<hoge.hoge@gmail.com>, 
relay=gmail-smtp-in.l.google.com[64.233.188.22]:25, delay=0.96, 
delays=0.01/0.02/0.37/0.56, 
dsn=5.7.26, 
status=bounced 
(host gmail-smtp-in.l.google.com[64.233.188.22] said: 
550-5.7.26 This message does not pass authentication checks (SPF and DKIM both 550-5.7.26 do not pass). 
SPF check for [fuga-dev01.localdomain] does not 550-5.7.26 pass with ip: [32.71.110.22].
To best protect our users from spam, 550-5.7.26 the message has been blocked. 
Please visit 550-5.7.26  https://support.google.com/mail/answer/81126#authentication for more 550 5.7.26 information. 
```
（ログ内のメールアドレス、IPアドレス、ドメインは架空のものです）

思い切り`status=bounced` ですね。
SPF、DKIMに関する理由で弾かれていて、詳細は `550-5.7.26`を見ろよ、ということなので親切にも記載してくれているドキュメントページのURLを見てみます。
[Gmail ユーザーへのメールがブロックされたり迷惑メール扱いされたりしないようにする - Gmail ヘルプ](https://support.google.com/mail/answer/81126#authentication)
ふむふむ。やはりメールが認証されていないのか。
そういえば書き忘れましたが、ログをみたところ上記のように `bounced`されているのは送信先メールアドレスのドメインが `gmail.com`のアドレスのみでした。つまりGmailを独自ドメインで使っているアドレス（私のメールアドレスはこれ）では `sent`だったので問題なく配信されています。
ということで、この時点で動作確認のために自分のgmailなメールアドレスを配信先に追加しました。

さて、ログにある `550-5.7.26`を見てみます。
[Gmail の SMTP に関するエラーとコード - Google Workspace 管理者 ヘルプ](https://support.google.com/a/answer/3726730?hl=ja)
あったあった。
```
550, "5.7.26", 
"Unauthenticated email from <ドメイン名> is not accepted due to domain's DMARC policy. 
Please contact the administrator of domain-name domain.
If this was a legitimate mail please visit Control unauthenticated mail from your domain to learn about the DMARC initiative. 
（550, "5.7.26", <ドメイン名> からの未承認のメールはドメインの DMARC ポリシーによって承認されませんでした。ドメイン <ドメイン> の管理者にお問い合わせください。正当なメールの場合は、ドメインからの未認証メールを管理するで DMARC の取り組みについてご確認ください。メールが有効であ迷惑メールでもない場合は、受信メールサーバーの管理者に連絡して、送信メールが認証チェックに合格しなかった理由を確認してください。）
```

やはりログにあった通りSPF設定不備でドメイン認証にパスしないせいで弾かれているようです。

### 対処その1 mb_send_mailに送信元情報を追加
ログをよく見ると弾かれている発信元ドメインが `fuga-dev01.localdomain` となっています。
私もこんなドメインを設定した覚えがなくおかしいな？と思っていたところ、どうやら `mb_send_mail`のパラメータとして `-f`オプションで渡すのが正しいようで、これが設定されていないため`localdomain` なんて仮のドメインでヘッダーに設定されてしまっているようでした。
コードはこんな感じです。
``` php
$add_param = '-f '.NOTIFY_MAIL_FROM;
$to_address = $value['mail_address'];
mb_send_mail($to_address, $subject, $message, $headers, $add_param);
```

修正前は第4引数までしか設定していませんでしたが、変数`$add_param`に`-f fuga-dev01@fugadomain`（fuga-dev01@fugadomainは送信元メールアドレス）という文字列を設定し、`mb_send_mail`の第5引数にセットしてみます。

原因からすると、この時点ではまだ送信できないのはわかりきっているのですが、一応確認。
やはり失敗ですが、先ほどとログの一部が変わっていました。
```
SPF check for [fuga-dev01.fugadomain] does not 550-5.7.26 pass with ip: [32.71.110.22].
```
あ、SPFチェックに引っかかっているドメイン名が実際のものになった。

### 対処その2 SPFレコード追加
ログのドメイン名が実際のものである場合には対処その1は必要ありません。
肝心のSPFレコードは、AWSのRoute53に以下のレコードで追加できました。
```
レコード名：ホスト名/空欄
タイプ：TXT
値："v=spf1 ip4:32.71.110.22 include:amazonses.com ~all"
```

レコード名は特定のホスト名を指定したい場合は入力してください。ドメイン全体に効かせたい場合は空欄でOKです。
タイプは`SPF`を選びたくなりますが`TXT`です。
値ですが、ダブルクォートで括って、内側に`v=spf1`は固定、`ip4:`に続いて送信元WebサーバーのIPアドレス（ログにも記載されています）を入れます。

私の場合はこれでgmailドメインのメールアドレスにも送信できるようになりました。

以上