Railsアプリからさくらのメールボックス経由でメールを送信しようとするとエラーメッセージが返された。 メールを送るにはまず受信しろ、という内容のものだった。
POP before SMTP
これはさくらのサーバがPOP before SMTPという認証方式を採っているためで、このサーバからメールを受信できる人だけがメールの送信をできる。
そんなわけで、送信前に一度POPサーバの方にアクセスする必要がある。 これをRailsというかRubyでは、次のようにする。
require 'net/pop' Net::POP3.auth_only('SMTPサーバのアドレス', 110, 'ユーザ名', 'パスワード')
これをメール送信のたびに実行する必要があるわけだが、DRYにやるにはどうすればいいのだろうか。
調べてみると、ActionMailerにはインターセプタという仕組みがあり、メールを送信するたびに、送信される直前のメールのデータにアクセスできる。 その中で認証用のPOPサーバへのアクセスを済ますのが丁度よいと考えた。
インターセプタを定義する
インターセプタはdelivering_email(message)メソッドが定義されたオブジェクトであればなんでもよく、検索して見つけた事例だと、たいていはクラスメソッドとしてdelivering_emailを定義したクラス自体(クラスはClassクラスのオブジェクト)をインターセプタとして使っていた。
今回は次のクラスを定義した。
require 'net/pop' class PopBeforeSmtpInterceptor def initialize(smtp_settings) @smtp_settings = smtp_settings end def delivering_email(message) Net::POP3.auth_only(@smtp_settings[:address], 110, @smtp_settings[:user_name], @smtp_settings[:password]) end end
このクラスではdelivering_emailをインスタンスメソッドとして定義している。 こうした理由は、delivering_email内でSMTPサーバの設定を参照する必要があり、それを初期化時のパラメータとして受け取るためだ。
delivering_emailをクラスメソッドとして定義して、その中でActionMailer::Base.smtp_settingsを参照する、というやり方でもいいのだろうけども。
インターセプタを登録する
config/initializersの下に適当なスクリプトファイル(例えばmail.rbなどと名付ける)を作成し、その中でActionMailer::Base.register_interceptorを実行する。
class ActionMailer::Base if delivery_method == :smtp register_interceptor PopBeforeSmtpInterceptor.new(smtp_settings) end end
アプリケーションで使うActionMailer::Baseのサブクラスが一つであれば、その定義の中でregister_interceptorを呼んでやるのが良いと思うが、今回は複数のメーラークラスがあったので、まとめて対応するためにActionMailer::Baseに対してregister_interceptorを呼ぶ形をとった。
メールを送信する
以上の設定で、例えば
class UserMailer < ActionMailer::Base def hello mail to: 'hoge@example.com', from: 'fuga@example.com' end end
というメーラークラスを定義して
UserMailer.hello.deliver
を実行すると、PopBeforeSmtpInterceptor#delivering_emailがメールの送信前に毎回呼び出されるようになる。