class EmailImporter attr_accessor :email_class attr_accessor :metadata_mapping_class attr_accessor :html_to_text_class def initialize( email_class: Email, metadata_mapping_class: MetadataMapping, html_to_text_class: Rails.configuration.html_to_text_class) @email_class = email_class @metadata_mapping_class = metadata_mapping_class @html_to_text_class = html_to_text_class end def import(mail) email = email_class.new( message_id: mail.message_id, subject: mail.subject, date: mail.date, to: mail.to, delivered_to: delivered_to(mail), from: mail.from, plain_body: text_plain_body(mail), headers: hashed_headers(mail), cron: sent_by_cron?(mail), mailing_list: mailing_list?(mail), clients: clients(mail), servers: servers(mail), tickets: tickets(mail) ) rescue => ex binding.pry end def delivered_to(mail) values_from_header(header: mail.header["Delivered-To"], default: Array(mail.to)) end def text_plain_body(mail) if mail.parts.present? if mail.text_part.present? mail.text_part.decoded elsif mail.html_part.present? html_to_text_class.new.convert(mail.html_part.decoded) else mail.parts[0].decoded end elsif mail.content_type && mail.content_type.match?(/\btext\/html\b/) plain_text = html_to_text_class.new.convert(mail.decoded) if mail.content_type.match?(/\butf-8\b/) # force utf-8 charset, even if we have to remve invalid characters plain_text.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') else plain_text end else mail.decoded end end def hashed_headers(mail) mail.header.map { |header| { name: header.name, value: header.value } } end def sent_by_cron?(mail) (mail.subject.present? && mail.subject.match?(/cron/i)) \ || mail.header["X-Cron-Env"].present? end def mailing_list?(mail) mail.header["List-Unsubscribe"].present? end def clients(mail) metadata(mail).filter_map(&:entity).uniq end def servers(mail) metadata(mail).filter_map(&:server).uniq end def tickets(mail) values_from_header(header: mail.header["X-Ticket-Id"]) end def values_from_header(header:, default: []) if header.respond_to?(:map) header.map(&:value) elsif header.respond_to?(:value) Array(header.value) elsif block_given? yield(header) else default end end def metadata(mail) @metadata ||= metadata_mapping_class.where(input: metadata_inputs(mail)).all end def metadata_inputs(mail) inputs = [] # add mail addresses and hostnames from headers inputs << ["To", "Delivered-To", "X-Original-To", "From", "Subject"].filter_map { |header_name| mail.header[header_name] }.flatten.map(&:value).filter_map() { |header_value| keep_email_and_hostnames(header_value) }.flatten.filter_map() { |value| clean_subdomains(value) }.flatten # add other values from headers inputs << mail.header["X-Client-Id"] inputs.uniq end def keep_email_and_hostnames(string) pattern = /\b((?:([-a-zA-Z0-9\._]+)@)?((?:[-a-zA-Z0-9\._]*)(?:\.[a-z]{2,})))\b/ results = string.scan(pattern) if results.present? results.map(&:first) end end def clean_subdomains(value) [ [/[-a-zA-Z0-9\._]+@([-a-zA-Z0-9]+).evolix.net/, '@\1'] ].filter_map { |item| if value.match?(item[0]) value.gsub!(item[0], item[1]) else value end } end end