fix: exponential backoff before prompting reauthorization

This commit is contained in:
Tanmay Deep Sharma
2026-02-23 13:44:44 +05:30
parent 138840a23f
commit 7b6b2afdce
3 changed files with 46 additions and 13 deletions
+17 -13
View File
@@ -6,26 +6,30 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
def perform(channel, interval = 1)
return unless should_fetch_email?(channel)
key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id)
with_lock(key, 5.minutes) do
process_email_for_channel(channel, interval)
end
rescue *ExceptionList::IMAP_EXCEPTIONS => e
Rails.logger.error "Authorization error for email channel - #{channel.inbox.id} : #{e.message}"
rescue EOFError, OpenSSL::SSL::SSLError, Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, Net::IMAP::InvalidResponseError,
Net::IMAP::ResponseParseError, Net::IMAP::ResponseReadError, Net::IMAP::ResponseTooLargeError => e
Rails.logger.error "Error for email channel - #{channel.inbox.id} : #{e.message}"
rescue LockAcquisitionError
Rails.logger.error "Lock failed for #{channel.inbox.id}"
fetch_emails_with_backoff(channel, interval)
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: channel.account).capture_exception
end
private
def fetch_emails_with_backoff(channel, interval)
key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id)
with_lock(key, 5.minutes) { process_email_for_channel(channel, interval) }
channel.clear_imap_backoff!
rescue *ExceptionList::IMAP_EXCEPTIONS => e
Rails.logger.error "Authorization error for email channel - #{channel.inbox.id} : #{e.message}"
channel.apply_imap_backoff!
rescue EOFError, OpenSSL::SSL::SSLError, Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, Net::IMAP::InvalidResponseError,
Net::IMAP::ResponseParseError, Net::IMAP::ResponseReadError, Net::IMAP::ResponseTooLargeError => e
Rails.logger.error "Error for email channel - #{channel.inbox.id} : #{e.message}"
channel.apply_imap_backoff!
rescue LockAcquisitionError
Rails.logger.error "Lock failed for #{channel.inbox.id}"
end
def should_fetch_email?(channel)
channel.imap_enabled? && !channel.reauthorization_required?
channel.imap_enabled? && !channel.reauthorization_required? && !channel.in_backoff?
end
def process_email_for_channel(channel, interval)
+23
View File
@@ -39,6 +39,8 @@ class Channel::Email < ApplicationRecord
include Reauthorizable
AUTHORIZATION_ERROR_THRESHOLD = 10
MAX_IMAP_RETRIES = 5
IMAP_BACKOFF_BASE_SECONDS = 60
# TODO: Remove guard once encryption keys become mandatory (target 3-4 releases out).
if Chatwoot.encryption_configured?
@@ -72,6 +74,27 @@ class Channel::Email < ApplicationRecord
imap_enabled && imap_address == 'imap.gmail.com'
end
def in_backoff?
imap_retry_after.present? && imap_retry_after > Time.current
end
def apply_imap_backoff!
new_count = imap_retry_count + 1
if new_count >= MAX_IMAP_RETRIES
update!(imap_retry_count: 0, imap_retry_after: nil)
prompt_reauthorization!
else
backoff_seconds = IMAP_BACKOFF_BASE_SECONDS * (2**new_count)
update!(imap_retry_count: new_count, imap_retry_after: backoff_seconds.seconds.from_now)
end
end
def clear_imap_backoff!
return unless imap_retry_count.positive? || imap_retry_after.present?
update!(imap_retry_count: 0, imap_retry_after: nil)
end
private
def ensure_forward_to_email
@@ -0,0 +1,6 @@
class AddImapBackoffToChannelEmail < ActiveRecord::Migration[7.2]
def change
add_column :channel_email, :imap_retry_count, :integer, default: 0, null: false
add_column :channel_email, :imap_retry_after, :datetime
end
end