class Mailings::OutboundQueue LOCK_PATH = Rails.root.join("tmp/outbound_smtp.lock") STAMP_PATH = Rails.root.join("tmp/outbound_last_sent") class << self attr_accessor :min_interval def enqueue(recipient) recipient.with_lock do return false unless recipient.mailing.sending? return false unless recipient.pending? recipient.update!(status: "queued", error_message: nil) true end end def drain_one! File.open(lock_path, File::RDWR | File::CREAT, 0o644) do |file| return :busy unless file.flock(File::LOCK_EX | File::LOCK_NB) wait = seconds_until_next_slot return :wait if wait.positive? recipient = next_queued return :empty if recipient.nil? return :closed unless Mailings::SendClock.new(recipient.mailing).open? begin CampaignMailer.raise_delivery_errors = true outcome = recipient.deliver_queued! stamp! outcome == :sent ? :sent : :deferred ensure CampaignMailer.raise_delivery_errors = false if Rails.env.development? end end end def exclusive File.open(lock_path, File::RDWR | File::CREAT, 0o644) do |file| file.flock(File::LOCK_EX) yield end end def next_queued queued_scope.order(:updated_at, :id).first end def queued_scope MailingRecipient.queued.joins(:mailing).merge(Mailing.sending) end def seconds_until_next_slot return 0 if min_interval.to_f <= 0 last = last_sent_at return 0 if last.nil? remaining = min_interval - (Time.current - last) remaining.positive? ? remaining : 0 end def reset! @last_sent_at = nil File.delete(stamp_path) if File.exist?(stamp_path) rescue Errno::ENOENT nil end def stamp! @last_sent_at = Time.current File.write(stamp_path, @last_sent_at.to_f.to_s) end def last_sent_at return @last_sent_at if @last_sent_at return unless File.exist?(stamp_path) @last_sent_at = Time.zone.at(Float(File.read(stamp_path))) rescue ArgumentError, TypeError, Errno::ENOENT nil end private def lock_path LOCK_PATH end def stamp_path STAMP_PATH end end self.min_interval = Rails.env.test? ? 0.0 : 180.0 end