fix(email): Allow inbox OAuth replies without global SMTP (#13820)

Email inbox replies now work for Google and Microsoft OAuth inboxes even
when the self-hosted instance does not have global SMTP configured. This
keeps agent replies working for email channels that already have valid
inbox-level delivery settings.

fixes: chatwoot/chatwoot#13118
closes: chatwoot/chatwoot#13118

## Why
Self-hosted email inbox replies were blocked by a global SMTP guard in
the `email_reply` path. For OAuth-backed email inboxes, outbound
delivery is configured at the inbox level, so the mailer returned early
and the reply flow failed before sending.

## What this change does
- Allows the `email_reply` path to proceed when the inbox has SMTP
configured
- Allows the `email_reply` path to proceed when the inbox has Google or
Microsoft OAuth delivery configured
- Renames the touched mailer helper predicates to `?` methods for
clarity

## Validation
- Configure a Google email inbox on a self-hosted instance without
global `SMTP_ADDRESS`
- Reply from Chatwoot to an existing email conversation
- Confirm the reply is sent through the inbox OAuth SMTP configuration
- Run `bundle exec rspec
spec/mailers/conversation_reply_mailer_spec.rb:595`

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sojan Jose
2026-03-17 00:10:42 -07:00
committed by GitHub
parent 349f55b558
commit ad1539c6cf
3 changed files with 30 additions and 7 deletions

View File

@@ -35,9 +35,8 @@ class ConversationReplyMailer < ApplicationMailer
end
def email_reply(message)
return unless smtp_config_set_or_development?
init_conversation_attributes(message.conversation)
return unless smtp_config_set_or_development? || email_smtp_enabled? || (email_imap_enabled? && email_oauth_enabled?)
@message = message
prepare_mail(true)

View File

@@ -79,28 +79,28 @@ module ConversationReplyMailerHelper
@options[:delivery_method_options] = smtp_settings
end
def email_smtp_enabled
def email_smtp_enabled?
@inbox.inbox_type == 'Email' && @channel.smtp_enabled
end
def email_imap_enabled
def email_imap_enabled?
@inbox.inbox_type == 'Email' && @channel.imap_enabled
end
def email_oauth_enabled
def email_oauth_enabled?
@inbox.inbox_type == 'Email' && (@channel.microsoft? || @channel.google?)
end
def email_from
return Email::FromBuilder.new(inbox: @inbox, message: current_message).build if @account.feature_enabled?(:reply_mailer_migration)
email_oauth_enabled || email_smtp_enabled ? channel_email_with_name : from_email_with_name
email_oauth_enabled? || email_smtp_enabled? ? channel_email_with_name : from_email_with_name
end
def email_reply_to
return Email::ReplyToBuilder.new(inbox: @inbox, message: current_message).build if @account.feature_enabled?(:reply_mailer_migration)
email_imap_enabled ? @channel.email : reply_email
email_imap_enabled? ? @channel.email : reply_email
end
# Use channel email domain in case of account email domain is not set for custom message_id and in_reply_to

View File

@@ -591,6 +591,30 @@ RSpec.describe ConversationReplyMailer do
expect(mail.delivery_method.settings[:address]).to eq 'smtp.gmail.com'
expect(mail.delivery_method.settings[:port]).to eq 587
end
it 'uses inbox oauth smtp when global smtp config is unavailable' do
allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(false)
mail = described_class.email_reply(message)
expect(mail).not_to be_nil
expect(mail.delivery_method.settings[:address]).to eq 'smtp.gmail.com'
expect(mail.delivery_method.settings[:port]).to eq 587
end
end
context 'when oauth provider is set but imap is disabled' do
let(:google_channel) do
create(:channel_email, imap_enabled: false, account: account, provider: 'google', provider_config: { access_token: 'access_token' })
end
let(:conversation) { create(:conversation, assignee: agent, inbox: google_channel.inbox, account: account).reload }
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
it 'does not build the mail without global smtp' do
allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(false)
expect(described_class.email_reply(message).deliver_now).to be_nil
end
end
context 'when smtp disabled for email channel', :test do