diff --git a/app/finders/email_channel_finder.rb b/app/finders/email_channel_finder.rb index 41cd8e910..1b6d6f844 100644 --- a/app/finders/email_channel_finder.rb +++ b/app/finders/email_channel_finder.rb @@ -6,19 +6,54 @@ class EmailChannelFinder end def perform - channel = nil - - recipient_mails.each do |email| - normalized_email = normalize_email_with_plus_addressing(email) - channel = Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', normalized_email, normalized_email) - - break if channel.present? - end - channel + channel_from_primary_recipients || channel_from_bcc_recipients end - def recipient_mails - recipient_addresses = @email_object.to.to_a + @email_object.cc.to_a + @email_object.bcc.to_a + [@email_object['X-Original-To'].try(:value)] - recipient_addresses.flatten.compact + private + + def channel_from_primary_recipients + primary_recipient_emails.each do |email| + channel = channel_from_email(email) + return channel if channel.present? + end + + nil + end + + def channel_from_bcc_recipients + bcc_recipient_emails.each do |email| + channel = channel_from_email(email) + + # Skip if BCC processing is disabled for this account + next if channel && !allow_bcc_processing?(channel.account_id) + + return channel if channel.present? + end + + nil + end + + def primary_recipient_emails + (@email_object.to.to_a + @email_object.cc.to_a + [@email_object['X-Original-To'].try(:value)]).flatten.compact + end + + def bcc_recipient_emails + @email_object.bcc.to_a.flatten.compact + end + + def channel_from_email(email) + normalized_email = normalize_email_with_plus_addressing(email) + Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', normalized_email, normalized_email) + end + + def bcc_processing_skipped_accounts + config_value = GlobalConfigService.load('SKIP_INCOMING_BCC_PROCESSING', '') + return [] if config_value.blank? + + config_value.split(',').map(&:to_i) + end + + def allow_bcc_processing?(account_id) + bcc_processing_skipped_accounts.exclude?(account_id) end end diff --git a/config/installation_config.yml b/config/installation_config.yml index db907b7ad..9eb6af14f 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -236,6 +236,10 @@ display_title: 'Blocked Email Domains' description: 'Add a domain per line to block them from signing up, accepts Regex' type: code +- name: SKIP_INCOMING_BCC_PROCESSING + value: + display_title: 'Skip BCC Processing For' + description: 'Comma-separated list of account IDs that should be skipped from incoming BCC processing' - name: INACTIVE_WHATSAPP_NUMBERS value: '' display_title: 'Inactive WhatsApp Numbers' diff --git a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb index 5e70d2d79..934462b93 100644 --- a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb +++ b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb @@ -33,7 +33,7 @@ module Enterprise::SuperAdmin::AppConfigsController def internal_config_options %w[CHATWOOT_INBOX_TOKEN CHATWOOT_INBOX_HMAC_KEY ANALYTICS_TOKEN CLEARBIT_API_KEY DASHBOARD_SCRIPTS INACTIVE_WHATSAPP_NUMBERS BLOCKED_EMAIL_DOMAINS - CAPTAIN_CLOUD_PLAN_LIMITS ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL CHATWOOT_INSTANCE_ADMIN_EMAIL + SKIP_INCOMING_BCC_PROCESSING CAPTAIN_CLOUD_PLAN_LIMITS ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL CHATWOOT_INSTANCE_ADMIN_EMAIL OG_IMAGE_CDN_URL OG_IMAGE_CLIENT_REF CLOUDFLARE_API_KEY CLOUDFLARE_ZONE_ID] end diff --git a/spec/finders/email_channel_finder_spec.rb b/spec/finders/email_channel_finder_spec.rb index fe57dec0b..d56d97008 100644 --- a/spec/finders/email_channel_finder_spec.rb +++ b/spec/finders/email_channel_finder_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' describe EmailChannelFinder do include ActionMailbox::TestHelper + let!(:channel_email) { create(:channel_email) } describe '#perform' do @@ -48,6 +49,75 @@ describe EmailChannelFinder do expect(channel).to eq(channel_email) end + it 'skip bcc email when account is configured to skip BCC processing' do + channel_email.update(email: 'test@example.com') + reply_mail.mail['to'] = nil + reply_mail.mail['bcc'] = 'test@example.com' + + allow(GlobalConfigService).to receive(:load) + .with('SKIP_INCOMING_BCC_PROCESSING', '') + .and_return(channel_email.account_id.to_s) + + channel = described_class.new(reply_mail.mail).perform + expect(channel).to be_nil + end + + it 'skip bcc email when account is in multiple account ids config' do + channel_email.update(email: 'test@example.com') + reply_mail.mail['to'] = nil + reply_mail.mail['bcc'] = 'test@example.com' + + # Include this account along with other account IDs + other_account_ids = [123, 456, channel_email.account_id, 789] + allow(GlobalConfigService).to receive(:load) + .with('SKIP_INCOMING_BCC_PROCESSING', '') + .and_return(other_account_ids.join(',')) + + channel = described_class.new(reply_mail.mail).perform + expect(channel).to be_nil + end + + it 'process bcc email when account is not in skip config' do + channel_email.update(email: 'test@example.com') + reply_mail.mail['to'] = nil + reply_mail.mail['bcc'] = 'test@example.com' + + # Configure other account IDs but not this one + other_account_ids = [123, 456, 789] + allow(GlobalConfigService).to receive(:load) + .with('SKIP_INCOMING_BCC_PROCESSING', '') + .and_return(other_account_ids.join(',')) + + channel = described_class.new(reply_mail.mail).perform + expect(channel).to eq(channel_email) + end + + it 'process bcc email when skip config is empty' do + channel_email.update(email: 'test@example.com') + reply_mail.mail['to'] = nil + reply_mail.mail['bcc'] = 'test@example.com' + + allow(GlobalConfigService).to receive(:load) + .with('SKIP_INCOMING_BCC_PROCESSING', '') + .and_return('') + + channel = described_class.new(reply_mail.mail).perform + expect(channel).to eq(channel_email) + end + + it 'process bcc email when skip config is nil' do + channel_email.update(email: 'test@example.com') + reply_mail.mail['to'] = nil + reply_mail.mail['bcc'] = 'test@example.com' + + allow(GlobalConfigService).to receive(:load) + .with('SKIP_INCOMING_BCC_PROCESSING', '') + .and_return(nil) + + channel = described_class.new(reply_mail.mail).perform + expect(channel).to eq(channel_email) + end + it 'return channel with X-Original-To email' do channel_email.update(email: 'test@example.com') reply_mail.mail['to'] = nil @@ -55,6 +125,19 @@ describe EmailChannelFinder do channel = described_class.new(reply_mail.mail).perform expect(channel).to eq(channel_email) end + + it 'process X-Original-To email even when account is configured to skip BCC processing' do + channel_email.update(email: 'test@example.com') + reply_mail.mail['to'] = nil + reply_mail.mail['X-Original-To'] = 'test@example.com' + + allow(GlobalConfigService).to receive(:load) + .with('SKIP_INCOMING_BCC_PROCESSING', '') + .and_return(channel_email.account_id.to_s) + + channel = described_class.new(reply_mail.mail).perform + expect(channel).to eq(channel_email) + end end end end diff --git a/spec/mailboxes/application_mailbox_spec.rb b/spec/mailboxes/application_mailbox_spec.rb index f4f28d811..33bbf9de8 100644 --- a/spec/mailboxes/application_mailbox_spec.rb +++ b/spec/mailboxes/application_mailbox_spec.rb @@ -66,6 +66,20 @@ RSpec.describe ApplicationMailbox do expect(dbl).to receive(:perform_processing).and_return(true) described_class.route reply_cc_mail end + + it 'skips routing when BCC processing is disabled for account' do + allow(GlobalConfigService).to receive(:load).with('SKIP_INCOMING_BCC_PROCESSING', '').and_return(channel_email.account_id.to_s) + + # Create a BCC-only email scenario + bcc_mail = create_inbound_email_from_fixture('support.eml') + bcc_mail.mail['to'] = nil + bcc_mail.mail['bcc'] = 'care@example.com' + + channel_email.update(email: 'care@example.com') + + expect(DefaultMailbox).to receive(:new).and_return(double.tap { |d| expect(d).to receive(:perform_processing) }) + described_class.route bcc_mail + end end describe 'Invalid Mail To Address' do diff --git a/spec/mailboxes/support_mailbox_spec.rb b/spec/mailboxes/support_mailbox_spec.rb index f6964285d..0dbfbbe3b 100644 --- a/spec/mailboxes/support_mailbox_spec.rb +++ b/spec/mailboxes/support_mailbox_spec.rb @@ -334,5 +334,19 @@ RSpec.describe SupportMailbox do expect(conversation.messages.last.content_attributes['email']['subject']).to eq('attachment with html') end end + + describe 'when BCC processing is disabled for account' do + before do + allow(GlobalConfigService).to receive(:load).with('SKIP_INCOMING_BCC_PROCESSING', '').and_return(account.id.to_s) + end + + it 'does not process BCC-only emails' do + bcc_mail = create_inbound_email_from_fixture('support.eml') + bcc_mail.mail['to'] = nil + bcc_mail.mail['bcc'] = 'care@example.com' + + expect { described_class.receive bcc_mail }.to raise_error('Email channel/inbox not found') + end + end end end