mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 03:57:52 +00:00
Previously, email replies were handled inside workers. There was no execution logs. This meant if emails silently failed (as reported by a customer), we had no way to trace where the issue happened, the only assumption was “no error = mail sent.” By moving email handling into jobs, we now have proper execution logs for each attempt. This makes it easier to debug delivery issues and would have better visibility when investigating customer reports. Fixes https://linear.app/chatwoot/issue/CW-5538/emails-are-not-sentdelivered-to-the-contact --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
698 lines
32 KiB
Ruby
698 lines
32 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
RSpec.describe ConversationReplyMailer do
|
|
describe 'reply' do
|
|
let!(:account) { create(:account) }
|
|
let!(:agent) { create(:user, email: 'agent1@example.com', account: account) }
|
|
let(:class_instance) { described_class.new }
|
|
let(:email_channel) { create(:channel_email, account: account) }
|
|
|
|
before do
|
|
allow(described_class).to receive(:new).and_return(class_instance)
|
|
allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(true)
|
|
end
|
|
|
|
context 'with summary' do
|
|
let(:conversation) { create(:conversation, account: account, assignee: agent) }
|
|
let(:message) do
|
|
create(:message,
|
|
account: account,
|
|
conversation: conversation,
|
|
content_attributes: {
|
|
cc_emails: 'agent_cc1@example.com',
|
|
bcc_emails: 'agent_bcc1@example.com'
|
|
})
|
|
end
|
|
let(:new_message) do
|
|
create(:message,
|
|
account: account,
|
|
conversation: conversation,
|
|
content_attributes: {
|
|
cc_emails: 'agent_cc2@example.com',
|
|
bcc_emails: 'agent_bcc2@example.com'
|
|
})
|
|
end
|
|
let(:cc_message) do
|
|
create(:message,
|
|
account: account,
|
|
message_type: :outgoing,
|
|
conversation: conversation,
|
|
content_attributes: {
|
|
cc_emails: 'agent_cc1@example.com',
|
|
bcc_emails: 'agent_bcc1@example.com'
|
|
})
|
|
end
|
|
|
|
let(:private_message) { create(:message, account: account, content: 'This is a private message', conversation: conversation) }
|
|
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
|
|
let(:cc_mail) { described_class.reply_with_summary(cc_message.conversation, message.id).deliver_now }
|
|
|
|
it 'renders the default subject' do
|
|
expect(mail.subject).to eq("[##{message.conversation.display_id}] New messages on this conversation")
|
|
end
|
|
|
|
it 'renders the subject in conversation as reply' do
|
|
conversation.additional_attributes = { 'mail_subject': 'Mail Subject' }
|
|
conversation.save!
|
|
new_message.save!
|
|
expect(mail.subject).to eq('Re: Mail Subject')
|
|
end
|
|
|
|
it 'not have private notes' do
|
|
# make the message private
|
|
private_message.private = true
|
|
private_message.save!
|
|
|
|
expect(mail.body.decoded).not_to include(private_message.content)
|
|
expect(mail.body.decoded).to include(message.content)
|
|
end
|
|
|
|
it 'will not send email if conversation is already viewed by contact' do
|
|
create(:message, message_type: 'outgoing', account: account, conversation: conversation)
|
|
conversation.update(contact_last_seen_at: Time.zone.now)
|
|
expect(mail).to be_nil
|
|
end
|
|
|
|
it 'will send email to cc and bcc email addresses' do
|
|
expect(cc_mail.cc.first).to eq(cc_message.content_attributes[:cc_emails])
|
|
expect(cc_mail.bcc.first).to eq(cc_message.content_attributes[:bcc_emails])
|
|
end
|
|
end
|
|
|
|
context 'without assignee' do
|
|
let(:conversation) { create(:conversation, assignee: nil) }
|
|
let(:message) { create(:message, message_type: :outgoing, conversation: conversation) }
|
|
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
|
|
|
|
it 'has correct name' do
|
|
expect(mail[:from].display_names).to eq(["#{message.sender.available_name} from #{message.conversation.inbox.sanitized_name}"])
|
|
end
|
|
end
|
|
|
|
context 'without summary' do
|
|
let(:conversation) { create(:conversation, assignee: agent, account: account).reload }
|
|
let(:message_1) { create(:message, conversation: conversation, account: account, content: 'Outgoing Message 1').reload }
|
|
let(:message_2) { build(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
let(:private_message) do
|
|
create(:message,
|
|
content: 'This is a private message',
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing').reload
|
|
end
|
|
let(:mail) { described_class.reply_without_summary(message_2.conversation, message_2.id).deliver_now }
|
|
|
|
before do
|
|
message_2.save!
|
|
end
|
|
|
|
it 'renders the default subject' do
|
|
expect(mail.subject).to eq("[##{message_2.conversation.display_id}] New messages on this conversation")
|
|
end
|
|
|
|
it 'renders the subject in conversation' do
|
|
conversation.additional_attributes = { 'mail_subject': 'Mail Subject' }
|
|
conversation.save!
|
|
expect(mail.subject).to eq('Mail Subject')
|
|
end
|
|
|
|
it 'not have private notes' do
|
|
# make the message private
|
|
private_message.private = true
|
|
private_message.save!
|
|
expect(mail.body.decoded).not_to include(private_message.content)
|
|
end
|
|
|
|
it 'onlies have the messages sent by the agent' do
|
|
expect(mail.body.decoded).not_to include(message_1.content)
|
|
expect(mail.body.decoded).to include(message_2.content)
|
|
end
|
|
|
|
it 'will not send email if conversation is already viewed by contact' do
|
|
create(:message, message_type: 'outgoing', account: account, conversation: conversation)
|
|
conversation.update(contact_last_seen_at: Time.zone.now)
|
|
expect(mail).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'with references header' do
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: email_channel.inbox, account: account).reload }
|
|
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
let(:mail) { described_class.email_reply(message).deliver_now }
|
|
|
|
context 'when starting a new conversation' do
|
|
let(:first_outgoing_message) do
|
|
create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'First outgoing message')
|
|
end
|
|
let(:mail) { described_class.email_reply(first_outgoing_message).deliver_now }
|
|
|
|
it 'has only the conversation reference' do
|
|
# When starting a conversation, references will have the default conversation ID
|
|
# Extract domain from the actual references header to handle dynamic domain selection
|
|
actual_domain = mail.references.split('@').last
|
|
expected_reference = "account/#{account.id}/conversation/#{conversation.uuid}@#{actual_domain}"
|
|
expect(mail.references).to eq(expected_reference)
|
|
end
|
|
end
|
|
|
|
context 'when replying to a message with no references' do
|
|
let(:incoming_message) do
|
|
create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'incoming',
|
|
source_id: '<incoming-123@example.com>',
|
|
content: 'Incoming message',
|
|
content_attributes: {
|
|
'email' => {
|
|
'message_id' => 'incoming-123@example.com'
|
|
}
|
|
})
|
|
end
|
|
let(:reply_message) do
|
|
create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Reply to incoming')
|
|
end
|
|
let(:mail) { described_class.email_reply(reply_message).deliver_now }
|
|
|
|
before do
|
|
incoming_message
|
|
end
|
|
|
|
it 'includes only the in_reply_to id in references' do
|
|
# References should only have the incoming message ID when no prior references exist
|
|
expect(mail.references).to eq('incoming-123@example.com')
|
|
end
|
|
end
|
|
|
|
context 'when replying to a message that has references' do
|
|
let(:incoming_message_with_refs) do
|
|
create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'incoming',
|
|
source_id: '<incoming-456@example.com>',
|
|
content: 'Incoming with references',
|
|
content_attributes: {
|
|
'email' => {
|
|
'message_id' => 'incoming-456@example.com',
|
|
'references' => ['<ref-1@example.com>', '<ref-2@example.com>']
|
|
}
|
|
})
|
|
end
|
|
let(:reply_message) do
|
|
create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Reply to message with refs')
|
|
end
|
|
let(:mail) { described_class.email_reply(reply_message).deliver_now }
|
|
|
|
before do
|
|
incoming_message_with_refs
|
|
end
|
|
|
|
it 'includes existing references plus the in_reply_to id' do
|
|
# Rails returns references as an array when multiple values are present
|
|
expected_references = ['ref-1@example.com', 'ref-2@example.com', 'incoming-456@example.com']
|
|
expect(mail.references).to eq(expected_references)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with email reply' do
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: email_channel.inbox, account: account).reload }
|
|
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
let(:mail) { described_class.email_reply(message).deliver_now }
|
|
|
|
it 'renders the subject' do
|
|
expect(mail.subject).to eq("[##{message.conversation.display_id}] New messages on this conversation")
|
|
end
|
|
|
|
it 'renders the body' do
|
|
expect(mail.decoded).to include message.content
|
|
end
|
|
|
|
it 'builds messageID properly' do
|
|
expect(mail.message_id).to eq("conversation/#{conversation.uuid}/messages/#{message.id}@#{conversation.account.domain}")
|
|
end
|
|
|
|
context 'when message is a CSAT survey' do
|
|
let(:csat_message) do
|
|
create(:message, conversation: conversation, account: account, message_type: 'template',
|
|
content_type: 'input_csat', content: 'How would you rate our support?', sender: agent)
|
|
end
|
|
|
|
it 'includes CSAT survey URL in outgoing_content' do
|
|
with_modified_env 'FRONTEND_URL' => 'https://app.chatwoot.com' do
|
|
mail = described_class.email_reply(csat_message).deliver_now
|
|
expect(mail.decoded).to include "https://app.chatwoot.com/survey/responses/#{conversation.uuid}"
|
|
end
|
|
end
|
|
|
|
it 'uses outgoing_content for CSAT message body' do
|
|
with_modified_env 'FRONTEND_URL' => 'https://app.chatwoot.com' do
|
|
mail = described_class.email_reply(csat_message).deliver_now
|
|
expect(mail.decoded).to include csat_message.outgoing_content
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with email attachments' do
|
|
it 'includes small attachments as email attachments' do
|
|
message_with_attachment = create(:message, conversation: conversation, account: account, message_type: 'outgoing',
|
|
content: 'Message with small attachment')
|
|
attachment = message_with_attachment.attachments.new(account_id: account.id, file_type: :file)
|
|
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
|
attachment.save!
|
|
|
|
mail = described_class.email_reply(message_with_attachment).deliver_now
|
|
|
|
# Should be attached to the email
|
|
expect(mail.attachments.map(&:filename).map(&:to_s)).to include('avatar.png')
|
|
# Should not be in large_attachments
|
|
expect(mail.body.encoded).not_to include('Attachments:')
|
|
end
|
|
|
|
it 'renders large attachments as links in the email body' do
|
|
message_with_large_attachment = create(:message, conversation: conversation, account: account, message_type: 'outgoing',
|
|
content: 'Message with large attachment')
|
|
attachment = message_with_large_attachment.attachments.new(account_id: account.id, file_type: :file)
|
|
attachment.file.attach(io: Rails.root.join('spec/assets/large_file.pdf').open, filename: 'large_file.pdf', content_type: 'application/pdf')
|
|
attachment.save!
|
|
|
|
mail = described_class.email_reply(message_with_large_attachment).deliver_now
|
|
|
|
# Should NOT be attached to the email
|
|
expect(mail.attachments.map(&:filename).map(&:to_s)).not_to include('large_file.pdf')
|
|
# Should be rendered as a link in the body
|
|
expect(mail.body.encoded).to include('Attachments:')
|
|
expect(mail.body.encoded).to include('large_file.pdf')
|
|
# Should render a link with large_file.pdf as the link text
|
|
expect(mail.body.encoded).to match(%r{<a [^>]*>large_file\.pdf</a>})
|
|
# Small file should not be rendered as a link in the body
|
|
expect(mail.body.encoded).not_to match(%r{<a [^>]*>avatar\.png</a>})
|
|
end
|
|
|
|
it 'handles both small and large attachments correctly' do
|
|
message_with_mixed_attachments = create(:message, conversation: conversation, account: account, message_type: 'outgoing',
|
|
content: 'Message with mixed attachments')
|
|
|
|
# Small attachment
|
|
small_attachment = message_with_mixed_attachments.attachments.new(account_id: account.id, file_type: :file)
|
|
small_attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
|
small_attachment.save!
|
|
|
|
# Large attachment
|
|
large_attachment = message_with_mixed_attachments.attachments.new(account_id: account.id, file_type: :file)
|
|
large_attachment.file.attach(io: Rails.root.join('spec/assets/large_file.pdf').open, filename: 'large_file.pdf',
|
|
content_type: 'application/pdf')
|
|
large_attachment.save!
|
|
|
|
mail = described_class.email_reply(message_with_mixed_attachments).deliver_now
|
|
|
|
# Small file should be attached
|
|
expect(mail.attachments.map(&:filename).map(&:to_s)).to include('avatar.png')
|
|
# Large file should NOT be attached
|
|
expect(mail.attachments.map(&:filename).map(&:to_s)).not_to include('large_file.pdf')
|
|
|
|
# Large file should be rendered as a link in the body
|
|
expect(mail.body.encoded).to include('Attachments:')
|
|
expect(mail.body.encoded).to include('large_file.pdf')
|
|
# Should render a link with large_file.pdf as the link text
|
|
expect(mail.body.encoded).to match(%r{<a [^>]*>large_file\.pdf</a>})
|
|
# Small file should not be rendered as a link in the body
|
|
expect(mail.body.encoded).not_to match(%r{<a [^>]*>avatar\.png</a>})
|
|
end
|
|
end
|
|
|
|
context 'with custom email content' do
|
|
it 'uses custom HTML content when available and creates multipart email' do
|
|
message_with_custom_content = create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Regular message content',
|
|
content_attributes: {
|
|
email: {
|
|
html_content: {
|
|
reply: '<p>Custom <strong>HTML</strong> content for email</p>'
|
|
},
|
|
text_content: {
|
|
reply: 'Custom text content for email'
|
|
}
|
|
}
|
|
})
|
|
|
|
mail = described_class.email_reply(message_with_custom_content).deliver_now
|
|
|
|
# Check HTML part contains custom HTML content
|
|
html_part = mail.html_part || mail
|
|
expect(html_part.body.encoded).to include('<p>Custom <strong>HTML</strong> content for email</p>')
|
|
expect(html_part.body.encoded).not_to include('Regular message content')
|
|
|
|
# Check text part contains custom text content
|
|
text_part = mail.text_part
|
|
if text_part
|
|
expect(text_part.body.encoded).to include('Custom text content for email')
|
|
expect(text_part.body.encoded).not_to include('Regular message content')
|
|
end
|
|
end
|
|
|
|
it 'falls back to markdown rendering when custom HTML content is not available' do
|
|
message_without_custom_content = create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Regular **markdown** content')
|
|
|
|
mail = described_class.email_reply(message_without_custom_content).deliver_now
|
|
|
|
html_part = mail.html_part || mail
|
|
expect(html_part.body.encoded).to include('<strong>markdown</strong>')
|
|
expect(html_part.body.encoded).to include('Regular')
|
|
end
|
|
|
|
it 'handles empty custom HTML content gracefully' do
|
|
message_with_empty_content = create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Regular **markdown** content',
|
|
content_attributes: {
|
|
email: {
|
|
html_content: {
|
|
reply: ''
|
|
}
|
|
}
|
|
})
|
|
|
|
mail = described_class.email_reply(message_with_empty_content).deliver_now
|
|
|
|
html_part = mail.html_part || mail
|
|
expect(html_part.body.encoded).to include('<strong>markdown</strong>')
|
|
expect(html_part.body.encoded).to include('Regular')
|
|
end
|
|
|
|
it 'handles nil custom HTML content gracefully' do
|
|
message_with_nil_content = create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Regular **markdown** content',
|
|
content_attributes: {
|
|
email: {
|
|
html_content: {
|
|
reply: nil
|
|
}
|
|
}
|
|
})
|
|
|
|
mail = described_class.email_reply(message_with_nil_content).deliver_now
|
|
|
|
expect(mail.body.encoded).to include('<strong>markdown</strong>')
|
|
expect(mail.body.encoded).to include('Regular')
|
|
end
|
|
|
|
it 'uses custom text content in text part when only text is provided' do
|
|
message_with_text_only = create(:message,
|
|
conversation: conversation,
|
|
account: account,
|
|
message_type: 'outgoing',
|
|
content: 'Regular message content',
|
|
content_attributes: {
|
|
email: {
|
|
text_content: {
|
|
reply: 'Custom text content only'
|
|
}
|
|
}
|
|
})
|
|
|
|
mail = described_class.email_reply(message_with_text_only).deliver_now
|
|
|
|
text_part = mail.text_part
|
|
if text_part
|
|
expect(text_part.body.encoded).to include('Custom text content only')
|
|
expect(text_part.body.encoded).not_to include('Regular message content')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when smtp enabled for email channel' do
|
|
let(:smtp_channel) do
|
|
create(:channel_email, smtp_enabled: true, smtp_address: 'smtp.gmail.com', smtp_port: 587, smtp_login: 'smtp@gmail.com',
|
|
smtp_password: 'password', smtp_domain: 'smtp.gmail.com', account: account)
|
|
end
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: smtp_channel.inbox, account: account).reload }
|
|
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
|
|
it 'use smtp mail server' do
|
|
mail = described_class.email_reply(message)
|
|
expect(mail.delivery_method.settings.empty?).to be false
|
|
expect(mail.delivery_method.settings[:address]).to eq 'smtp.gmail.com'
|
|
expect(mail.delivery_method.settings[:port]).to eq 587
|
|
end
|
|
|
|
it 'renders sender name in the from address' do
|
|
mail = described_class.email_reply(message)
|
|
expect(mail['from'].value).to eq "#{message.sender.available_name} from #{smtp_channel.inbox.sanitized_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
it 'renders sender name even when assignee is not present' do
|
|
conversation.update(assignee_id: nil)
|
|
mail = described_class.email_reply(message)
|
|
expect(mail['from'].value).to eq "#{message.sender.available_name} from #{smtp_channel.inbox.sanitized_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
it 'renders assignee name in the from address when sender_name not available' do
|
|
message.update(sender_id: nil)
|
|
mail = described_class.email_reply(message)
|
|
expect(mail['from'].value).to eq "#{conversation.assignee.available_name} from #{smtp_channel.inbox.sanitized_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
it 'renders inbox name as sender and assignee or business_name not present' do
|
|
message.update(sender_id: nil)
|
|
conversation.update(assignee_id: nil)
|
|
|
|
mail = described_class.email_reply(message)
|
|
expect(mail['from'].value).to eq "Notifications from #{smtp_channel.inbox.sanitized_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
context 'when friendly name enabled' do
|
|
before do
|
|
conversation.inbox.update(sender_name_type: 0)
|
|
conversation.inbox.update(business_name: 'Business Name')
|
|
end
|
|
|
|
it 'renders sender name as sender and assignee and business_name not present' do
|
|
message.update(sender_id: nil)
|
|
conversation.update(assignee_id: nil)
|
|
conversation.inbox.update(business_name: nil)
|
|
|
|
mail = described_class.email_reply(message)
|
|
|
|
expect(mail['from'].value).to eq "Notifications from #{conversation.inbox.sanitized_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
it 'renders sender name as sender and assignee nil and business_name present' do
|
|
message.update(sender_id: nil)
|
|
conversation.update(assignee_id: nil)
|
|
|
|
mail = described_class.email_reply(message)
|
|
|
|
expect(mail['from'].value).to eq(
|
|
"Notifications from #{conversation.inbox.business_name} <#{smtp_channel.email}>"
|
|
)
|
|
end
|
|
|
|
it 'renders sender name as sender nil and assignee and business_name present' do
|
|
message.update(sender_id: nil)
|
|
conversation.update(assignee_id: agent.id)
|
|
|
|
mail = described_class.email_reply(message)
|
|
expect(mail['from'].value).to eq "#{agent.available_name} from #{conversation.inbox.business_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
it 'renders sender name as sender and assignee and business_name present' do
|
|
agent_2 = create(:user, email: 'agent2@example.com', account: account)
|
|
message.update(sender_id: agent_2.id)
|
|
conversation.update(assignee_id: agent.id)
|
|
|
|
mail = described_class.email_reply(message)
|
|
expect(mail['from'].value).to eq "#{agent_2.available_name} from #{conversation.inbox.business_name} <#{smtp_channel.email}>"
|
|
end
|
|
end
|
|
|
|
context 'when friendly name disabled' do
|
|
before do
|
|
conversation.inbox.update(sender_name_type: 1)
|
|
conversation.inbox.update(business_name: 'Business Name')
|
|
end
|
|
|
|
it 'renders sender name as business_name not present' do
|
|
message.update(sender_id: nil)
|
|
conversation.update(assignee_id: nil)
|
|
conversation.inbox.update(business_name: nil)
|
|
|
|
mail = described_class.email_reply(message)
|
|
|
|
expect(mail['from'].value).to eq "#{conversation.inbox.sanitized_name} <#{smtp_channel.email}>"
|
|
end
|
|
|
|
it 'renders sender name as business_name present' do
|
|
message.update(sender_id: nil)
|
|
conversation.update(assignee_id: nil)
|
|
|
|
mail = described_class.email_reply(message)
|
|
|
|
expect(mail['from'].value).to eq "#{conversation.inbox.business_name} <#{smtp_channel.email}>"
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when smtp enabled for microsoft email channel' do
|
|
let(:ms_smtp_channel) do
|
|
create(:channel_email, imap_login: 'smtp@outlook.com',
|
|
imap_enabled: true, account: account, provider: 'microsoft', provider_config: { access_token: 'access_token' })
|
|
end
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: ms_smtp_channel.inbox, account: account).reload }
|
|
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
|
|
it 'use smtp mail server' do
|
|
mail = described_class.email_reply(message)
|
|
expect(mail.delivery_method.settings.empty?).to be false
|
|
expect(mail.delivery_method.settings[:address]).to eq 'smtp.office365.com'
|
|
expect(mail.delivery_method.settings[:port]).to eq 587
|
|
end
|
|
end
|
|
|
|
context 'when smtp enabled for google email channel' do
|
|
let(:ms_smtp_channel) do
|
|
create(:channel_email, imap_login: 'smtp@gmail.com',
|
|
imap_enabled: true, account: account, provider: 'google', provider_config: { access_token: 'access_token' })
|
|
end
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: ms_smtp_channel.inbox, account: account).reload }
|
|
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
|
|
it 'use smtp mail server' do
|
|
mail = described_class.email_reply(message)
|
|
expect(mail.delivery_method.settings.empty?).to be false
|
|
expect(mail.delivery_method.settings[:address]).to eq 'smtp.gmail.com'
|
|
expect(mail.delivery_method.settings[:port]).to eq 587
|
|
end
|
|
end
|
|
|
|
context 'when smtp disabled for email channel', :test do
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: email_channel.inbox, account: account).reload }
|
|
let(:message) { create(:message, conversation: conversation, account: account, message_type: 'outgoing', content: 'Outgoing Message 2') }
|
|
|
|
it 'use default mail server' do
|
|
mail = described_class.email_reply(message)
|
|
expect(mail.delivery_method.settings).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when custom domain and email is not enabled' do
|
|
let(:inbox) { create(:inbox, account: account) }
|
|
let(:inbox_member) { create(:inbox_member, user: agent, inbox: inbox) }
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: inbox_member.inbox, account: account) }
|
|
let!(:message) { create(:message, conversation: conversation, account: account) }
|
|
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
|
|
let(:domain) { account.inbound_email_domain }
|
|
|
|
it 'renders the receiver email' do
|
|
expect(mail.to).to eq([message&.conversation&.contact&.email])
|
|
end
|
|
|
|
it 'renders the reply to email' do
|
|
expect(mail.reply_to).to eq([message&.conversation&.assignee&.email])
|
|
end
|
|
|
|
it 'sets the correct custom message id' do
|
|
expect(mail.message_id).to eq("conversation/#{conversation.uuid}/messages/#{message.id}@#{domain}")
|
|
end
|
|
|
|
it 'sets the correct in reply to id' do
|
|
expect(mail.in_reply_to).to eq("account/#{conversation.account.id}/conversation/#{conversation.uuid}@#{domain}")
|
|
end
|
|
end
|
|
|
|
context 'when inbox email address is available' do
|
|
let(:inbox) { create(:inbox, account: account, email_address: 'noreply@chatwoot.com') }
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: inbox, account: account) }
|
|
let!(:message) { create(:message, conversation: conversation, account: account) }
|
|
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
|
|
|
|
it 'set reply to email address as inbox email address' do
|
|
expect(mail.from).to eq([inbox.email_address])
|
|
expect(mail.reply_to).to eq([inbox.email_address])
|
|
end
|
|
end
|
|
|
|
context 'when the custom domain emails are enabled' do
|
|
let(:account) { create(:account) }
|
|
let(:conversation) { create(:conversation, assignee: agent, account: account).reload }
|
|
let(:message) { create(:message, message_type: :outgoing, conversation: conversation, account: account, inbox: conversation.inbox) }
|
|
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
|
|
|
|
before do
|
|
account = conversation.account
|
|
account.domain = 'example.com'
|
|
account.support_email = 'support@example.com'
|
|
account.enable_features('inbound_emails')
|
|
account.save!
|
|
end
|
|
|
|
it 'sets reply to email to be based on the domain' do
|
|
reply_to_email = "reply+#{message.conversation.uuid}@#{conversation.account.domain}"
|
|
reply_to = "#{message.sender.available_name} from #{conversation.inbox.sanitized_name} <#{reply_to_email}>"
|
|
expect(mail['REPLY-TO'].value).to eq(reply_to)
|
|
expect(mail.reply_to).to eq([reply_to_email])
|
|
end
|
|
|
|
it 'sets the from email to be the support email' do
|
|
expect(mail['FROM'].value).to eq("#{conversation.messages.last.sender.available_name} from Inbox <#{conversation.account.support_email}>")
|
|
expect(mail.from).to eq([conversation.account.support_email])
|
|
end
|
|
|
|
it 'sets the correct custom message id' do
|
|
expect(mail.message_id).to eq("conversation/#{conversation.uuid}/messages/#{message.id}@#{conversation.account.domain}")
|
|
end
|
|
|
|
it 'sets the correct in reply to id' do
|
|
expect(mail.in_reply_to).to eq("account/#{conversation.account.id}/conversation/#{conversation.uuid}@#{conversation.account.domain}")
|
|
end
|
|
end
|
|
|
|
context 'when inbound email domain is not enabled' do
|
|
let(:new_account) { create(:account, domain: nil) }
|
|
let!(:email_channel) { create(:channel_email, account: new_account) }
|
|
let!(:inbox) { create(:inbox, channel: email_channel, account: new_account) }
|
|
let(:inbox_member) { create(:inbox_member, user: agent, inbox: inbox) }
|
|
let(:conversation) { create(:conversation, assignee: agent, inbox: inbox_member.inbox, account: new_account) }
|
|
let!(:message) { create(:message, conversation: conversation, account: new_account) }
|
|
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
|
|
let(:domain) { inbox.channel.email.split('@').last }
|
|
|
|
it 'sets the correct custom message id' do
|
|
expect(mail.message_id).to eq("conversation/#{conversation.uuid}/messages/#{message.id}@#{domain}")
|
|
end
|
|
|
|
it 'sets the correct in reply to id' do
|
|
expect(mail.in_reply_to).to eq("account/#{conversation.account.id}/conversation/#{conversation.uuid}@#{domain}")
|
|
end
|
|
end
|
|
end
|
|
end
|