PHP
mail
Docker
maildev
alpine

DockerでPHP-alpineのメール配信テスト(SMTP/sendmail)環境を構築する


はじめに

2019/06/15 いろいろと不備がありましたので、書き換えました。

開発するサービスは、なんらかのメールを送信する必要があります。

例えば、会員登録をする場合は、メールアドレスが会員IDになり、そのメールアドレスがただしいかどうか、一度、送信を行います。

また、クリティカルなシステムだと、ログインをしただけでもメールを送って、ログインがあったことを伝えます。

最後にこちらが本命なのですが、私の務めている会社が提供するサービスは、メールをバンバン送ります。このためメールの受信環境を構築することが重要になります。


TL;DR

Githubにあげていますので、こちらでのStep8 => Step9を御覧ください。

https://github.com/idani/nginx-http2-php-mysql


システム構成

前回用意した、DockerでPHP5-Alpineの開発環境(MySQL、Redis)に続きを作っていきます。


  • nginx

  • php-fpm


    • php5/php7



  • mysql

  • redis

  • maildev


メールのテスト環境

さっさっと調べた感じ、

が検索してでてきました。


所感

どちらも使ってみましたが、SMTP経由でのメール送信では大差ありませんでした。

ただし、どちらも本文や差出人は日本語(iso-2022-jp)でOKなのですが、どちらもサブジェクトが文字化けをするという現象を確認しました。

※これが私の設定不足なのか、不具合なのかわかりません。

サブジェクトの日本語が表示できればいいなぁっと探していたら、ISO-2022-JP に対応した maildev の dockerイメージ を作るを発見。

これでmaildevを選ぶことにしました。

Dockerイメージなので導入するだけでハッピーになれました。


maildevの導入

docker-compose.ymlに追加するだけです。

最後にコンテナを追記しました。


docker-compose.yml

version: '3'

services:
 ・・・(他のコンテナの設定を省略)・・・
maildev:
image: kanemu/maildev-with-iconv
ports:
- "1025:25"
- "8025:80"
・・・

この「docker-compose up -d」をしてコンテナを起動し、「127.0.0.1:8025」にアクセスをすると、以下の画面が表示されます。

MailDev.png


SMTPのメール送信

MailDevが起動したのですが、メールを送れないと、正しく動いているかわからないですよね。

PHPでSMTP配信をする場合は、PHPMailerがよいです。

PHPMailerは、composerで導入します。

ComposerもDockerのコンテナとして使う方が便利そうです。

data/html/composer.jsonを作成して、PHPMailerを追加します。


data/html/composer.json

{

"require": {
"phpmailer/phpmailer": "^6.0"
}
}

docker-compose.ymlにもcomposerを追加します。

「volumes」には、「composer install」を実行したいパスを指定します。


docker-compose.yml

・・・

composer:
restart: 'no'
image: composer/composer
command: install
volumes:
- ./data/html:/app

これで「docker-compose up」をした時に、composer コンテナが起動してcomposer install を実行してくれます。

# docker-compose up composer

Starting step8-php5-xdebug-redis-mail_composer_1 ... done
Attaching to step8-php5-xdebug-redis-mail_composer_1
composer_1 | Loading composer repositories with package information
composer_1 | Installing dependencies (including require-dev) from lock file
composer_1 | - Installing phpmailer/phpmailer (v6.0.7)
composer_1 | Loading from cache
composer_1 |
composer_1 | phpmailer/phpmailer suggests installing hayageek/oauth2-yahoo (Needed for Yahoo XOAUTH2 authentication)
composer_1 | phpmailer/phpmailer suggests installing league/oauth2-google (Needed for Google XOAUTH2 authentication)
composer_1 | phpmailer/phpmailer suggests installing psr/log (For optional PSR-3 debug logging)
composer_1 | phpmailer/phpmailer suggests installing stevenmaguire/oauth2-microsoft (Needed for Microsoft XOAUTH2 authentication)
composer_1 | phpmailer/phpmailer suggests installing symfony/polyfill-mbstring (To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2))
composer_1 | Generating autoload files
step8-php5-xdebug-redis-mail_composer_1 exited with code 0

次に、メールの配信テストを記述していきます。

PHPMailerのSMTPサンプルそのままになっています。


data/html/index.php

       //////////////////////////////////////////////////////

// メール
//////////////////////////////////////////////////////
// https://github.com/PHPMailer/PHPMailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

// Load Composer's autoloader
require 'vendor/autoload.php';

// Instantiation and passing `true` enables exceptions
$mail = new PHPMailer;

try {
//Server settings
echo '<pre class="log">';
echo 'SMTPでメール配信' . PHP_EOL;

$mail->SMTPDebug = 2; // Enable verbose debug output
$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = 'maildev'; // Specify main and backup SMTP servers
// $mail->SMTPAuth = false; // Enable SMTP authentication
// $mail->Username = 'user@example.com'; // SMTP username
// $mail->Password = 'secret'; // SMTP password
$mail->SMTPSecure = false; // Enable TLS encryption, `ssl` also accepted
$mail->SMTPAutoTLS = false;
$mail->Port = 25; // TCP port to connect to

//Recipients
$mail->setFrom('from@example.com');
$mail->addAddress('smtp@example.net'); // Add a recipient

// Attachments
// $mail->addAttachment('/var/www/html/index.php'); // Add attachments
$mail->addAttachment('/var/www/html/img.jpg', 'img.jpg'); // Optional name

// // Content
// $mail->isHTML(true); // Set email format to HTML
// $mail->Subject = 'Here is the subject';
// $mail->Body = 'This is the HTML message body <b>in bold!</b>';
// $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

$mail->Subject = mb_encode_mimeheader('日本語サブジェクト(SMTP)');
$mail->Encoding = '7bit';
$mail->CharSet ='ISO-2022-JP';

$mailBody =<<< EOL
日本語のメールのテストです

改行も問題ないと思います

SMTPで配信しています
EOL;

$mail->Body = mb_convert_encoding($mailBody, "JIS", "UTF-8");

$mail->send();
echo 'Message has been sent';
} catch (Exception $e) {
echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}
echo '</pre>';


これで「127.0.0.1」にアクセスをすると以下のような画面が表示されます。

「data/html/index.php」にテスト配信のサンプルコードを記述しています。

PHPMailerのデバッグモードのログが出力されます。

127.0.0.1_(iPad Pro).png

そして、「maildev」に、以下のようにメールが届きます。

サブジェクトも本文も日本語で読めますね。

2019-06-16_08h35_27.png


sendmailでメール送信

alpine linuxでは、sendmailもbusyboxで実現されています。

# docker exec -it step8-php5-xdebug-redis-mail_app_1 /bin/ash

/var/www/html # which sendmail
/usr/sbin/sendmail
/var/www/html # sendmail -V
sendmail: unrecognized option: V
BusyBox v1.28.4 (2018-12-31 18:05:13 UTC) multi-call binary.

Usage: sendmail [-tv] [-f SENDER] [-amLOGIN 4<user_pass.txt | -auUSER -apPASS]
[-w SECS] [-H 'PROG ARGS' | -S HOST] [RECIPIENT_EMAIL]...

Read email from stdin and send it

Standard options:
-t Read additional recipients from message body
-f SENDER For use in MAIL FROM:<sender>. Can be empty string
Default: -auUSER, or username of current UID
-o OPTIONS Various options. -oi implied, others are ignored
-i -oi synonym, implied and ignored

Busybox specific options:
-v Verbose
-w SECS Network timeout
-H 'PROG ARGS' Run connection helper. Examples:
openssl s_client -quiet -tls1 -starttls smtp -connect smtp.gmail.com:25
openssl s_client -quiet -tls1 -connect smtp.gmail.com:465
$SMTP_ANTISPAM_DELAY: seconds to wait after helper connect
-S HOST[:PORT] Server (default $SMTPHOST or 127.0.0.1)
-amLOGIN Log in using AUTH LOGIN (-amCRAM-MD5 not supported)
-auUSER Username for AUTH
-apPASS Password for AUTH

If no -a options are given, authentication is not done.
If -amLOGIN is given but no -au/-ap, user/password is read from fd #4.
Other options are silently ignored; -oi is implied.
Use makemime to create emails with attachments.

「-S HOST[:PORT] Server (default \$SMTPHOST or 127.0.0.1)」でsendmailのリレー先を設定できます。

しかも、「$SMTPHOST」という環境変数で指定できます。

「.env」ファイルにSMTPHOSTを追記します。


.env

MYSQL_RANDOM_ROOT_PASSWORD=yes

MYSQL_DATABASE=step3
MYSQL_USER=db_user
MYSQL_PASSWORD=password

# REDIS関係
REDIS_PORT=6379
REDIS_PASSWORD=password

# mail
SMTPHOST=maildev


これでsendmailの設定も完了です。

index.phpにsendmailの送信サンプルを追記しました。

こちらもPHPMailer/sendmailサンプルをほぼコピーしています。


data/html/index.php

        echo '<pre class="log">';

echo 'sendmailでメール配信' . PHP_EOL;
$mail2 = new PHPMailer;
$mail2->isSendmail();
//Recipients
$mail2->setFrom('from@example.com');
$mail2->addAddress('sendmail@example.net'); // Add a recipient

$mail2->Subject = mb_encode_mimeheader('日本語サブジェクト(sendmail)');
$mail2->Encoding = '7bit';
$mail2->CharSet ='ISO-2022-JP';

$mailBody =<<< EOL
日本語のメールのテストです

改行も問題ないと思います

sendmailで配信しています
EOL;

$mail2->Body = mb_convert_encoding($mailBody, "JIS", "UTF-8");

$mail2->send();
echo 'Message has been sent';
echo '</pre>';


これで「127.0.0.1」をリロードすると、sendmailでのメールも届いています。

2019-06-16_08h38_36.png


sendmailに問題が。。。。(2019/06/16 追記)

sendmailの準備ができたので、開発を進めていましたが、メールが送信できない事象がありました。

実は、busyboxのsendmailは、受信者名にメールアドレスしか指定ができないようです。

どういうことかというと

To: sendmail@example.net

はOKのなのですが、

To: sendmail user <sendmail@example.net>

``

では送信ができないのです。

以下のように「/data/html/index2.php」を用意しました。
index.phpとの差分は、アドレスの指定方法の違いになります。

```php:data/html/index2.php
・・・・
$mail->setFrom('from@example.com', 'Mailer');
$mail->addAddress('smtp@example.net', 'Smtp User'); // Add a recipient
・・・
$mail2->setFrom('from@example.com', 'Mailer');
$mail2->addAddress('sendmail@example.net', 'Sendmail User'); // Add a recipient

127.0.0.1/index2.phpを開くと、SMTP/Sendmaiilで2通届くはずなのですが、SMTPの1通しか届きません><

2019-06-16_08h47_16.png

Dockerのログを確認すると、「stderr: "sendmail: bad address 'sendmail@example.net>'」とあり、アドレスの指定が悪いようです。


# docker logs step8-php5-xdebug-redis-mail_app_1
[16-Jun-2019 08:30:33] NOTICE: fpm is running, pid 1
[16-Jun-2019 08:30:33] NOTICE: ready to handle connections
172.18.0.5 - 16/Jun/2019:08:30:43 +0900 "GET /index.php" 200
[16-Jun-2019 08:44:29] WARNING: [pool www] child 8 said into stderr: "sendmail: bad address 'sendmail@example.net>'"
172.18.0.5 - 16/Jun/2019:08:44:29 +0900 "GET /index2.php" 200


SSMTPの導入

ここからのサンプルは、step9になります。

senmailとして、ssmtpを導入します。


app/Dockerfile

FROM php:5-fpm-alpine

# timezone
ARG TZ=Asia/Tokyo

# Composer install
RUN set -eux && \
apk add --update --no-cache --virtual=.build-dependencies \
autoconf \
gcc \
g++ \
make \
tzdata && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
echo ${TZ} > /etc/timezone && \
apk add --no-cache ssmtp && \ <-ここ
pecl install redis xdebug-2.5.5 && \
apk del .build-dependencies && \
docker-php-ext-install pdo_mysql mysqli mbstring && \
docker-php-ext-enable redis xdebug


ssmtp.confを以下のようにします。

hostnameとmailhubが環境変数から取得できるとよいのですが、今回は直接指定します。


app/ssmtp.conf

root=postmaster

mailhub=maildev:25
hostname=app
FromLineOverride=YES


ssmtp.confの共有していをDocker-compose.ymlに記載します。


docker-compose.yml

・・・

app:
build: ./app
env_file: .env
environment:
DATABASE_HOST: db
REDIS_HOST: redis
depends_on:
- db
- redis
volumes:
- ./data/html:/var/www/html
- ./app/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
- ./app/ssmtp.conf:/etc/ssmtp/ssmtp.conf <-ここ
・・・

サンプルのindex.phpも送信者名、受信者名に日本語が指定するように修正をしました。

これでsendmailでも送信者名をつけて送ることができるようになりました。

index.phpの詳細はGithubを参照してください。

https://github.com/idani/nginx-http2-php-mysql/blob/master/step9-php5-xdebug-redis-mail2/data/html/index.php#L72

2019-06-16_09h48_16.png

2019-06-16_09h48_35.png


まとめ

たぶん、これで一通りの開発ができる状態になりました。