AWSロゴ

WordPress × Amazon SES × DMARC でメール認証三冠 PASS させた話

WordPress のサイトから送るメールが Gmail に届かない、あるいは「迷惑メール」に直行する……その原因はだいたい送信元の認証不足です。本記事では、EC2 上の WordPress で wp_mail() がサイレント失敗していた状況から、SPF / DKIM / DMARC の3つすべてを PASS させるまでの実装記録を残します。

ハマりどころも全部書いたので、同じ構成で同じ罠にハマる人の参考になれば。

続きを読む: WordPress × Amazon SES × DMARC でメール認証三冠 PASS させた話

構成

  • インフラ: EC2 (Amazon Linux 2023) + nginx + php-fpm
  • WordPress: 7
  • メール送信: WP Mail SMTP プラグイン + Amazon SES (ap-northeast-1)
  • ドメイン: 独自ドメイン、DNS は Amazon Route 53 で管理

出発点:メールが一切届かない状態

確認したのは以下。

$ ls -la /usr/sbin/sendmail
ls: cannot access '/usr/sbin/sendmail': No such file or directory

$ systemctl is-active postfix
inactive

$ php -i | grep sendmail_path
sendmail_path => /usr/sbin/sendmail -t -i

つまり PHP の mail() は存在しない sendmail バイナリを呼ぼうとしてサイレント失敗していた。WordPress の wp_mail() も内部的に mail() を経由するので同じく失敗。パスワードリセット、コメント通知、各種プラグイン通知、すべて飛ばない状態。

ローカル MTA を立てる選択肢もあるが、IP レピュテーション・逆引き DNS・OP25B 対策などを自前で管理するのは現代では割に合わない。マネージドサービスを使う。

候補は Gmail SMTP / Amazon SES / SendGrid あたり。今回は AWS インフラに揃っている ので Amazon SES を選択。EC2 から送る場合は月 62,000 通まで無料、独自ドメイン送信、DKIM/SPF/DMARC を綺麗に通せる、というメリットがある。

全体の流れ

3 レイヤーで分業する設計にした。

レイヤー担当内容
AWS Console手動操作SES Identity 作成、DKIM、受信先 verify、SMTP credentials 発行
GitHub Secrets手動操作.env 配備用 Secret の更新
リポジトリrepo 駆動デプロイWP Mail SMTP プラグイン導入、wp-config.php に WPMS_* 定数追加

Phase A: AWS SES のセットアップ

A-1. Domain identity の作成

SES Console (ap-northeast-1) → IdentitiesCreate identity で以下を設定。

  • Identity type: Domain
  • Domain: example.com(自分のドメイン)
  • Use a custom MAIL FROM domain: ✅ チェック、サブドメインに mail を指定
  • 結果: mail.example.com がカスタム MAIL FROM になる
  • SPF アライメントが綺麗に通る
  • Advanced DKIM settings:
  • Easy DKIM を選択
  • RSA_2048_BIT
  • Publish DNS records to Amazon Route 53: ✅ チェック
  • これで DKIM 用 CNAME 3 本 + MAIL FROM 用 MX/TXT が Route 53 に自動発行される

ドメインを Route 53 で管理している場合、ここをチェックするだけで DNS レコード追加が全部終わる。手動コピペでハマるリスクを避けられるので強く推奨。

数分後、Identity 一覧でステータスが Verified に変わる。Route 53 統合なら反映が早い(数秒〜数分)。

A-2. 受信先メールアドレスの verify

SES は新規アカウントだとサンドボックスモードで、verify した受信者宛てにしか送信できない

通知先となる Gmail アドレスを IdentitiesCreate identityEmail address で追加。AWS から確認メールが届くので、リンクをクリックして verify。

サンドボックス解除の申請は今回はしない。送信先がほぼ自分宛てなら、verify さえ通せばサンドボックスのまま全機能が使える(AWS の審査待ちをスキップできる)。

A-3. SMTP credentials の発行

SES Console 左メニュー SMTP settingsCreate SMTP credentials

IAM ユーザーが自動生成されて、SMTP username と SMTP password が一度だけ表示される。必ず CSV をダウンロードするか、安全な場所にコピー保管する。失うと再発行が必要。

この SMTP password は IAM Access Key とは別フォーマット(SES が SMTP 用にハッシュ変換した文字列)。

Phase B: 認証情報を env 経由で本番に配備

このプロジェクトでは GitHub Actions のデプロイ workflow が、GitHub Secret ENV_FILE_BASE64 を base64 デコードして本番 EC2 の /var/www/env/.env に配置する仕組みになっている。WP 側は vlucas/phpdotenv$_ENV から値を読む。

B-1. ローカル env/.env に追記

SES_SMTP_USER=<CSVのSMTP username>
SES_SMTP_PASS=<CSVのSMTP password>
SES_FROM_EMAIL=noreply@example.com
SES_FROM_NAME=サイト名

dotenv なのでクォート不要。値に空白や # を含まない限り素のままで OK。

B-2. GitHub Secret を更新

base64 -i ~/path/to/env/.env | pbcopy

GitHub の Settings > Secrets and variables > ActionsENV_FILE_BASE64 を Update し、ペースト保存。

これで次回デプロイから新しい .env が本番に配備される。

Phase C: WordPress 側の実装

C-1. WP Mail SMTP プラグインを導入

WordPress.org 公式の WP Mail SMTP (WPForms 製、4.x 系) をローカル Docker で導入し、code/wp-content/plugins/wp-mail-smtp/ をリポジトリに含める。

docker compose exec -T php wp plugin install wp-mail-smtp --allow-root --path=/var/www/blog

評価が高く、最終更新も活発で、定数経由の設定上書きに対応している。

C-2. wp-config.phpWPMS_* 定数を追加

WP Mail SMTP は wp-config.php 内の定数で全設定を上書きできる。DB に書く必要がなく、管理画面操作も不要。

// WP Mail SMTP プラグイン経由で Amazon SES に送信する設定。
// 認証情報・差出人は env/.env から読む(リポジトリ直書きしない)。
// env に SES_SMTP_USER / SES_SMTP_PASS / SES_FROM_EMAIL / SES_FROM_NAME を設定すること(必須)。
define('WPMS_ON', true);
define('WPMS_DO_NOT_SEND', false);
define('WPMS_MAILER', 'smtp');
define('WPMS_SET_RETURN_PATH', true);
define('WPMS_SMTP_HOST', 'email-smtp.ap-northeast-1.amazonaws.com');
define('WPMS_SMTP_PORT', 587);
define('WPMS_SSL', 'tls');
define('WPMS_SMTP_AUTH', true);
define('WPMS_SMTP_USER', $_ENV['SES_SMTP_USER'] ?? '');
define('WPMS_SMTP_PASS', $_ENV['SES_SMTP_PASS'] ?? '');
define('WPMS_MAIL_FROM', $_ENV['SES_FROM_EMAIL'] ?? '');
define('WPMS_MAIL_FROM_FORCE', true);
define('WPMS_MAIL_FROM_NAME', $_ENV['SES_FROM_NAME'] ?? '');
define('WPMS_MAIL_FROM_NAME_FORCE', true);

ポイントは fallback を空文字にしたこと。?? 'noreply@example.com' のように実メアドをコードに書くと、リポジトリ内に PII リテラルが残ってしまう。env に値があれば使う、なければ空文字、というシンプルな設計に揃えた。env 必須化されるが、運用上は env を必ず配備する設計なので問題なし。

C-3. デプロイして有効化

git push → GitHub Actions が rsync でプラグイン本体を本番に配備。

ssh ec2-user@example.com 'wp plugin activate wp-mail-smtp --allow-root --path=/var/www/blog'

ハマりどころ 1: DMARC が FAIL

最初のテスト送信は届いた。Gmail で「メッセージのソースを表示」で確認するとこうなった。

SPF:    PASS
DKIM:   PASS
DMARC:  FAIL

SPF / DKIM は通っているのに DMARC だけ FAIL。原因は単純で、DMARC レコードが DNS に存在しなかったから。

SES の Identity verify では DKIM の CNAME と MAIL FROM の MX/TXT は自動追加されるが、DMARC レコードは別途自分で追加する必要がある。これは SES に限らず多くのメール送信サービスに共通する落とし穴。

DMARC レコードを追加

Route 53 の Hosted Zone で TXT レコードを追加。

  • Record name: _dmarc
  • Type: TXT
  • Value: v=DMARC1; p=none; rua=mailto:report@example.com
  • TTL: 300

p=none は「監視のみ」モード。最初からいきなり p=reject にすると誤判定でメールが破棄される可能性があるので、まずは none で運用観察、慣れたら quarantine → reject と段階的に強化するのが定石。

rua=mailto:... は集計レポートの送信先。自分宛にしておくと「このドメインのメール認証はこんな状況」が日次でわかる。

ハマりどころ 2: Route 53 の TXT レコード入力でクォートが二重に

dig +short TXT _dmarc.example.com で確認したらこんな結果。

"v=DMARC1; p=none; rua=mailto:report@example.com\""

末尾に \"" が見える。これは dig が「データ内に含まれる " 文字」をエスケープして表示したもの。つまり TXT データの末尾に余分な " が 1 文字混入していた

原因は Route 53 のコンソール入力欄で「ダブルクォートで囲んで入力」してしまったこと。新しい Route 53 UI は値を入力すると自動でクォートで囲んでくれる仕様らしく、自分でクォートを書くと二重に囲まれてしまう

修正は簡単で、Route 53 UI でレコードを編集し、値欄の中身をクォートなしで入れ直す。

v=DMARC1; p=none; rua=mailto:report@example.com

保存後に再度 dig すると正常な値になっている。

"v=DMARC1; p=none; rua=mailto:report@example.com"

外側の " は dig が TXT レコードを表示する標準形式(データの一部ではない)。

再テスト

DMARC レコード修正後にもう一度テスト送信。

SPF:    PASS
DKIM:   PASS
DMARC:  PASS

三冠 PASS 達成。

検証コマンド集

実装後の確認用にまとめておく。

本番で WPMS 定数が正しく読まれているか

ssh ec2-user@example.com '
wp eval "
echo defined(\"WPMS_ON\") && WPMS_ON ? \"true\" : \"false\";
" --allow-root --path=/var/www/blog'

テスト送信

ssh ec2-user@example.com '
wp eval "
\$r = wp_mail(\"your@gmail.com\", \"SES test\", \"body\");
echo \$r ? \"true\" : \"false\";
" --allow-root --path=/var/www/blog'

true が返れば SMTP 接続まで成功。実際に届くかは Gmail を確認する。

認証ステータスの確認

Gmail の受信メール → 三点メニュー → オリジナルを表示(メッセージのソース)で、

SPF:    PASS
DKIM:   PASS
DMARC:  PASS

の 3 つが揃っていれば認証完璧。

DMARC レコードが世界中で見えているか

dig +short TXT _dmarc.example.com
dig @8.8.8.8 +short TXT _dmarc.example.com

Google DNS でも同じ値が返れば反映完了。

まとめ

  • Amazon SES の Domain verify は Route 53 統合で 1 クリック、Console 操作はわずか
  • DKIM/SPF は SES が自動追加するが、DMARC は自分で追加する必要がある
  • Route 53 の TXT レコードはクォートなしで値だけ入れる(クォートで囲むと二重になる罠)
  • WordPress 側は wp-config.php の WPMS_* 定数で SMTP 設定を完結(管理画面で設定する必要なし)
  • 認証情報は env 経由、リポジトリには直書きしない
  • 三冠 PASS が揃って初めて、配信信頼性が最大になる

ハマりどころは「DMARC レコード追加忘れ」と「TXT レコードのクォート二重化」の 2 つが大きい。同じ構成で詰まったら、まずこの 2 つを疑ってほしい。

Home » コンピューター » WordPress × Amazon SES × DMARC でメール認証三冠 PASS させた話