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) → Identities → Create 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 アドレスを Identities → Create identity → Email address で追加。AWS から確認メールが届くので、リンクをクリックして verify。
サンドボックス解除の申請は今回はしない。送信先がほぼ自分宛てなら、verify さえ通せばサンドボックスのまま全機能が使える(AWS の審査待ちをスキップできる)。
A-3. SMTP credentials の発行
SES Console 左メニュー SMTP settings → Create 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 > Actions で ENV_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.php に WPMS_* 定数を追加
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 つを疑ってほしい。
安達棒とアンバサダーで色々釣りたいおじさん。
Macでプログラムを書いて暮らしています。 趣味はルアーフィッシング、ギター、アクアリウムとストリートファイター(格ゲー) 。
宮崎県在住。




