From beeb023e19b67f97fb11957e8fb7e4448ba6ef05 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Thu, 26 Feb 2026 13:09:49 +0530 Subject: [PATCH] refactor: move the imap backoff keys to installation configs --- app/models/channel/email.rb | 23 ++++++++++++++++------- app/services/imap/authentication_error.rb | 1 + config/installation_config.yml | 13 +++++++++++++ lib/exception_list.rb | 2 -- 4 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 app/services/imap/authentication_error.rb diff --git a/app/models/channel/email.rb b/app/models/channel/email.rb index f34e402d2..416b3915c 100644 --- a/app/models/channel/email.rb +++ b/app/models/channel/email.rb @@ -39,8 +39,6 @@ class Channel::Email < ApplicationRecord include Reauthorizable AUTHORIZATION_ERROR_THRESHOLD = 10 - IMAP_BACKOFF_MAX_INTERVAL_MINUTES = 5 - IMAP_BACKOFF_MAX_INTERVAL_COUNT = 5 # TODO: Remove guard once encryption keys become mandatory (target 3-4 releases out). if Chatwoot.encryption_configured? @@ -85,17 +83,14 @@ class Channel::Email < ApplicationRecord def apply_imap_backoff! new_count = imap_retry_count + 1 - max_retries = (IMAP_BACKOFF_MAX_INTERVAL_MINUTES - 1) + IMAP_BACKOFF_MAX_INTERVAL_COUNT + max_retries = imap_backoff_max_retries if new_count > max_retries Rails.logger.warn "Error for email channel - #{inbox.id} exhausted backoff (#{new_count} failures), prompting reauthorization" clear_imap_backoff! prompt_reauthorization! else - wait_minutes = [new_count, IMAP_BACKOFF_MAX_INTERVAL_MINUTES].min - ::Redis::Alfred.set(imap_retry_count_key, new_count.to_s) - ::Redis::Alfred.set(imap_retry_after_key, wait_minutes.minutes.from_now.to_f.to_s) - Rails.logger.warn "Error for email channel - #{inbox.id} backoff retry #{new_count}/#{max_retries}, next attempt in #{wait_minutes}m" + schedule_imap_retry(new_count, max_retries) end end @@ -110,6 +105,20 @@ class Channel::Email < ApplicationRecord self.forward_to_email ||= "#{SecureRandom.hex}@#{account.inbound_email_domain}" end + def imap_backoff_max_retries + max_interval = GlobalConfigService.load('IMAP_BACKOFF_MAX_INTERVAL_MINUTES', 5).to_i + max_count = GlobalConfigService.load('IMAP_BACKOFF_MAX_INTERVAL_COUNT', 10).to_i + (max_interval - 1) + max_count + end + + def schedule_imap_retry(new_count, max_retries) + max_interval = GlobalConfigService.load('IMAP_BACKOFF_MAX_INTERVAL_MINUTES', 5).to_i + wait_minutes = [new_count, max_interval].min + ::Redis::Alfred.set(imap_retry_count_key, new_count.to_s) + ::Redis::Alfred.set(imap_retry_after_key, wait_minutes.minutes.from_now.to_f.to_s) + Rails.logger.warn "Error for email channel - #{inbox.id} backoff retry #{new_count}/#{max_retries}, next attempt in #{wait_minutes}m" + end + def imap_retry_count_key format(::Redis::Alfred::IMAP_BACKOFF_RETRY_COUNT, channel_id: id) end diff --git a/app/services/imap/authentication_error.rb b/app/services/imap/authentication_error.rb new file mode 100644 index 000000000..5698f5b3b --- /dev/null +++ b/app/services/imap/authentication_error.rb @@ -0,0 +1 @@ +class Imap::AuthenticationError < StandardError; end diff --git a/config/installation_config.yml b/config/installation_config.yml index 34cb736bf..754a64f32 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -96,6 +96,19 @@ locked: false # ------- End of Account Related Config ------- # +# ------- IMAP Backoff Related Config ------- # +- name: IMAP_BACKOFF_MAX_INTERVAL_MINUTES + display_title: 'IMAP Backoff Max Interval (minutes)' + description: 'Maximum wait time in minutes between IMAP retry attempts before the backoff plateaus' + value: 5 + locked: false +- name: IMAP_BACKOFF_MAX_INTERVAL_COUNT + display_title: 'IMAP Backoff Max Retry Count' + description: 'Number of additional retries at the maximum interval before prompting reauthorization' + value: 10 + locked: false +# ------- End of IMAP Backoff Related Config ------- # + # ------- Email Related Config ------- # - name: MAILER_INBOUND_EMAIL_DOMAIN display_title: 'Inbound Email Domain' diff --git a/lib/exception_list.rb b/lib/exception_list.rb index 9992765b1..dd468f07b 100644 --- a/lib/exception_list.rb +++ b/lib/exception_list.rb @@ -1,7 +1,5 @@ require 'net/imap' -class Imap::AuthenticationError < StandardError; end - module ExceptionList REST_CLIENT_EXCEPTIONS = [RestClient::NotFound, RestClient::GatewayTimeout, RestClient::BadRequest, RestClient::MethodNotAllowed, RestClient::Forbidden, RestClient::InternalServerError,