Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
233581cd05 |
@@ -1,5 +1,7 @@
|
|||||||
class ReplyMailbox < ApplicationMailbox
|
class ReplyMailbox < ApplicationMailbox
|
||||||
attr_accessor :conversation, :processed_mail
|
include IncomingEmailValidityHelper
|
||||||
|
|
||||||
|
attr_accessor :conversation, :processed_mail, :account
|
||||||
|
|
||||||
before_processing :find_conversation
|
before_processing :find_conversation
|
||||||
|
|
||||||
@@ -7,12 +9,17 @@ class ReplyMailbox < ApplicationMailbox
|
|||||||
# Return early if no conversation was found (e.g., notification emails, suspended accounts)
|
# Return early if no conversation was found (e.g., notification emails, suspended accounts)
|
||||||
return unless @conversation
|
return unless @conversation
|
||||||
|
|
||||||
|
decorate_mail
|
||||||
|
unless incoming_email_from_valid_email?
|
||||||
|
Rails.logger.info "Email #{mail.message_id} rejected - failed incoming email validity checks"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# Wrap everything in a transaction to ensure atomicity
|
# Wrap everything in a transaction to ensure atomicity
|
||||||
# This prevents orphan conversations if message/attachment creation fails
|
# This prevents orphan conversations if message/attachment creation fails
|
||||||
# and ensures idempotency on job retry (conversation won't be duplicated)
|
# and ensures idempotency on job retry (conversation won't be duplicated)
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
persist_conversation_if_needed
|
persist_conversation_if_needed
|
||||||
decorate_mail
|
|
||||||
create_message
|
create_message
|
||||||
add_attachments_to_message
|
add_attachments_to_message
|
||||||
end
|
end
|
||||||
@@ -22,6 +29,7 @@ class ReplyMailbox < ApplicationMailbox
|
|||||||
|
|
||||||
def find_conversation
|
def find_conversation
|
||||||
@conversation = Mailbox::ConversationFinder.new(mail).find
|
@conversation = Mailbox::ConversationFinder.new(mail).find
|
||||||
|
@account = @conversation&.account
|
||||||
# Log when email is rejected
|
# Log when email is rejected
|
||||||
Rails.logger.info "Email #{mail.message_id} rejected - no conversation found" unless @conversation
|
Rails.logger.info "Email #{mail.message_id} rejected - no conversation found" unless @conversation
|
||||||
end
|
end
|
||||||
@@ -36,6 +44,6 @@ class ReplyMailbox < ApplicationMailbox
|
|||||||
end
|
end
|
||||||
|
|
||||||
def decorate_mail
|
def decorate_mail
|
||||||
@processed_mail = MailPresenter.new(mail, @conversation.account)
|
@processed_mail = MailPresenter.new(mail, @account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -174,13 +174,31 @@ class MailPresenter < SimpleDelegator
|
|||||||
end
|
end
|
||||||
|
|
||||||
def notification_email_from_chatwoot?
|
def notification_email_from_chatwoot?
|
||||||
# notification emails are send via mailer sender email address. so it should match
|
sender_address = original_sender.to_s.downcase
|
||||||
configured_sender = Mail::Address.new(ENV.fetch('MAILER_SENDER_EMAIL', 'Chatwoot <accounts@chatwoot.com>')).address
|
return false if sender_address.blank?
|
||||||
original_sender.to_s.casecmp?(configured_sender)
|
|
||||||
|
# Notification emails are sent via mailer sender email address.
|
||||||
|
configured_sender = parse_mail_address(ENV.fetch('MAILER_SENDER_EMAIL', 'Chatwoot <accounts@chatwoot.com>'))&.address&.downcase
|
||||||
|
return true if configured_sender.present? && sender_address.casecmp?(configured_sender)
|
||||||
|
|
||||||
|
reply_thread_email_from_chatwoot?(sender_address)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
REPLY_THREAD_SENDER_PATTERN = /^reply\+[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||||
|
|
||||||
|
def reply_thread_email_from_chatwoot?(sender_address)
|
||||||
|
inbound_domain = @account&.inbound_email_domain.to_s.downcase
|
||||||
|
return false if inbound_domain.blank?
|
||||||
|
|
||||||
|
local_part, domain = sender_address.split('@', 2)
|
||||||
|
return false if local_part.blank? || domain.blank?
|
||||||
|
return false unless domain.casecmp?(inbound_domain)
|
||||||
|
|
||||||
|
local_part.match?(REPLY_THREAD_SENDER_PATTERN)
|
||||||
|
end
|
||||||
|
|
||||||
def parse_mail_address(email)
|
def parse_mail_address(email)
|
||||||
return if email.blank?
|
return if email.blank?
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,17 @@ class Messages::SendEmailNotificationService
|
|||||||
|
|
||||||
def should_send_email_notification?
|
def should_send_email_notification?
|
||||||
return false unless message.email_notifiable_message?
|
return false unless message.email_notifiable_message?
|
||||||
|
return false if bot_sender_message?
|
||||||
return false if message.conversation.contact.email.blank?
|
return false if message.conversation.contact.email.blank?
|
||||||
return false unless message.account.within_email_rate_limit?
|
return false unless message.account.within_email_rate_limit?
|
||||||
|
|
||||||
email_reply_enabled?
|
email_reply_enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bot_sender_message?
|
||||||
|
message.sender_type.in?(%w[AgentBot Captain::Assistant])
|
||||||
|
end
|
||||||
|
|
||||||
def email_reply_enabled?
|
def email_reply_enabled?
|
||||||
inbox = message.inbox
|
inbox = message.inbox
|
||||||
case inbox.channel.class.to_s
|
case inbox.channel.class.to_s
|
||||||
|
|||||||
Reference in New Issue
Block a user