ref: master
app/mailers/mail_queuer.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
module MailQueuer extend ActiveSupport::Concern included do alias_method :deliver_now, :deliver prepend InstanceMethods end class Job < ActiveJob::Base queue_as :default ## # def self.schedule message, options = {} delivery_method = ApplicationMailer.delivery_methods.find{ |n, k| k == message.delivery_method.class }.first delivery_method_options = message.delivery_method.settings set(options).perform_later message.encoded, message.bcc, delivery_method.to_s, delivery_method_options.to_yaml end ## # Mail delivery # def perform message, bcc, delivery_method, delivery_method_options m = Mail.read_from_string message m.bcc = bcc ApplicationMailer.wrap_delivery_behavior m, delivery_method.to_sym, YAML.load(delivery_method_options) m.deliver_now end end module InstanceMethods def deliver message = nil last_sched = MailSchedule.last last_sched.with_lock do if last_sched != MailSchedule.last message = deliver_now else message = deliver_schedule last_sched end end message end def deliver_schedule last_sched limit = ENV['MAIL_QUEUER_LIMIT'].to_i - 1 orig_to = Array(to).dup orig_cc = Array(cc).dup dests = { to: Array(self.to), cc: Array(self.cc), bcc: Array(self.bcc), } loop do # mailers don't like emails without :to, # so fill one if not present dests[:to] = [orig_to.first] if dests[:to].blank? dest_count = dests[:to].size + dests[:cc].size + dests[:bcc].size # dests are changed on email splits, so set it to the remaining values self.to = dests[:to] self.cc = dests[:cc] self.bcc = dests[:bcc] ## # The last schedule is outside the quota period # if last_sched.scheduled_to < 1.hour.ago last_sched = MailSchedule.create! dest_count: 0, scheduled_to: Time.now.beginning_of_hour end available_limit = limit - last_sched.dest_count if dest_count <= available_limit last_sched.update dest_count: last_sched.dest_count + dest_count Job.schedule self, wait_until: last_sched.scheduled_to return self # avoid breaking mail which respect the mail limit. Schedule it all to the next hour elsif dest_count <= limit #&& ENV['AVOID_SPLIT'] last_sched = MailSchedule.create! dest_count: dest_count, scheduled_to: last_sched.scheduled_to+1.hour Job.schedule self, wait_until: last_sched.scheduled_to return self else # dest_count > limit if available_limit == 0 available_limit = limit last_sched = MailSchedule.create! dest_count: limit, scheduled_to: last_sched.scheduled_to+1.hour else # reuse current schedule last_sched.update dest_count: limit # limit = last.sched.dest_count + available_limit end # needs to preserve replies when spliting the email self.reply_to = orig_to + orig_cc if self.reply_to.blank? ## # start sending :to, followed by :cc, and so :bcc creating new schedules as needed # [:to, :cc, :bcc].each do |field| next self[field] = [] if available_limit == 0 self[field] = dests[field].shift(available_limit) available_limit -= self.send(field).size end Job.schedule self, wait_until: last_sched.scheduled_to end end end end end |