独自ドメインから少しだけメールを送りたいサイドプロジェクトがあるとする。サインアップ確認、パスワードリセット、たまの通知。それくらい。個人のGmail認証情報をコードに埋め込みたくはないし、月200通のために本格的なトランザクションメールサービスを契約するのも気が重い。そして、受信転送のためにすでにKaiMailを使っている。
朗報だ。同じKaiMailアカウントで送信もできる。本記事では、PythonとRubyでそれを行う方法を、私が削れる限り最小のコードで紹介する。
| 設定項目 | 値 |
|---|---|
| SMTPサーバー | mail.kaimail.net |
| ポート | 587 |
| 暗号化 | STARTTLS |
| ユーザー名 | KaiMailのメール |
| パスワード | SMTP用パスワード |
ただのSMTP submissionである。独自仕様は何もない。まともなSMTPライブラリならどの言語でも動く。下の例では、PythonとRubyで普通に手が伸びるツールを使っている。
Pythonの標準ライブラリで全部足りる。pip installは不要だ。
import os
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "[email protected]"
msg["To"] = "[email protected]"
msg["Subject"] = "Hello from KaiMail"
msg.set_content("Sent via KaiMail SMTP.")
with smtplib.SMTP("mail.kaimail.net", 587) as smtp:
smtp.starttls()
smtp.login(os.environ["KAIMAIL_USER"], os.environ["KAIMAIL_PASS"])
smtp.send_message(msg)
保存して、KAIMAIL_USERとKAIMAIL_PASSをシェルでexportし、実行する。これで全部だ。
最初にハマりがちな点が2つある。
starttls()はlogin()より先に呼ぶ。 順序が逆だとパスワードを暗号化されていない接続で送ることになる。KaiMail側でログインを拒否するが、サーバーに救ってもらうことを当てにしてはいけない。常に先に接続を暗号化する。FromはKaiMail上で自分が所有しているドメインでなければならない。 サーバーは送信のたびに検証する。[email protected]や、自分のアカウントに追加していないドメインで送ろうとすると拒否される。これはバグではなく仕様だ。あなたのアカウントが他人のなりすましに使われないようにするための仕組みである。HTMLメールが必要なら、set_content()の後にmsg.add_alternative(html, subtype="html")を足す。ほかのコードはそのままでいい。
Rubyにも標準ライブラリのnet/smtpがあるが、デファクトスタンダードはmail gemで、こちらの方がはるかに扱いやすい。依存1つは妥当な対価だと思う。
require "mail"
Mail.defaults do
delivery_method :smtp,
address: "mail.kaimail.net",
port: 587,
user_name: ENV["KAIMAIL_USER"],
password: ENV["KAIMAIL_PASS"],
authentication: :plain,
enable_starttls_auto: true
end
Mail.deliver do
from "[email protected]"
to "[email protected]"
subject "Hello from KaiMail"
body "Sent via KaiMail SMTP."
end
gem install mailして、同じ2つの環境変数を設定し、実行する。
Rubyのハマりどころは本質的にPythonと同じで、キーの名前が違うだけだ。
enable_starttls_auto: trueが、Pythonでのsmtp.starttls()の役割を果たしている。 これがないと、gemは平文で認証情報を送ろうとする。authentication: :plainで正しい。 STARTTLSが暗号化を担当し、PLAIN認証は暗号化されたチャネルの上で動く。「もっと安全そうだから」と:loginや:cram_md5に切り替える必要はない。安全性は変わらない。Fromの検証は同じく適用される。 KaiMailで所有していないドメインは失敗する。Railsアプリの中で使う場合は、Mail.defaultsブロックをそのままconfig/environments/production.rbのconfig.action_mailer.smtp_settingsに置けばよい。ActionMailerは内部で同じgemを使っているので、キーは完全に同じだ。
PHP組み込みのmail()関数はSMTPには使い物にならないことで有名だ。ローカルにsendmailの設定が要り、認証も扱えず、エラー報告もほぼない。使わない方がいい。代わりにPHPMailerを使う。10年以上前からデファクトスタンダードだ。
<?php
require "vendor/autoload.php";
use PHPMailer\PHPMailer\PHPMailer;
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = "mail.kaimail.net";
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Username = getenv("KAIMAIL_USER");
$mail->Password = getenv("KAIMAIL_PASS");
$mail->setFrom("[email protected]");
$mail->addAddress("[email protected]");
$mail->Subject = "Hello from KaiMail";
$mail->Body = "Sent via KaiMail SMTP.";
$mail->send();
composer require phpmailer/phpmailerして、同じ2つの環境変数を設定し、php send.phpで実行する。
PHPでのハマりどころ。
SMTPSecure = ENCRYPTION_STARTTLSがPythonのstarttls()に相当する。 もう1つの定数ENCRYPTION_SMTPSは465での暗黙的TLSを使う。KaiMailは465で待ち受けていないので、こちらは使わない。new PHPMailer(true)の引数trueは例外を有効にする。 これがないと、エラーはオブジェクトに静かに溜まり、send()は単にfalseを返すだけになる。最初に「何も起きなかったのはなぜか」をデバッグするのに何時間も溶かすことになる。必ずtrueを渡すこと。Fromの検証は同じく適用される。 なりすましの送信ドメインはサーバーに拒否される。Laravelの中で使うなら、PHPMailerは不要だ。フレームワークに同梱されているSymfony Mailerで済む。.envにMAIL_MAILER=smtp、MAIL_HOST=mail.kaimail.net、MAIL_PORT=587、MAIL_ENCRYPTION=tls、それにMAIL_USERNAMEとMAIL_PASSWORDを設定すれば、あとはLaravelがやってくれる。
実際のサポート問い合わせから、もう少し挙げておく。
FromのドメインはKaiMailで所有しているものでなければならない。 サーバーはなりすまし送信を拒否する。新しいドメインの追加はダッシュボードで1分で終わるが、送信前に必ず行う必要がある。このスクリプトは「動く最小のもの」だ。本番コードでは、3つ追加する価値がある。
メールを受信してアプリに流し込みたい場合は、本記事の対になるWebhook統合ガイドがある。両方を合わせれば送受信両方向をカバーできる。
機能の全容はSMTP送信機能ページを参照。アカウントでまだSMTPを有効化していなければ、アナウンス記事にダッシュボード上の手順が載っている。Gmailの「Send mail as」回避策をまだ使っているなら、そのセットアップガイドに、コードを書くなら結局プログラムからのSMTPの方が綺麗だという話が書いてある。