おおまかな構成
PHPのmb_send_mail関数でメールを送るため、PHPと同じコンテナ内にPostfixを構築し、AWS SESを使ってメールを送信します。
構築手順
SESの設定
「Amazon SES の ID の検証」にしたがって、送信元のメールアドレスを検証しておきます。
任意の宛先にメールを送るためには、「Amazon SES サンドボックス外への移動」も必要です。
Dockerfileの記述
FROM php:5.6.37-apache
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y \
&& apt-get install -y postfix libsasl2-modules syslog-ng
COPY entrypoint.sh /usr/local/bin/
COPY html/ /var/www/html/
ENTRYPOINT ["entrypoint.sh"]
インストールしているのは以下の3つです。
- postfix
- libsasl2-modules
- SESのドキュメント「Amazon SES と Postfix の統合」にしたがってインストールしています。
- PostfixがSMTPの認証に使うようです。
- syslog-ng
- syslog-ngを起動しないと、Postfixのログが/var/log/mail.logに出力されません。1
ENV DEBIAN_FRONTEND=noninteractive は、Postfixのインストール時にインタラクティブな操作が求められるのを避けるための環境変数です。
SMTPの接続情報はコンテナ起動時に環境変数として指定したいので、Postfixの設定はentrypoint.sh内で行います。
entrypoint.shの記述
# !/bin/bash -eu
# Postfix setting
sed -i '/relayhost/d' /etc/postfix/main.cf
cat << EOT >> /etc/postfix/main.cf
relayhost = [$SMTP_ENDPOINT]:$SMTP_PORT
smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes
EOT
echo "[$SMTP_ENDPOINT]:$SMTP_PORT $SMTP_USER_NAME:$SMTP_PASSWORD" > /etc/postfix/sasl_passwd
postmap hash:/etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
postconf -e 'smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt'
cp /etc/resolv.conf /var/spool/postfix/etc/resolv.conf
# PHP setting
cat << EOT > `php -i | grep php.ini | awk '{print $NF}'`/php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mail function]
sendmail_path = /usr/sbin/sendmail -t -i
EOT
service syslog-ng start
service postfix start
exec docker-php-entrypoint apache2-foreground
Postfixの設定は、SESのドキュメント「Amazon SES と Postfix の統合」にしたがって実施していますが、2点だけドキュメントと違います。
-
master.cf内の設定変更は実施せず
master.cfに
-o smtp_fallback_relay=
という行がなかったため、コメントアウトする処理は行いませんでした。 -
resolv.confのコピーを実施
/var/spool/postfix/etc/resolv.confがないと、email-smtp.us-east-1.amazonaws.com を名前解決できないというエラーが発生しました。
SMTP認証情報の取得
SESのドキュメント「Amazon SES SMTP 認証情報の取得」にしたがって、SMTP接続情報を取得してください。
SMTPの認証情報は、IAMユーザのアクセスキーとは違います。ご注意ください。
.envを記載
環境変数をDockerfileやdocker-compose.ymlから切り離し、.gitignoreに記載することでコミットを防ぎます。
SMTP_ENDPOINT=email-smtp.us-east-1.amazonaws.com
SMTP_PORT=587
SMTP_USER_NAME=xxx
SMTP_PASSWORD=xxx
docker-compose.ymlの記述
docker buildやdocker runをシェルスクリプト化する代わりに、docker-compose.ymlを記述します。
version: '3'
services:
php-postfix-ses:
build: .
image: php-postfix-ses
env_file:
- .env
ports:
- "80:80"
起動
以下のコマンドでコンテナが起動します。
$ docker-compose up -d
イメージをビルドしていない場合、ビルドが走ります。
ビルドだけ実行したい場合は、以下のコマンドです。
$ docker-compose build
動作確認
sendmailでコンテナ内からメール送信
cat << EOT | sendmail -f from@example.com to@example.com
From: from@example.com
To: to@example.com
Subject: Hello World!
Hello World!
.
EOT
SESでは検証済みのメールアドレスからの送信のみを許可しているため、sendmailの-fオプションでenvelop-fromを設定することが必須です。
PHPでコンテナ内からメール送信
mb_send_mailでメールを送るコードを記述します。
<?php
mb_language("Japanese");
mb_internal_encoding("UTF-8");
$to = 'to@example.com';
$from = 'from@example.com';
$subject = 'Hello World!';
$body = 'こんにちは!';
$header = 'From: ' . $from;
mb_send_mail($to, $subject, $body, $header, '-f ' . $from);
?>
sendmailコマンドの場合と同様に、mb_send_mailの第5引数の-fでenvelope-fromを設定することが必須です。
docker-compose execでコンテナに接続し、phpコマンドでプログラムを実行します。
$ docker-compose exec php-postfix-ses bash
# php mail_sample.php
日本語でも問題なく送れます。
ログ確認
PHPのログ確認は以下のコマンドです。
$ docker-compose logs -f
Postfixのログ確認は以下のコマンドです。
$ docker-compose exec php-postfix-ses bash
# tail -f /var/log/mail.log
こうすればよかった...
以下の2つの理由から、PHPとPostfixのコンテナは分けるべきでした。
- この構成ではPostfixのログが/var/log/mail.logに出力されるが、本来なら標準出力に出したい。しかし、標準出力にPostfixのログを出すと、PHPのログと混ざる
- Postfixだけのコンテナなら、サンプルが大量に見つかる
なぜ 1 コンテナ 1 プロセスにすべきと言われるか、身をもって学びました。。。