ActionMailer でメールを配信する場合、deliver_now,deliver_now!,deliver_later,deliver_later!のいずれかのメソッドを使用してメールを送信することになる。
以下は例
Notifier.welcome(User.first).deliver_now # sends the email
Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
deliver_nowとdeliver_laterにはそれぞれ対応する!付きメソッドが用意されている(deliver_now!, deliver_later!)。運用や実装方法によっては意図しない挙動をしてしまうことがあるので注意が必要になる。(例えば、メールをインターセプトしている場合など)
この記事では、ActionMailer のメール送信メソッドの挙動を整理した上で、deliver_xxx系メソッドの運用方針について考えてみることにする。
指定したメールを即時送信するメソッド。 CronJob や非同期ジョブなどのバックグラウンド処理からメール送信する場合に使用されることが多い。
deliver_nowメソッドは、ActionMailer::MessageDeliveryクラスに実装されている。
ActionMailer::MessageDelivery オブジェクトは、Mail::Message のラッパーです。
と公式ドキュメントにあるように、deliver_now メソッドはMail::Messageのdeliverメソッドを delegate している。
class MessageDelivery < Delegator
...
def deliver_now
processed_mailer.handle_exceptions do
processed_mailer.run_callbacks(:deliver) do
message.deliver # ← Mail::Messageクラスのdeliverメソッドを呼び出し
end
end
end
...
end
Mail::Messageクラスと言うのは、Mail gemのこと。
Rails では拡張した上で使われている。
Active Job 経由で非同期的にメール送信を送信するメソッド。
登録されたジョブでは前述したdeliver_nowでメールが送信される。
deliver_later,deliver_later!メソッドでは内部で、deliver_now と deliver_now!が呼び出される。そのため挙動の違いについては、deliver_now と deliver_now!について見ていくことにする。
Delivers an email without checking perform_deliveries and raise_delivery_errors, so use with caution.
https://api.rubyonrails.org/v8.0/classes/ActionMailer/MessageDelivery.html#method-i-deliver_now-21
perform_deliveriesとraise_delivery_errorsというMail gemのオプションを無視するdeliver!メソッドを delegate している。
無視されるオプションの概要は以下
trueMail::Messageクラスのdeliverメソッドを実行した時に、実際にメール配信を行うかどうかを指定できるtrueconfig/environments/$RAILS_ENV.rb の設定例
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
class MessageDelivery < Delegator
...
def deliver_now!
processed_mailer.handle_exceptions do
processed_mailer.run_callbacks(:deliver) do
message.deliver! # Mail::Messageクラスのdeliver!メソッドを呼び出し
end
end
end
...
end
Mail::Messageクラスのdeliverとdeliver!のコード上の違い
# Delivers an mail object.
#
# Examples:
#
# mail = Mail.read('file.eml')
# mail.deliver
def deliver
inform_interceptors
if delivery_handler
delivery_handler.deliver_mail(self) { do_delivery }
else
do_delivery
end
inform_observers
self
end
# This method bypasses checking perform_deliveries and raise_delivery_errors,
# so use with caution.
#
# It still however fires callbacks to the observers if they are defined.
#
# Returns self
def deliver!
response = delivery_method.deliver!(self)
inform_observers
delivery_method.settings[:return_response] ? response : self
end
...
def do_delivery # 設定値に基づいて送信の有無や例外のハンドリングの有無を決める
begin
if perform_deliveries
delivery_method.deliver!(self)
end
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
raise e if raise_delivery_errors
end
end
...
オプション(perform_deliveries や raise_delivery_errors)を設定しない場合の挙動は以下である。
deliver_now: メール送信する & 失敗時に例外を発生させるdeliver_now!: メール送信する & 失敗時に例外を発生させるつまり、デフォルトの挙動は同じであることがわかる。
perform_deliveriesが無視されることで、意図しないメールアドレス宛にメールを送信してしまう可能性があるの理由から、サービスの運用にあたっては、基本的にdeliver_nowメソッドを使っておくのが安全。逆にdeliver_now!はメール送信のデバッグ用途で使用する(問題がオプションの設定にあるか、メールサーバーにあるかの切り分けなど)に使うくらいにとどめておくとよさそう。
Mail::Messageクラスのdeliver!メソッドを private メソッドにして、deliverメソッドだけを公開メソッドにしていってもよいのではないか?と思ったりした。
Rails コンソールから検証してみる
rails c
raise_delivery_errors = trueにした場合 → 同じ
> ActionMailer::Base.delivery_method = :smtp
=> :smtp
ActionMailer::Base.smtp_settings = {
address: 'invalid.example.local', # 存在しないホスト名
port: 25,
open_timeout: 1,
read_timeout: 1,
}
> ActionMailer::Base.raise_delivery_errors = true
=> true
> ActionMailer::Base.perform_deliveries = true
=> true
class ConsoleTestMailer < ActionMailer::Base
default from: 'from@example.com'
def hello
mail(to: 'to@example.com', subject: 'test', body: 'hello')
end
> end
=> :hello
# 例外が発生する
> ConsoleTestMailer.hello.deliver_now
ConsoleTestMailer#hello: processed outbound mail in 1.7ms
(generator):14:in '<main>': execution expired (Net::OpenTimeout)
# 例外が発生する
> ConsoleTestMailer.hello.deliver_now!
ConsoleTestMailer#hello: processed outbound mail in 4.6ms
raise_delivery_errors = falseにした場合 → deliver_nowは例外が発生しない
generator(dev):028> ActionMailer::Base.raise_delivery_errors = false
=> false
# Mail::Messageクラスのオブジェクトが返される
> ConsoleTestMailer.hello.deliver_now
=> #<Mail::Message:128136, Multipart: false, Headers: <Date: Sun, 30 Nov 2025 04:49:51 +0000>, <From: from@example.com>, <To: to@example.com>, <Message-ID: <692bccefbfb25_eb89b071036@7516cc252f3e.mail>>, <Subject: test>, <MIME-Version: 1.0>, <Content-Type: text/plain>, <Content-Transfer-Encoding: 7bit>>
# 例外が発生する
> ConsoleTestMailer.hello.deliver_now!
ConsoleTestMailer#hello: processed outbound mail in 1.7ms
(generator):29:in '<main>': execution expired (Net::OpenTimeout)
raise_delivery_errors = trueにした場合の例外発生時の挙動は同じ。