Dopo un EOF SMTP passa al destinatario successivo invece di congelare la coda.
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / scan_ruby (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-07 12:20:35 +02:00
co-authored by Cursor
parent 403fe80d37
commit 238bbb0c68
2 changed files with 49 additions and 17 deletions
+47 -17
View File
@@ -1,9 +1,10 @@
class Mailings::OutboundQueue class Mailings::OutboundQueue
LOCK_PATH = Rails.root.join("tmp/outbound_smtp.lock") LOCK_PATH = Rails.root.join("tmp/outbound_smtp.lock")
STAMP_PATH = Rails.root.join("tmp/outbound_last_sent") STAMP_PATH = Rails.root.join("tmp/outbound_last_sent")
ATTEMPT_PATH = Rails.root.join("tmp/outbound_last_attempt")
class << self class << self
attr_accessor :min_interval attr_accessor :min_interval, :fail_interval
def enqueue(recipient) def enqueue(recipient)
recipient.with_lock do recipient.with_lock do
@@ -29,7 +30,9 @@ class Mailings::OutboundQueue
begin begin
CampaignMailer.raise_delivery_errors = true CampaignMailer.raise_delivery_errors = true
outcome = recipient.deliver_queued! outcome = recipient.deliver_queued!
stamp! stamp_attempt!
stamp! if outcome == :sent
Rails.logger.info("[outbound] #{outcome} mailing=#{recipient.mailing_id} to=#{recipient.email}")
outcome == :sent ? :sent : :deferred outcome == :sent ? :sent : :deferred
ensure ensure
CampaignMailer.raise_delivery_errors = false if Rails.env.development? CampaignMailer.raise_delivery_errors = false if Rails.env.development?
@@ -45,7 +48,11 @@ class Mailings::OutboundQueue
end end
def next_queued def next_queued
queued_scope.order(:updated_at, :id).first queued_scope.order(
Arel.sql(
"CASE WHEN mailing_recipients.error_message IS NULL OR mailing_recipients.error_message = '' THEN 0 ELSE 1 END, mailing_recipients.updated_at ASC, mailing_recipients.id ASC"
)
).first
end end
def queued_scope def queued_scope
@@ -53,18 +60,17 @@ class Mailings::OutboundQueue
end end
def seconds_until_next_slot def seconds_until_next_slot
return 0 if min_interval.to_f <= 0 [
remaining(last_sent_at, min_interval),
last = last_sent_at remaining(last_attempt_at, fail_interval)
return 0 if last.nil? ].max
remaining = min_interval - (Time.current - last)
remaining.positive? ? remaining : 0
end end
def reset! def reset!
@last_sent_at = nil @last_sent_at = nil
@last_attempt_at = nil
File.delete(stamp_path) if File.exist?(stamp_path) File.delete(stamp_path) if File.exist?(stamp_path)
File.delete(attempt_path) if File.exist?(attempt_path)
rescue Errno::ENOENT rescue Errno::ENOENT
nil nil
end end
@@ -74,17 +80,36 @@ class Mailings::OutboundQueue
File.write(stamp_path, @last_sent_at.to_f.to_s) File.write(stamp_path, @last_sent_at.to_f.to_s)
end end
def last_sent_at def stamp_attempt!
return @last_sent_at if @last_sent_at @last_attempt_at = Time.current
return unless File.exist?(stamp_path) File.write(attempt_path, @last_attempt_at.to_f.to_s)
end
@last_sent_at = Time.zone.at(Float(File.read(stamp_path))) def last_sent_at
rescue ArgumentError, TypeError, Errno::ENOENT @last_sent_at ||= read_time(stamp_path)
nil end
def last_attempt_at
@last_attempt_at ||= read_time(attempt_path)
end end
private private
def remaining(timestamp, interval)
return 0 if interval.to_f <= 0 || timestamp.nil?
leftover = interval - (Time.current - timestamp)
leftover.positive? ? leftover : 0
end
def read_time(path)
return unless File.exist?(path)
Time.zone.at(Float(File.read(path)))
rescue ArgumentError, TypeError, Errno::ENOENT
nil
end
def lock_path def lock_path
LOCK_PATH LOCK_PATH
end end
@@ -92,7 +117,12 @@ class Mailings::OutboundQueue
def stamp_path def stamp_path
STAMP_PATH STAMP_PATH
end end
def attempt_path
ATTEMPT_PATH
end
end end
self.min_interval = Rails.env.test? ? 0.0 : 180.0 self.min_interval = Rails.env.test? ? 0.0 : 60.0
self.fail_interval = Rails.env.test? ? 0.0 : 30.0
end end
@@ -5,6 +5,7 @@ class Mailings::OutboundQueueTest < ActiveSupport::TestCase
setup do setup do
Mailings::OutboundQueue.min_interval = 0 Mailings::OutboundQueue.min_interval = 0
Mailings::OutboundQueue.fail_interval = 0
Mailings::OutboundQueue.reset! Mailings::OutboundQueue.reset!
Mailings::SmtpGate.min_gap = 0 Mailings::SmtpGate.min_gap = 0
Mailings::SmtpGate.backoff_base = 0 Mailings::SmtpGate.backoff_base = 0
@@ -13,6 +14,7 @@ class Mailings::OutboundQueueTest < ActiveSupport::TestCase
teardown do teardown do
Mailings::OutboundQueue.min_interval = 0 Mailings::OutboundQueue.min_interval = 0
Mailings::OutboundQueue.fail_interval = 0
Mailings::OutboundQueue.reset! Mailings::OutboundQueue.reset!
end end