kakudooo docs

ActionMailer | deliver_now と deliver_now!の違い

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_nowdeliver_laterにはそれぞれ対応する!付きメソッドが用意されている(deliver_now!, deliver_later!)。運用や実装方法によっては意図しない挙動をしてしまうことがあるので注意が必要になる。(例えば、メールをインターセプトしている場合など)

この記事では、ActionMailer のメール送信メソッドの挙動を整理した上で、deliver_xxx系メソッドの運用方針について考えてみることにする。

deliver_now(deliver_now!) と deliver_later(deliver_later!)

deliver_now

指定したメールを即時送信するメソッド。 CronJob や非同期ジョブなどのバックグラウンド処理からメール送信する場合に使用されることが多い。

deliver_nowメソッドは、ActionMailer::MessageDeliveryクラスに実装されている。

ActionMailer::MessageDelivery オブジェクトは、Mail::Message のラッパーです。

と公式ドキュメントにあるように、deliver_now メソッドはMail::Messagedeliverメソッドを 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

https://github.com/rails/rails/blob/90a1eaa1b30ba1f2d524e197460e549c03cf5698/actionmailer/lib/action_mailer/message_delivery.rb#L157

Mail::Messageクラスと言うのは、Mail gemのこと。 Rails では拡張した上で使われている。

deliver_later

Active Job 経由で非同期的にメール送信を送信するメソッド。 登録されたジョブでは前述したdeliver_nowでメールが送信される。

deliver_now と deliver_now!の違い

deliver_later,deliver_later!メソッドでは内部で、deliver_nowdeliver_now!が呼び出される。そのため挙動の違いについては、deliver_nowdeliver_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_deliveriesraise_delivery_errorsというMail gemのオプションを無視するdeliver!メソッドを delegate している。

無視されるオプションの概要は以下

config/environments/$RAILS_ENV.rb の設定例

config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true

https://railsguides.jp/v4.2/action_mailer_basics.html#action-mailer%E3%81%AE%E8%A8%AD%E5%AE%9A%E4%BE%8B

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

https://github.com/rails/rails/blob/90a1eaa1b30ba1f2d524e197460e549c03cf5698/actionmailer/lib/action_mailer/message_delivery.rb#L145

Mail::Messageクラスのdeliverdeliver!のコード上の違い

# 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

...

https://github.com/basecamp/mail/blob/68526ec1db07048ededcc95beafbafa8f54a7049/lib/mail/message.rb#L244

!ありメソッドの用途と運用について

オプション(perform_deliveries や raise_delivery_errors)を設定しない場合の挙動は以下である。

つまり、デフォルトの挙動は同じであることがわかる。

の理由から、サービスの運用にあたっては、基本的にdeliver_nowメソッドを使っておくのが安全。逆にdeliver_now!はメール送信のデバッグ用途で使用する(問題がオプションの設定にあるか、メールサーバーにあるかの切り分けなど)に使うくらいにとどめておくとよさそう。

Mail::Messageクラスのdeliver!メソッドを private メソッドにして、deliverメソッドだけを公開メソッドにしていってもよいのではないか?と思ったりした。

おまけ

raise_delivery_errors = true の場合に deliver_now と deliver_now!は同じ挙動をするか?

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にした場合の例外発生時の挙動は同じ。