No an answer, but some advice from another forum.
Sending emails is a process that is filled with potential failures. It is always a good idea to do it in a background job that can be re-tried in case of intermittent errors like networks etc. and also skipped due to faulty addresses.
Here is a sketch of what may work:
# Reminder process rake task
namespace :bookings do
desc "Deliver reminders to upcoming bookings"
task remind_upcoming: :environment do
EnqueueUpcomingBookingReminders.call(UpcomingBookingRemindersQuery.call)
end
end
class EnqueueUpcomingBookingReminders
def self.call(bookings_scope)
booking_communication_attrs =
bookings_scope
.pluck(:id)
.map { |id| {booking_id: id, type: "reminder"} }
communications_result =
BookingCommunication.insert_all(booking_communication_attrs, unique_by: %i[booking_id type])
# Email a reminder
communications_result.rows.flatten.each do |communication_id|
DeliverBookingCommunicationJob.perform_later(communication_id)
end
end
end
class UpcomingBookingRemindersQuery
def self.call(scope: Booking)
Booking
.upcoming_this_day
.left_outer_joins(:communications)
.merge(BookingCommunication.reminder)
.where(communications: {id: nil})
end
end
class Booking
has_many :communications, class_name: "BookingCommunication"
def self.upcoming_this_day
where(starts_at:, (Time.current..24.hours.from_now))
end
end
class BookingCommunication
belongs_to :booking
enum step: {confirmation: "confirmation", reminder: "reminder"} # combination of this and the booking id should be unique
enum status: {pending: "pending", delivered: "delivered", canceled: "canceled", failed: "failed"} # should default to pending at database layer
end
class DeliverBookingCommunicationJob < ApplicationJob
def perform(communication_id)
communication = BookingCommunication.find_by(communication_id)
# Guard against state that invalidates us running this job
return unless communication
return unless communication.pending?
return communication.canceled! if communication.booking.canceled? # This should probably live in the cancel booking process
booking = communication.booking
mailer = CustomerMailer.with(customer: booking.customer, booking: booking)
case communication.step
when "reminder"
mailer.reminder_24h.deliver_now
else
# log unknown communication step, send to error tracking but dont raise since we do not want job to run again
end
communication.delivered!
rescue SomeEmailRelatedError => err
communication.failed!
# deliver err to error tracking service
end
end
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…