Class: UnsubscribesMailbox

Inherits:
ApplicationMailbox show all
Defined in:
app/mailboxes/unsubscribes_mailbox.rb

Overview

ActionMailbox handler for inbound list-unsubscribe replies.

Routed from config/initializers/450_action_mailbox_routing.rb for
recipients matching RECIPIENT_FORMAT (e.g.
list-unsubscribe+<uid>@warmlyyours.com or list-remove+<uid>@...).
The <uid> is the Communication#unique_id minted in
Communication#email and surfaced as the List-Unsubscribe header on
outbound marketing email — see Communication#email.

When the UID resolves to known recipients, their EmailPreference rows
are flipped to fully-unsubscribed via EmailPreference.unsubscribe.

See Also:

Defined Under Namespace

Classes: EmailNotFoundError

Constant Summary collapse

RECIPIENT_FORMAT =

E.g list-unsubscribe+79b3b15c-4f68-4d53-bf32-b44966744c45@warmlyyours.com
list-remove+8b3ee4d1-6c53-4c7b-b654-66f4328030ce@warmlyyours.com>
See communication mailer where this is set into the header in #email method.
If this format changes then it needs to change there as well. The named
uid capture is consumed by #processString#scan with multiple
capture groups previously returned [type, uid] and a .first.first
silently extracted "remove"/"unsubscribe" instead of the UUID, so every
inbound list-unsubscribe became a no-op.

/list-(?:remove|unsubscribe)\+(?<uid>[\w-]+)@/i

Constants inherited from ApplicationMailbox

ApplicationMailbox::DIRECT_FILE_EXTENSIONS, ApplicationMailbox::FETCHABLE_MIME_TYPES, ApplicationMailbox::FETCH_MAX_SIZE, ApplicationMailbox::GDRIVE_FILE_RE, ApplicationMailbox::GDRIVE_OPEN_RE, ApplicationMailbox::MAX_FILENAME_LENGTH

Instance Method Summary collapse

Methods inherited from ApplicationMailbox

#attempt_url_download, #collect_fetchable_urls, #create_activity, #create_communication, #fetch_linked_files, #find_sender_party, #get_resource_id, #inline_or_attachment_part?, #process_attachments, #process_content, #replace_cid_references, #sanitize_attachment_filename

Instance Method Details

#processvoid

This method returns an undefined value.

Routes a single inbound list-unsubscribe email to the relevant
EmailPreference rows. When the sender's address matches one of the
original communication's recipients, only that address is unsubscribed;
otherwise every recipient on the originating communication is
unsubscribed (the typical case for "Clean Email"-style proxies that
send from their own domain).

Raises:



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/mailboxes/unsubscribes_mailbox.rb', line 44

def process
  to = mail.to&.first
  sender = mail.from&.first

  Rails.logger.info "Processing Unbuscribe Mail for #{sender}, in response to #{to}"

  uid = to&.match(RECIPIENT_FORMAT)&.[](:uid)
  if uid.blank?
    msg = "UID could not be extracted for sender #{sender} with unsubscribe #{to} process manually"
    Rails.logger.error msg
    ErrorReporting.error(msg, source: :background, sender: sender, to: to, inbound_email_id: inbound_email.id)
    return
  end

  Rails.logger.info "Parsed uid as #{uid}, seeking emails matching"
  emails = CommunicationRecipient.emails
                                 .joins(:communication)
                                 .where(communications: { unique_id: uid })
                                 .where.not(detail: nil)
                                 .distinct
                                 .pluck(:detail)
                                 .map(&:downcase)

  if emails.empty?
    msg = "UnsubscribesMailbox could not find any communication recipients for uid=#{uid}"
    ErrorReporting.error(msg, source: :background, sender: sender, to: to, uid: uid, inbound_email_id: inbound_email.id)
    raise EmailNotFoundError, msg
  end

  if (parsed_emails = emails.select { |e| e == sender&.downcase }).present?
    Rails.logger.info "Detected #{parsed_emails.join(', ')} and unsubscribing"
    emails = parsed_emails
  else
    Rails.logger.info "Unsubscribing all emails: #{emails.join(', ')}"
  end

  PaperTrail.request(whodunnit: 'UnsubscribesMailbox') do
    EmailPreference.unsubscribe(emails)
  end
end