fiedl/your_platform

View on GitHub
core_ext/mail/message.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require File.join(Gem.loaded_specs['mail'].full_gem_path, 'lib/mail/message')
require File.join(Gem.loaded_specs['extended_email_reply_parser'].full_gem_path, 'lib/extended_email_reply_parser/mail/message')

# This extends the mail message class, which is originally defined in
#
#     https://github.com/mikel/mail
#     https://github.com/mikel/mail/blob/master/lib/mail/message.rb
#
# and further extended in
#
#     https://github.com/fiedl/extended_email_reply_parser
#
module YourPlatformMailMessageExtensions

  # This method overrides the original delivery method in order to
  # handle cases where the message cannot be delivered. In those
  # cases, the email adress is marked as 'invalid'.
  #
  # Also make sure to only deliver emails to users with accounts.
  #
  def deliver
    Rails.logger.info "Beginning delivery of Mail::Message #{message_id} for #{recipient_address}."

    if recipient_address.include?('@')
      begin
        Rails.logger.info "Sending mail smtp_envelope_to #{self.smtp_envelope_to.to_s}."
        return super
      rescue Net::SMTPFatalError, Net::SMTPSyntaxError => error
        Rails.logger.debug error
        Rails.logger.warn "smtp-envelope-to field: #{self.smtp_envelope_to.to_s}"
        Rails.logger.warn "to field: #{self.to.to_s}"
        Rails.logger.warn "recognized recipient address (address only!): #{recipient_address}"
        Rails.logger.warn error.message
        recipient_address_needs_review!
        return false
      rescue Net::SMTPServerBusy => error
        Rails.logger.debug error
        Rails.logger.warn "Net::SMTPServerBusy when sending message. Waiting 60 seconds and retrying then ..."
        sleep 60
        retry
      end
    else
      Rails.logger.info "Recipient address #{recipient_address} needs review. Not delivering."
      recipient_address_needs_review!
      return false
    end
  end

  def allow_recipients_without_account!
    @allow_recipients_without_account = true
  end

  def content_type
    super || guess_content_type
  end

  def guess_content_type
    if body_in_utf8.include? "<p>"
      'text/html'
    else
      'text/plain'
    end
  end

  def recipient_address
    self.smtp_envelope_to.try(:first) || self.to.try(:first)
  end

  def recipient_address_needs_review!
    raise RuntimeError, 'no recipient address' unless recipient_address.present?
    if profile_field = recipient_email_profile_field
      Rails.logger.warn "Adding :needs_review flag to email address #{recipient_address}."
      profile_field.needs_review!
    else
      Rails.logger.warn "Could not find matching email address #{recipient_address} in our database."
    end
  end

  # When we have an incoming email, the smtp envelope is already gone and unavailable
  # to this application. Thus, we need to read out the headers.
  #
  def smtp_envelope_to_header
    header_fields.select { |field| field.name.downcase.in? ['smtp-envelope-to', 'envelope-to'] }.collect { |field|
      value = field.value
      value = value.split("<").last.split(">").first if value && value.include?("<")
      value
    }
  end

  # The postfix transport might add the `RCPT TO` as `X-Original-To` header.
  # See: https://trello.com/c/ZbMA33GL/1021-e-mails-envelope
  # And: https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script#comment1270334_258491
  #
  def x_original_to
    header_fields.select { |field| field.name.downcase == "x-original-to" }.collect do |field|
      if field.value.include? "<"
        field.value.split("<").last.split(">").first
      else
        field.value
      end
    end.reverse # The top-most header is added latest.
  end

  # For forwarding a modified message through action mailer, we need to deliver
  # the message object. But in order to do that we need to import some settings
  # from action mailer.
  #
  def deliver_with_action_mailer_now
    import_delivery_method_from_actionmailer
    deliver
  end

  def deliver_with_action_mailer_later
    DeliverEmailMessageJob.perform_later(raw_message: self.to_s, envelope_attributes: envelope_attributes)
  end

  def import_delivery_method_from_actionmailer
    case ActionMailer::Base.delivery_method
    when :test
      delivery_method Mail::TestMailer
    when :smtp
      delivery_method Mail::SMTP, ActionMailer::Base.smtp_settings
    when :letter_opener
      delivery_method LetterOpener::DeliveryMethod, location: File.join(Rails.root, 'tmp/letter_opener')
    when :sendmail
      delivery_method Mail::Sendmail, location: '/usr/sbin/sendmail', arguments: '-i -t'
    end
  end

  def envelope_attributes
    {smtp_envelope_to: smtp_envelope_to, smtp_envelope_from: smtp_envelope_from}
  end


  # Replace a string in the message, e.g. a {{placeholder}}.
  #
  def replace(search, replace)
    if self.multipart?
      self.parts.each do |part|
        if part.text?
          part.body.raw_source.replace part.body.decoded.gsub(search, replace) if part.body.decoded.include? search
        elsif part.multipart?
          part.parts.each do |part|
            if part.text?
              part.body.raw_source.replace part.body.decoded.gsub(search, replace) if part.body.decoded.include? search
            end
          end
        end
      end
    else
      self.body.raw_source.replace self.body.decoded.gsub(search, replace) if self.body.decoded.include? search
    end
  end

  # Try to extract the text of the message
  def text
    if self.multipart?
      self.parts.each do |part|
        if part.text?
          return part.body.decoded
        elsif part.multipart?
          part.parts.each do |part|
            if part.text?
              return part.body.decoded
            end
          end
        end
      end
    else
      return self.body.decoded
    end
  end


  private

  def recipient_email_profile_field
    ProfileFields::Email.where(value: recipient_address).first if recipient_address.present?
  end

  def recipient_has_user_account?
    recipient_address.present? && User.find_by_email(recipient_address).try(:has_account?)
  end

  def recipient_is_system_address?
    (recipient_address == Setting.support_email) or recipient_address.ends_with?("fiedlschuster.de") or recipient_address.ends_with?("yourplatform.io") or recipient_address.ends_with?("wingolf.io")
  end

end

class Mail::Message
  prepend YourPlatformMailMessageExtensions
end