deliver_nowと、deliver_now!の違い
アプリ作成時に、Railsでメール送信を行う際に使用した以下2つ
- deliver_now
- deliver_now!
一見よく似ていますが、失敗時の挙動が大きく異なるようでした。
この記事では、ブログアプリの通知機能を例に、それぞれの違いを備忘録としてまとめます。
違いについて
| メソッド | 挙動 | 失敗時 |
|---|---|---|
deliver_now |
メールをすぐ送信(同期実行) | 原則として例外を投げず、Mail::Message を返す(静かに失敗する場合あり) |
deliver_now! |
メールをすぐ送信(同期実行) | 送信エラー発生時に例外を投げる(rescue可能) |
deliver_now→ 失敗してもアプリは落ちない。ログでしか気づけない。
deliver_now!→ 失敗時に例外を発生させ、トランザクションと組み合わせて安全に扱える。
ブログアプリで「記事投稿通知メール」を送る
通知ジョブ(非同期処理)
# app/jobs/post_notification_job.rb
class PostNotificationJob < ApplicationJob
queue_as :default
def perform(post_id)
post = Post.find(post_id)
Notification.transaction do
# 投稿者へ「記事が公開されました」メールを送信
NotificationMailer.post_published(post).deliver_now!
# メール送信が成功したら通知を記録
Notification.create!(post: post, sent_at: Time.current)
end
end
end
上記についてのポイント
deliver_now!は送信失敗時に例外を投げるからNotification.create!は実行されない。
また、Notification.transaction によって、メール送信と通知レコード作成が1つの処理単位として扱われる。
もしも、エラーなどで送信に失敗した場合でも、トランザクションがロールバックされ、
Notificationのsent_atが誤って登録されることを防げる。
deliver_nowの場合
NotificationMailer.post_published(post).deliver_now
Notification.create!(post: post, sent_at: Time.current)
もしメール送信が失敗しても例外が発生せず、そのまま次の行が実行される。
- ユーザーにはメールが届かないけど、DBには「送信済み」と記録される
そうすると、実際の状態とデータベースの整合性が崩れる可能性がある
例外を投げるメリット
deliver_now!を使うことで、失敗を確実に検知してロールバックできる。
Notification.transactionの中で使用すれば、送信に失敗した際に例外が発生して、後続の Notification.create!は実行されない。
そうすると、送信失敗と同時にトランザクション全体が巻き戻され、「送れていないのに送信済みと記録される」といった不整合を防げる。
リマインド通知の例
複数の記事に対して「明日リマインドメールを送る」ジョブも同様に扱える。
# app/jobs/post_reminder_job.rb
class PostReminderJob < ApplicationJob
queue_as :default
def perform
target_date = Time.zone.tomorrow
Post.where(published: true, reminder_sent: false, reminder_date: target_date)
.find_each do |post|
Notification.transaction do
NotificationMailer.reminder(post).deliver_now!
post.update!(reminder_sent: true)
end
end
end
end
deliver_now!によって、送信失敗時には例外が発生して、post.update!が実行されない。
→「リマインド済みフラグ」だけが誤って立つといった事態を防げる。
まとめ
| 項目 | deliver_now |
deliver_now! |
|---|---|---|
| メール送信 | 即時(同期) | 即時(同期) |
| 非同期処理 |
deliver_later を使用 |
deliver_later! を使用 |
| 失敗時の挙動 | 原則として例外を投げない(Mail gem依存) | 例外を投げる(Mail gem依存) |
| 戻り値 |
Mail::Message オブジェクト |
Mail::Message オブジェクト |
| トランザクション内での利用 | 失敗を検知できない | 例外でロールバック可能 |
結論
- 安全にトランザクションを管理したい場合は
deliver_now!を使う - 失敗を無視して処理を続行したい場合のみ
deliver_nowを使う - 特に、通知やリマインドのように「送信状態をDBで管理する」機能では、
deliver_now!を使うことで、データの整合性が保たれる
deliver_now!は「失敗を確実に検知したいとき」
deliver_nowは「失敗しても処理を続けたいとき」
という使い分けをする
初学者のため、間違えていたらすいません。