feat: IMAP Email Channel (#3298)
This change allows the user to configure both IMAP and SMTP for an email inbox. IMAP enables the user to see emails in Chatwoot. And user can use SMTP to reply to an email conversation. Users can use the default settings to send and receive emails for email inboxes if both IMAP and SMTP are disabled. Fixes #2520
This commit is contained in:
@@ -376,6 +376,57 @@ RSpec.describe 'Inboxes API', type: :request do
|
||||
expect(email_channel.reload.email).to eq('emailtest@email.test')
|
||||
end
|
||||
|
||||
it 'updates email inbox with imap when administrator' do
|
||||
email_channel = create(:channel_email, account: account)
|
||||
email_inbox = create(:inbox, channel: email_channel, account: account)
|
||||
|
||||
imap_connection = double
|
||||
allow(Mail).to receive(:connection).and_return(imap_connection)
|
||||
|
||||
patch "/api/v1/accounts/#{account.id}/inboxes/#{email_inbox.id}",
|
||||
headers: admin.create_new_auth_token,
|
||||
params: {
|
||||
channel: {
|
||||
imap_enabled: true,
|
||||
imap_address: 'imap.gmail.com',
|
||||
imap_port: 993,
|
||||
imap_email: 'imaptest@gmail.com'
|
||||
}
|
||||
},
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(email_channel.reload.imap_enabled).to be true
|
||||
expect(email_channel.reload.imap_address).to eq('imap.gmail.com')
|
||||
expect(email_channel.reload.imap_port).to eq(993)
|
||||
end
|
||||
|
||||
it 'updates email inbox with smtp when administrator' do
|
||||
email_channel = create(:channel_email, account: account)
|
||||
email_inbox = create(:inbox, channel: email_channel, account: account)
|
||||
|
||||
smtp_connection = double
|
||||
allow(smtp_connection).to receive(:finish).and_return(true)
|
||||
allow(Net::SMTP).to receive(:start).and_return(smtp_connection)
|
||||
|
||||
patch "/api/v1/accounts/#{account.id}/inboxes/#{email_inbox.id}",
|
||||
headers: admin.create_new_auth_token,
|
||||
params: {
|
||||
channel: {
|
||||
smtp_enabled: true,
|
||||
smtp_address: 'smtp.gmail.com',
|
||||
smtp_port: 587,
|
||||
smtp_email: 'smtptest@gmail.com'
|
||||
}
|
||||
},
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(email_channel.reload.smtp_enabled).to be true
|
||||
expect(email_channel.reload.smtp_address).to eq('smtp.gmail.com')
|
||||
expect(email_channel.reload.smtp_port).to eq(587)
|
||||
end
|
||||
|
||||
it 'updates avatar when administrator' do
|
||||
# no avatar before upload
|
||||
expect(inbox.avatar.attached?).to eq(false)
|
||||
|
||||
27
spec/jobs/inboxes/fetch_imap_email_inboxes_job_spec.rb
Normal file
27
spec/jobs/inboxes/fetch_imap_email_inboxes_job_spec.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::FetchImapEmailInboxesJob, type: :job do
|
||||
let(:account) { create(:account) }
|
||||
let(:imap_email_channel) do
|
||||
create(:channel_email, imap_enabled: true, imap_address: 'imap.gmail.com', imap_port: 993, imap_email: 'imap@gmail.com',
|
||||
imap_password: 'password', account: account)
|
||||
end
|
||||
let(:email_inbox) { create(:inbox, channel: imap_email_channel, account: account) }
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||
.on_queue('low')
|
||||
end
|
||||
|
||||
context 'when called' do
|
||||
it 'fetch all the email channels' do
|
||||
imap_email_inboxes = double
|
||||
allow(imap_email_inboxes).to receive(:all).and_return([email_inbox])
|
||||
allow(Inbox).to receive(:where).and_return(imap_email_inboxes)
|
||||
|
||||
expect(Inboxes::FetchImapEmailsJob).to receive(:perform_later).with(imap_email_channel).once
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
37
spec/jobs/inboxes/fetch_imap_emails_job_spec.rb
Normal file
37
spec/jobs/inboxes/fetch_imap_emails_job_spec.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::FetchImapEmailsJob, type: :job do
|
||||
let(:account) { create(:account) }
|
||||
let(:imap_email_channel) do
|
||||
create(:channel_email, imap_enabled: true, imap_address: 'imap.gmail.com', imap_port: 993, imap_email: 'imap@gmail.com',
|
||||
imap_password: 'password', imap_inbox_synced_at: Time.now.utc - 10, account: account)
|
||||
end
|
||||
let(:email_inbox) { create(:inbox, channel: imap_email_channel, account: account) }
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||
.on_queue('low')
|
||||
end
|
||||
|
||||
context 'when imap fetch latest 10 emails' do
|
||||
it 'check for the new emails' do
|
||||
mail_date = Time.now.utc
|
||||
mail = Mail.new do
|
||||
to 'test@outlook.com'
|
||||
from 'test@gmail.com'
|
||||
subject :test.to_s
|
||||
body 'hello'
|
||||
date mail_date
|
||||
end
|
||||
|
||||
allow(Mail).to receive(:find).and_return([mail])
|
||||
imap_mailbox = double
|
||||
allow(Imap::ImapMailbox).to receive(:new).and_return(imap_mailbox)
|
||||
expect(imap_mailbox).to receive(:process).with(mail, imap_email_channel).once
|
||||
|
||||
described_class.perform_now(imap_email_channel)
|
||||
|
||||
expect(imap_email_channel.reload.imap_inbox_synced_at).to be > mail_date
|
||||
end
|
||||
end
|
||||
end
|
||||
93
spec/mailboxes/imap/imap_mailbox_spec.rb
Normal file
93
spec/mailboxes/imap/imap_mailbox_spec.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Imap::ImapMailbox, type: :mailbox do
|
||||
include ActionMailbox::TestHelper
|
||||
|
||||
describe 'add mail as a new conversation in the email inbox' do
|
||||
let(:account) { create(:account) }
|
||||
let(:agent) { create(:user, email: 'agent@example.com', account: account) }
|
||||
let(:channel) do
|
||||
create(:channel_email, imap_enabled: true, imap_address: 'imap.gmail.com',
|
||||
imap_port: 993, imap_email: 'imap@gmail.com', imap_password: 'password',
|
||||
account: account)
|
||||
end
|
||||
let(:inbox) { create(:inbox, channel: channel, account: account) }
|
||||
let!(:contact) { create(:contact, email: 'email@gmail.com', phone_number: '+919584546666', account: account, identifier: '123') }
|
||||
let(:conversation) { Conversation.where(inbox_id: channel.inbox).last }
|
||||
let(:class_instance) { described_class.new }
|
||||
|
||||
before do
|
||||
create(:contact_inbox, contact_id: contact.id, inbox_id: channel.inbox.id)
|
||||
end
|
||||
|
||||
context 'when a new email from non existing contact' do
|
||||
let(:inbound_mail) { create_inbound_email_from_mail(from: 'testemail@gmail.com', to: 'imap@gmail.com', subject: 'Hello!') }
|
||||
|
||||
it 'creates the contact and conversation with message' do
|
||||
class_instance.process(inbound_mail.mail, channel)
|
||||
expect(conversation.contact.email).to eq(inbound_mail.mail.from.first)
|
||||
expect(conversation.additional_attributes['source']).to eq('email')
|
||||
expect(conversation.messages.empty?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a new email from existing contact' do
|
||||
let(:inbound_mail) { create_inbound_email_from_mail(from: 'email@gmail.com', to: 'imap@gmail.com', subject: 'Hello!') }
|
||||
|
||||
it 'creates a new conversation with message' do
|
||||
class_instance.process(inbound_mail.mail, channel)
|
||||
expect(conversation.contact.email).to eq(contact.email)
|
||||
expect(conversation.additional_attributes['source']).to eq('email')
|
||||
expect(conversation.messages.empty?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a reply for existing email conversation' do
|
||||
let(:prev_conversation) { create(:conversation, account: account, inbox: channel.inbox, assignee: agent) }
|
||||
let(:reply_mail) do
|
||||
create_inbound_email_from_mail(from: 'email@gmail.com', to: 'imap@gmail.com', subject: 'Hello!', in_reply_to: 'test-in-reply-to')
|
||||
end
|
||||
|
||||
it 'appends new email to the existing conversation' do
|
||||
create(
|
||||
:message,
|
||||
content: 'Incoming Message',
|
||||
message_type: 'incoming',
|
||||
inbox: inbox,
|
||||
account: account,
|
||||
conversation: prev_conversation
|
||||
)
|
||||
create(
|
||||
:message,
|
||||
content: 'Outgoing Message',
|
||||
message_type: 'outgoing',
|
||||
inbox: inbox,
|
||||
source_id: 'test-in-reply-to',
|
||||
account: account,
|
||||
conversation: prev_conversation
|
||||
)
|
||||
|
||||
expect(prev_conversation.messages.size).to eq(2)
|
||||
|
||||
class_instance.process(reply_mail.mail, channel)
|
||||
|
||||
expect(prev_conversation.messages.size).to eq(3)
|
||||
expect(prev_conversation.messages.last.content_attributes['email']['from']).to eq(reply_mail.mail.from)
|
||||
expect(prev_conversation.messages.last.content_attributes['email']['to']).to eq(reply_mail.mail.to)
|
||||
expect(prev_conversation.messages.last.content_attributes['email']['subject']).to eq(reply_mail.mail.subject)
|
||||
expect(prev_conversation.messages.last.content_attributes['email']['in_reply_to']).to eq(reply_mail.mail.in_reply_to)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a reply for non existing email conversation' do
|
||||
let(:reply_mail) do
|
||||
create_inbound_email_from_mail(from: 'email@gmail.com', to: 'imap@gmail.com', subject: 'Hello!', in_reply_to: 'test-in-reply-to')
|
||||
end
|
||||
|
||||
it 'creates new email conversation with incoming in-reply-to' do
|
||||
class_instance.process(reply_mail.mail, channel)
|
||||
expect(conversation.additional_attributes['in_reply_to']).to eq(reply_mail.mail.in_reply_to)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -154,6 +154,32 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when smtp enabled for email channel' do
|
||||
let(:smtp_email_channel) do
|
||||
create(:channel_email, smtp_enabled: true, smtp_address: 'smtp.gmail.com', smtp_port: 587, smtp_email: 'smtp@gmail.com',
|
||||
smtp_password: 'password', smtp_domain: 'smtp.gmail.com', account: account)
|
||||
end
|
||||
let(:conversation) { create(:conversation, assignee: agent, inbox: smtp_email_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) }
|
||||
|
||||
Reference in New Issue
Block a user