mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	feat: add SKIP_INCOMING_BCC_PROCESSING as internal config (#12484)
				
					
				
			Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
		| @@ -6,19 +6,54 @@ class EmailChannelFinder | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def perform |   def perform | ||||||
|     channel = nil |     channel_from_primary_recipients || channel_from_bcc_recipients | ||||||
|  |   end | ||||||
|  |  | ||||||
|     recipient_mails.each do |email| |   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) |     normalized_email = normalize_email_with_plus_addressing(email) | ||||||
|       channel = Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', normalized_email, normalized_email) |     Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', normalized_email, normalized_email) | ||||||
|  |  | ||||||
|       break if channel.present? |  | ||||||
|     end |  | ||||||
|     channel |  | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def recipient_mails |   def bcc_processing_skipped_accounts | ||||||
|     recipient_addresses = @email_object.to.to_a + @email_object.cc.to_a + @email_object.bcc.to_a + [@email_object['X-Original-To'].try(:value)] |     config_value = GlobalConfigService.load('SKIP_INCOMING_BCC_PROCESSING', '') | ||||||
|     recipient_addresses.flatten.compact |     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 | ||||||
| end | end | ||||||
|   | |||||||
| @@ -236,6 +236,10 @@ | |||||||
|   display_title: 'Blocked Email Domains' |   display_title: 'Blocked Email Domains' | ||||||
|   description: 'Add a domain per line to block them from signing up, accepts Regex' |   description: 'Add a domain per line to block them from signing up, accepts Regex' | ||||||
|   type: code |   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 | - name: INACTIVE_WHATSAPP_NUMBERS | ||||||
|   value: '' |   value: '' | ||||||
|   display_title: 'Inactive WhatsApp Numbers' |   display_title: 'Inactive WhatsApp Numbers' | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ module Enterprise::SuperAdmin::AppConfigsController | |||||||
|  |  | ||||||
|   def internal_config_options |   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 |     %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] |        OG_IMAGE_CDN_URL OG_IMAGE_CLIENT_REF CLOUDFLARE_API_KEY CLOUDFLARE_ZONE_ID] | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ require 'rails_helper' | |||||||
|  |  | ||||||
| describe EmailChannelFinder do | describe EmailChannelFinder do | ||||||
|   include ActionMailbox::TestHelper |   include ActionMailbox::TestHelper | ||||||
|  |  | ||||||
|   let!(:channel_email) { create(:channel_email) } |   let!(:channel_email) { create(:channel_email) } | ||||||
|  |  | ||||||
|   describe '#perform' do |   describe '#perform' do | ||||||
| @@ -48,6 +49,75 @@ describe EmailChannelFinder do | |||||||
|         expect(channel).to eq(channel_email) |         expect(channel).to eq(channel_email) | ||||||
|       end |       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 |       it 'return channel with X-Original-To email' do | ||||||
|         channel_email.update(email: 'test@example.com') |         channel_email.update(email: 'test@example.com') | ||||||
|         reply_mail.mail['to'] = nil |         reply_mail.mail['to'] = nil | ||||||
| @@ -55,6 +125,19 @@ describe EmailChannelFinder do | |||||||
|         channel = described_class.new(reply_mail.mail).perform |         channel = described_class.new(reply_mail.mail).perform | ||||||
|         expect(channel).to eq(channel_email) |         expect(channel).to eq(channel_email) | ||||||
|       end |       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 |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -66,6 +66,20 @@ RSpec.describe ApplicationMailbox do | |||||||
|         expect(dbl).to receive(:perform_processing).and_return(true) |         expect(dbl).to receive(:perform_processing).and_return(true) | ||||||
|         described_class.route reply_cc_mail |         described_class.route reply_cc_mail | ||||||
|       end |       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 |     end | ||||||
|  |  | ||||||
|     describe 'Invalid Mail To Address' do |     describe 'Invalid Mail To Address' do | ||||||
|   | |||||||
| @@ -334,5 +334,19 @@ RSpec.describe SupportMailbox do | |||||||
|         expect(conversation.messages.last.content_attributes['email']['subject']).to eq('attachment with html') |         expect(conversation.messages.last.content_attributes['email']['subject']).to eq('attachment with html') | ||||||
|       end |       end | ||||||
|     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 | ||||||
| end | end | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shivam Mishra
					Shivam Mishra