Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

WordPress: localhostで動かすと自動送信メールの動作確認ができない

ebi です。今更感ある話ながらちょっとした WordPress ネタです。
タイトルは文字数の都合上、色々説明を端折っているので再度真面目に表現すると……。

WordPress を手元のローカルな開発環境で localhost と言うドメインで動かしている状況で、新規ユーザ登録をした時などに WordPress から自動送信されるメールが期待通り届かない現象に遭遇しました。
その原因の調査と回避策例を紹介します。

TL;DR

  • WordPress 内部でメール送信処理用に定義されている wp_mail() 関数はFromアドレスやToアドレスが適切なメールアドレスであるかどうかのバリデーションを行います
  • デフォルトのFromアドレスは wordpress@サイトドメイン名 であり、 localhost と言うドメイン名で動作確認していた場合、 wordpress@localhost を使用しようとします
  • このメールアドレスは RFC 2822 に違反した不適切なメールアドレスであるため、新規ユーザ登録時などの自動送信メールが送信されません
  • そのため、このような自動送信メールを動作確認する場合、以下のような方法で回避する必要があります
    • 動作確認する際のドメインを lvh.me (※個人所有ドメインのため利用にはご注意ください) や自分で hosts 定義した上で wp-test.example.com などの任意のドメインを利用する
    • wp_mail_from のフィルターフックを使い、開発中テーマ内の functions.php でデフォルトのFromアドレス指定を上書きする

当該事象の調査

前準備の開発環境構築

実際のところはプロジェクト内(なんなら何を隠そうこの TechRacho サイト関連)で遭遇していて、ソースコードの共有が難しいので、
まずは再現環境を適当に用意します。

# Dockerfile
FROM wordpress:php7.4-apache

# public wordpress docker image's PHP uid is 33(debian variants)
# see: https://hub.docker.com/_/wordpress/
ARG uid=33
ARG gid=33
RUN usermod -u $uid www-data
RUN groupmod -g $gid www-data

# install wp-cli
RUN set -ex; \
  curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; \
  chmod +x wp-cli.phar; \
  mv wp-cli.phar /usr/local/bin/wp

# for mail setting
RUN apt-get update; apt-get install -y msmtp
COPY docker/msmtprc /etc/msmtprc
COPY docker/php.ini "$PHP_INI_DIR/conf.d/"
# compose.yml
services:
  wordpress:
    build: .
    ports:
      - 80:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: username
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./web_root:/var/www/html

  db:
    image: mysql:5.7
    platform: linux/x86_64
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: username
      MYSQL_PASSWORD: password
      MYSQL_RANDOM_ROOT_PASSWORD: '1'

  mail:
    image: djfarrelly/maildev
    ports:
      - "1080:80"
# docker/msmtprc
host mail
from "postmaster@example.com"
# docker/php.ini
sendmail_path = "/usr/bin/msmtp -t"

Docker を立ち上げて WP-CLI で WordPress サイトの初期設定をします。

ebi@LAPTOP-R8D4FNMF:~/techracho-wp-mail$ docker compose build --build-arg uid=$(id -u) --build-arg gid=$(id -g)
[+] Building 31.0s (12/12) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                          0.0s
 => => transferring dockerfile: 579B                                                                                                                                                          0.0s
 => [internal] load .dockerignore                                                                                                                                                             0.0s
 中略
 Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
ebi@LAPTOP-R8D4FNMF:~/techracho-wp-mail$ docker compose up -d
[+] Running 4/4
 ⠿ Network techracho-wp-mail_default        Created                                                                                                                                           0.0s
 ⠿ Container techracho-wp-mail-mail-1       Started                                                                                                                                           1.7s
 ⠿ Container techracho-wp-mail-wordpress-1  Started                                                                                                                                           1.7s
 ⠿ Container techracho-wp-mail-db-1         Started 
ebi@LAPTOP-R8D4FNMF:~/techracho-wp-mail$ docker compose exec wordpress bash
root@f2941d5faeed:/var/www/html# wp core install --url=localhost --title='wp_mail test site' --admin_user=admin --admin_email=admin@example.com --allow-root
Admin password: BYKCZyWw&(3B#Eh&#!
Success: WordPress installed successfully.

そんでもって流れ的には順番が逆転しちゃってるかもなんですが、
試しに wp_mail() を WP-CLI 上で実行して送信に失敗する、つまり返り値が false になる環境になっていることまで確認しちゃいます。

root@f2941d5faeed:/var/www/html# wp shell --allow-root
wp> wp_mail( 'mail_to@example.com', 'mail test', 'hello wp_mail!' );
=> bool(false)

ね?メール送信に失敗してるでしょ?

期待通り動かない原因箇所を特定する

実際には最初は wp_mail() に原因があるとは分かってない訳で、メール送信周りが怪しそうなのでベースになってそうな関数定義を探ろうと言うアイデアに至るには勘や経験が必要ですが、その辺の過程は端折ります。

さて、お次は具体的にどこのソースコード行でどんな原因で動作しなくなるのかを特定するデバッグ技術を要します。
以下の流れで調査しました。

  1. WordPressが自動送信メール送信時に利用しているであろう wp_mail() 関数に当たりを付けた(経緯は割愛)
  2. ドキュメントより、上記関数の定義場所が wp-includes/pluggable.php であることを突き止めた
  3. 実際に当該ファイルのソースコードを軽く読んでみると、 try-catch の例外処理で catch されてエラーが握り潰されていることに気付く
  4. この try-catch の例外処理を片っ端からコメントアウトして、再度自動送信メールの操作を試す

と言うことでやってみましょう。
catch 文中の return false; 等を片っ端からコメントアウトして、軒並み throw $e; に変えて wp_mail() が呼び出されるであろう操作をリベンジしてみます。

root@f2941d5faeed:/var/www/html# wp shell --allow-root
wp> wp_mail( 'mail_to@example.com', 'mail test', 'hello wp_mail!' );
[27-Dec-2022 05:57:51 UTC] PHP Fatal error:  Uncaught PHPMailer\PHPMailer\Exception: Invalid address:  (From): wordpress@localhost in /var/www/html/wp-includes/PHPMailer/PHPMailer.php:1308
Stack trace:
#0 /var/www/html/wp-includes/pluggable.php(406): PHPMailer\PHPMailer\PHPMailer->setFrom('wordpress@local...', 'WordPress', false)
#1 phar:///usr/local/bin/wp/vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php(46) : eval()'d code(1): wp_mail(Array, 'mail test', 'hello wp_mail!')
#2 phar:///usr/local/bin/wp/vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php(46): eval()
#3 phar:///usr/local/bin/wp/vendor/wp-cli/shell-command/src/Shell_Command.php(52): WP_CLI\Shell\REPL->start()
#4 [internal function]: Shell_Command->__invoke(Array, Array)
#5 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandFactory.php(100): call_user_func(Array, Array, Array)
#6 [internal function]: WP_CLI\Dispatcher\CommandFactory::WP_CLI\Dispatcher\{closure}(Array, Array)
#7 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/Subcommand.php(4 in /var/www/html/wp-includes/PHPMailer/PHPMailer.php on line 1308
Fatal error: Uncaught PHPMailer\PHPMailer\Exception: Invalid address:  (From): wordpress@localhost in /var/www/html/wp-includes/PHPMailer/PHPMailer.php:1308
Stack trace:
#0 /var/www/html/wp-includes/pluggable.php(406): PHPMailer\PHPMailer\PHPMailer->setFrom('wordpress@local...', 'WordPress', false)
#1 phar:///usr/local/bin/wp/vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php(46) : eval()'d code(1): wp_mail(Array, 'mail test', 'hello wp_mail!')
#2 phar:///usr/local/bin/wp/vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php(46): eval()
#3 phar:///usr/local/bin/wp/vendor/wp-cli/shell-command/src/Shell_Command.php(52): WP_CLI\Shell\REPL->start()
#4 [internal function]: Shell_Command->__invoke(Array, Array)
#5 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandFactory.php(100): call_user_func(Array, Array, Array)
#6 [internal function]: WP_CLI\Dispatcher\CommandFactory::WP_CLI\Dispatcher\{closure}(Array, Array)
#7 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/Subcommand.php(4 in /var/www/html/wp-includes/PHPMailer/PHPMailer.php on line 1308
Error: There has been a critical error on this website.Learn more about troubleshooting WordPress. There has been a critical error on this website.

以上より PHP Fatal error: Uncaught PHPMailer\PHPMailer\Exception: Invalid address: (From): wordpress@localhost のメッセージ内容から、
ははーん、From指定のメールアドレスが悪かったんかと言う結論に辿り着きました。

回避策① 動作確認ドメインを変更する

デフォルトのFromアドレスを指定しているのは以下のような定義部分で network_home_url() と言うのが WordPress に登録しているホームURLを返すので、 http://localhost と言うサイトとしてデータを登録している以上はこれを変えることはできません。

// wp-includes/pluggable.php
            // Get the site domain and get rid of www.
            $sitename   = wp_parse_url( network_home_url(), PHP_URL_HOST );
            $from_email = 'wordpress@';

            if ( null !== $sitename ) {
                if ( 'www.' === substr( $sitename, 0, 4 ) ) {
                    $sitename = substr( $sitename, 4 );
                }

                $from_email .= $sitename;
            }

WordPress の初期設定で登録したサイトURLを更新しましょう。その後メール送信のリベンジをします。

root@2f0241abdb2d:/var/www/html# wp option update siteurl 'http://lvh.me' --allow-root
Success: Updated 'siteurl' option.
root@2f0241abdb2d:/var/www/html# wp option update home 'http://lvh.me' --allow-root
Success: Updated 'home' option.
root@2f0241abdb2d:/var/www/html# wp shell --allow-root
wp> wp_mail( 'mail_to@example.com', 'mail test', 'hello wp_mail!' );
=> bool(true)

無事メールが届きました

回避策② デフォルトのFromアドレス定義を変更する

ドキュメントに記載の通り、Fromアドレスは wp_mail_from のフィルターフックを使うことで上書きができます。

// wp-includes/pluggable.php
        /**
         * Filters the email address to send from.
         *
         * @since 2.2.0
         *
         * @param string $from_email Email address to send from.
         */
        $from_email = apply_filters( 'wp_mail_from', $from_email );

と言うことで functions.php からこのフィルターフック経由で上書きしてみます。

ところでこの記事を書くために新規構築した WordPress サイトのデフォルトテーマ twentytwentythree はサイトエディターを利用して構築していくサイトテーマとなっており、最早 functions.php すらありませんでした。
そのため、 functions.php の名前でファイルを新規追加するところからで、

// wp-content/themes/twentytwentythree/functions.php
<?php
function custom_default_mail_from( $from_email ) {
  if ( preg_match('/@localhost\z/', $from_email) ) {
    return 'wp@example.com';
  }
}
add_filter( 'wp_mail_from', 'custom_default_mail_from' );

の定義を追加して、リベンジすると

root@2f0241abdb2d:/var/www/html# wp shell --allow-root
wp> wp_mail( 'mail_to@example.com', 'mail test', 'hello wp_mail!' );
=> bool(true)

無事メール届きました。

まとめ

回避できたはできたんですけど、 lvh.me としてサイト構築するのを必ずやるのもなんか嫌だし、
かと言って functions.php に開発環境の時だけ wp_mail_from にフィルターフックさせる定義を記載するのも微妙だしと思ってしまっていて、正直どうしたもんかなぁと言うオチではあります。
第三の選択肢のアイデアお待ちしてます。

ところで、 twentytwentythree のテーマを覗き見しても改めて思い知らされましたが、その他 Web 技術と同様に WordPress に関しても近年は良くも悪くも急速に発展し続けているように思います。
( IE11 のサポートは切られましたし、長年ビルトインされていた jQuery1 系もようやく3系になったりもしました)
付け焼き刃の知識だけでは最早 WordPress サイト扱えますよ、作れますよ、なんて言えないレベルのギャップを個人的には感じている今日この頃ですが、
今後もマイペースにキャッチアップしたり気付いたことがあればまた発信していきます。

関連記事

lvh.me ドメインを使って、サブドメイン形式の WordPress マルチサイトの Docker 開発環境構築をしよう

WP REST API × NuxtJS で SSG するサイト作り


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。