✉️
ActionMailer の送信直前・直後に処理するコールバック
ActionMailer で送信直前に処理したいケースがあった。具体的には Amazon SES の秒間送信数制限にかからないように、必要なら sleep
したい。
これをインスタンスメソッド内や before_action
で行うと、deliver_later
を呼ぶときにも処理が走ってしまい嬉しくないので、送信直前に処理を追加できるようにした。
実装の方針
deliver_later
でも送信時には ActionMailer::MessageDelivery#deliver_now
が呼ばれるので、下記のようにする。
-
ActionMailer::Base.(before|after|around)_deliver
でコールバックを定義できるようにする -
ActionMailer::MessageDelivery#(deliver_now|deliver_now!)
で 1 で定義したコールバックを呼ぶ
実装
# config/initializers/action_mailer_deliver_callbacks.rb
# モンキーパッチなので動作確認したバージョンかチェック
raise "#{__FILE__} monkey patches actionmailer. Please check compatibility" unless ActionMailer.version.to_s == "6.1.3.2"
ActionMailer::Base.class_eval do
define_callbacks :deliver
class << self
# AbstractController::Callbacks の実装を参考に before_deliver など定義
[:before, :after, :around].each do |callback|
define_method "#{callback}_deliver" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:deliver, callback, name, options)
end
end
end
end
end
# rubocop:disable Style/ClassAndModuleChildren
module ActionMailer::MessageDelivery::Callbacks
def deliver_now!
processed_mailer.run_callbacks(:deliver) do
super
end
end
def deliver_now
processed_mailer.run_callbacks(:deliver) do
super
end
end
end
# rubocop:enable Style/ClassAndModuleChildren
ActionMailer::MessageDelivery.prepend(ActionMailer::MessageDelivery::Callbacks)
使い方
下記のように書ける。
※実際は複数プロセスで処理しても一定の頻度を超えないようにしました
class ApplicationMailer < ActionMailer::Base
before_deliver do
sleep 0.1
end
end
Discussion