POP before SMTPで認証するメールサーバをAction Mailerで使う

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がメールの送信前に毎回呼び出されるようになる。