Browse Source

Fixes #5428 - Notification loops because mail bouncing timeouts do not apply to users in system notifications.

Co-authored-by: Florian Liebe <fl@zammad.com>
Rolf Schmidt 3 months ago
parent
commit
ed079e358e

+ 66 - 39
app/models/channel/filter/bounce_delivery_permanent_failed.rb

@@ -1,15 +1,12 @@
 # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
 module Channel::Filter::BounceDeliveryPermanentFailed
-
   def self.run(_channel, mail, _transaction_params)
-
     return if !mail[:mail_instance]
     return if !mail[:mail_instance].bounced?
     return if !mail[:attachments]
 
     # remember, do not send notifications to certain recipients again if failed permanent
-    lines = %w[to cc]
     mail[:attachments].each do |attachment|
       next if !attachment[:preferences]
       next if attachment[:preferences]['Mime-Type'] != 'message/rfc822'
@@ -18,37 +15,12 @@ module Channel::Filter::BounceDeliveryPermanentFailed
       result = Channel::EmailParser.new.parse(attachment[:data], allow_missing_attribute_exceptions: false)
       next if !result[:message_id]
 
-      message_id_md5 = Digest::MD5.hexdigest(result[:message_id])
-      article = Ticket::Article.where(message_id_md5: message_id_md5).reorder('created_at DESC, id DESC').limit(1).first
-      next if !article
-
       # check user preferences
       next if mail[:mail_instance].action != 'failed'
       next if mail[:mail_instance].retryable? != false
-      next if mail[:mail_instance].error_status != '5.1.1'
+      next if !match_error_status?(mail[:mail_instance].error_status)
 
-      # get recipient of origin article, if only one - mark this user to not sent notifications anymore
-      recipients = []
-      if article.sender.name == 'System' || article.sender.name == 'Agent'
-        lines.each do |line|
-          next if article[line].blank?
-
-          recipients = []
-          begin
-            list = Mail::AddressList.new(article[line])
-            list.addresses.each do |address|
-              next if address.address.blank?
-
-              recipients.push address.address.downcase
-            end
-          rescue
-            Rails.logger.info "Unable to parse email address in '#{article[line]}'"
-          end
-        end
-        if recipients.count > 1
-          recipients = []
-        end
-      end
+      recipients = recipients_article(mail, result) || recipients_system_notification(mail, result) || []
 
       # get recipient bounce mail, mark this user to not sent notifications anymore
       final_recipient = mail[:mail_instance].final_recipient
@@ -60,19 +32,74 @@ module Channel::Filter::BounceDeliveryPermanentFailed
       end
 
       # set user preferences
-      recipients.each do |recipient|
-        users = User.where(email: recipient)
-        users.each do |user|
-          next if !user
-
-          user.preferences[:mail_delivery_failed] = true
-          user.preferences[:mail_delivery_failed_data] = Time.zone.now
-          user.save!
-        end
+      User.where(email: recipients.uniq).each do |user|
+        next if !user
+
+        user.preferences[:mail_delivery_failed] = true
+        user.preferences[:mail_delivery_failed_data] = Time.zone.now
+        user.save!
       end
     end
 
     true
+  end
+
+  def self.recipients_system_notification(_mail, bounce_email)
+    return if bounce_email['date'].blank?
+
+    date = bounce_email['date']
+    message_id = bounce_email['message-id']
+    return if message_id !~ %r{<notification\.\d+.(\d+).(\d+).[^>]+>}
+
+    ticket = Ticket.lookup(id: $1)
+    user   = User.lookup(id: $2)
+    return if user.blank?
+    return if ticket.blank?
+
+    valid = ticket.history_get.any? do |row|
+      next if row['created_at'] > date + 10.seconds
+      next if row['created_at'] < date - 10.seconds
+      next if row['type'] != 'notification'
+      next if !row['value_to'].start_with?(user.email)
+
+      true
+    end
+
+    return if valid.blank?
+
+    [user.email]
+  end
+
+  # get recipient of origin article, if only one - mark this user to not sent notifications anymore
+  def self.recipients_article(_mail, bounce_email)
+    message_id_md5 = Digest::MD5.hexdigest(bounce_email[:message_id])
+    article = Ticket::Article.where(message_id_md5: message_id_md5).reorder('created_at DESC, id DESC').limit(1).first
+    return if !article
+    return if article.sender.name != 'System' && article.sender.name != 'Agent'
+
+    recipients = []
+    %w[to cc].each do |line|
+      next if article[line].blank?
+
+      recipients = []
+      begin
+        list = Mail::AddressList.new(article[line])
+        list.addresses.each do |address|
+          next if address.address.blank?
+
+          recipients.push address.address.downcase
+        end
+      rescue
+        Rails.logger.info "Unable to parse email address in '#{article[line]}'"
+      end
+    end
+
+    return [] if recipients.count > 1
+
+    recipients
+  end
 
+  def self.match_error_status?(status)
+    status == '5.1.1'
   end
 end

+ 3 - 27
app/models/ticket/perform_changes/action/notification_email.rb

@@ -305,7 +305,7 @@ class Ticket::PerformChanges::Action::NotificationEmail < Ticket::PerformChanges
     users = User.where(email: recipient_email)
 
     users.any? do |user|
-      blocked_in_days = trigger_based_notification_blocked_in_days(user)
+      blocked_in_days = user.mail_delivery_failed_blocked_days
       if blocked_in_days.zero?
         false
       else
@@ -316,28 +316,6 @@ class Ticket::PerformChanges::Action::NotificationEmail < Ticket::PerformChanges
     end
   end
 
-  def trigger_based_notification_blocked_in_days(user)
-    preferences = user.preferences
-
-    return 0 if !preferences[:mail_delivery_failed]
-    return 0 if preferences[:mail_delivery_failed_data].blank?
-
-    # Blocked for 60 full days; see #4459.
-    remaining_days = (preferences[:mail_delivery_failed_data].to_date - Time.zone.now.to_date).to_i + 61
-    return remaining_days if remaining_days.positive?
-
-    # Cleanup the user preferences
-    trigger_based_notification_reset_blocking(user)
-
-    0
-  end
-
-  def trigger_based_notification_reset_blocking(user)
-    user.preferences[:mail_delivery_failed] = false
-    user.preferences[:mail_delivery_failed_data] = nil
-    user.save!
-  end
-
   def valid_recipient_address(recipient_email)
     begin
       Mail::AddressList.new(recipient_email).addresses.each do |address|
@@ -347,11 +325,9 @@ class Ticket::PerformChanges::Action::NotificationEmail < Ticket::PerformChanges
       end
     rescue
       if recipient_email.present?
-        if recipient_email !~ %r{^(.+?)<(.+?)@(.+?)>$}
-          return nil # no usable format found
-        end
+        return if recipient_email !~ %r{^.+?<(.+?@.+?)>$}
 
-        recipient_email = "#{$2}@#{$3}".downcase.strip
+        recipient_email = $1.downcase.strip
 
         email_address_validation = EmailAddressValidation.new(recipient_email)
         return recipient_email if email_address_validation.valid?

+ 7 - 1
app/models/transaction/notification.rb

@@ -153,6 +153,12 @@ class Transaction::Notification
     # ignore inactive users
     return if !user.active?
 
+    blocked_in_days = user.mail_delivery_failed_blocked_days
+    if blocked_in_days.positive?
+      Rails.logger.info "Send no system notifications to #{user.email} because email is marked as mail_delivery_failed for #{blocked_in_days} day(s)"
+      return
+    end
+
     # ignore if no changes has been done
     changes = human_changes(@item[:changes], ticket, user)
     return if @item[:type] == 'update' && !article && changes.blank?
@@ -313,7 +319,7 @@ class Transaction::Notification
   end
 
   def possible_recipients_of_group(group_id)
-    Rails.cache.fetch("User/notification/possible_recipients_of_group/#{group_id}", expires_in: 20.seconds) do
+    Rails.cache.fetch("User/notification/possible_recipients_of_group/#{group_id}/#{User.latest_change}", expires_in: 20.seconds) do
       User.group_access(group_id, 'full').sort_by(&:login)
     end
   end

+ 19 - 0
app/models/user.rb

@@ -706,6 +706,25 @@ try to find correct name
     preferences[:notification_config][:matrix] = Setting.get('ticket_agent_default_notifications')
   end
 
+  def mail_delivery_failed_blocked_days
+    return 0 if !preferences[:mail_delivery_failed]
+    return 0 if preferences[:mail_delivery_failed_data].blank?
+
+    # Blocked for 60 full days; see #4459.
+    remaining_days = (preferences[:mail_delivery_failed_data].to_date - Time.zone.now.to_date).to_i + 61
+    return remaining_days if remaining_days.positive?
+
+    reset_mail_delivery_failed
+
+    0
+  end
+
+  def reset_mail_delivery_failed
+    preferences[:mail_delivery_failed]      = false
+    preferences[:mail_delivery_failed_data] = nil
+    save!
+  end
+
   private
 
   def organization_history_log(org, type)

+ 1 - 1
spec/models/channel/email_parser_spec.rb

@@ -69,7 +69,7 @@ RSpec.describe Channel::EmailParser, type: :model do
       end
 
       it 'ensures tests were dynamically generated' do
-        expect(tests.count).to eq(108)
+        expect(tests.count).to eq(109)
       end
     end
 

+ 51 - 0
spec/models/channel/filter/bounce_delivery_permanent_failed_spec.rb

@@ -0,0 +1,51 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+require 'rails_helper'
+
+RSpec.describe Channel::Filter::BounceDeliveryPermanentFailed, type: :channel_filter do
+  describe 'Notification loops because mail bouncing timeouts do not apply to users in system notifications #5428', performs_jobs: true, sends_notification_emails: true do
+    let(:random_agent) { create(:agent, groups: [Group.first]) }
+    let(:dead_agent)   { create(:agent, email: 'not_existing@znuny.com', groups: [Group.first]) }
+    let(:ticket)       { create(:ticket, number: '10010', owner: dead_agent, group: Group.first) }
+    let(:failed_eml)   { Rails.root.join('test/data/mail/mail033-undelivered-mail-returned-to-sender-notification.box').read.sub('1.4.5f4fd063-f69b-4e64-a92d-5e7128bd6682', "#{ticket.id}.#{dead_agent.id}.5f4fd063-f69b-4e64-a92d-5e7128bd6682") }
+
+    before do
+      travel_to Time.zone.parse('Sun, 29 Aug 2015 16:56:00 +0200')
+      ticket
+      TransactionDispatcher.commit
+      perform_enqueued_jobs
+      travel_back
+    end
+
+    it 'does find the agent based on the system notifications and disables him for mail delivery', :aggregate_failures do
+      UserInfo.current_user_id = random_agent.id
+
+      check_notification do
+        ticket.update!(priority: Ticket::Priority.find_by(name: '3 high'))
+        TransactionDispatcher.commit
+        perform_enqueued_jobs
+
+        sent(
+          template: 'ticket_update',
+          user:     dead_agent,
+        )
+      end
+
+      mail = Channel::EmailParser.new.parse(failed_eml, allow_missing_attribute_exceptions: true | false)
+      filter(mail)
+      expect(dead_agent.reload.preferences[:mail_delivery_failed]).to be(true)
+      expect(dead_agent.reload.preferences[:mail_delivery_failed_data]).to be_present
+
+      check_notification do
+        ticket.update!(priority: Ticket::Priority.find_by(name: '1 low'))
+        TransactionDispatcher.commit
+        perform_enqueued_jobs
+
+        not_sent(
+          template: 'ticket_update',
+          user:     dead_agent,
+        )
+      end
+    end
+  end
+end

+ 168 - 0
test/data/mail/mail033-undelivered-mail-returned-to-sender-notification.box

@@ -0,0 +1,168 @@
+Return-Path: <MAILER-DAEMON>
+Delivered-To: edenhofer@zammad.example
+Received: by mx1.zammad.com (Postfix)
+	id 9246B20C3E96; Sun, 30 Aug 2015 16:56:06 +0200 (CEST)
+Date: Sun, 30 Aug 2015 16:56:06 +0200 (CEST)
+From: MAILER-DAEMON@mx1.zammad.com (Mail Delivery System)
+Subject: Undelivered Mail Returned to Sender
+To: edenhofer@zammad.example
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+	boundary="EF1A820C3E97.1440946566/mx1.zammad.com"
+Message-Id: <20150830145606.9246B20C3E96@mx1.zammad.com>
+
+This is a MIME-encapsulated message.
+
+--EF1A820C3E97.1440946566/mx1.zammad.com
+Content-Description: Notification
+Content-Type: text/plain; charset=us-ascii
+
+This is the mail system at host mx1.zammad.com.
+
+I'm sorry to have to inform you that your message could not
+be delivered to one or more recipients. It's attached below.
+
+For further assistance, please send mail to postmaster.
+
+If you do so, please include this problem report. You can
+delete your own text from the attached returned message.
+
+                   The mail system
+
+<not_existing@znuny.com>: host arber.znuny.com[88.198.51.81] said: 550 5.1.1
+    <not_existing@znuny.com>: Recipient address rejected: User unknown (in
+    reply to RCPT TO command)
+
+--EF1A820C3E97.1440946566/mx1.zammad.com
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mx1.zammad.com
+X-Postfix-Queue-ID: EF1A820C3E97
+X-Postfix-Sender: rfc822; edenhofer@zammad.example
+Arrival-Date: Sun, 30 Aug 2015 16:56:02 +0200 (CEST)
+
+Final-Recipient: rfc822; not_existing@znuny.com
+Original-Recipient: rfc822;not_existing@znuny.com
+Action: failed
+Status: 5.1.1
+Remote-MTA: dns; arber.znuny.com
+Diagnostic-Code: smtp; 550 5.1.1 <not_existing@znuny.com>: Recipient address
+    rejected: User unknown
+
+--EF1A820C3E97.1440946566/mx1.zammad.com
+Content-Description: Undelivered Message
+Content-Type: message/rfc822
+
+Return-Path: <edenhofer@zammad.example>
+Received: from appnode2.dc.zammad.com (appnode2.dc.zammad.com [144.1.1.1])
+	by mx1.zammad.com (Postfix) with ESMTP id 4B9BB20C3E96
+	for <not_existing@znuny.com>; Sun, 30 Aug 2015 16:56:02 +0200 (CEST)
+Received: by appnode2.dc.zammad.com (Postfix, from userid 1001)
+	id 36680141408; Sun, 30 Aug 2015 16:56:07 +0200 (CEST)
+Date: Sun, 30 Aug 2015 16:56:07 +0200
+From: Martin Edenhofer via Zammad Helpdesk <edenhofer@zammad.example>
+To: Martin Edenhofer <not_existing@znuny.com>
+Message-ID: <notification.20150830145600.1.4.5f4fd063-f69b-4e64-a92d-5e7128bd6682@127.0.0.1:3000>
+In-Reply-To:
+Subject: test [Ticket#10010]
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="--==_mimepart_55e319872c918_39d5375468c949db";
+ charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Organization:
+X-Mailer: Zammad Mail Service (1.x)
+X-znuny-MailScanner-Information: Please contact the ISP for more information
+X-znuny-MailScanner-ID: 4B9BB20C3E96.A3EE2
+X-znuny-MailScanner: Found to be clean
+X-znuny-MailScanner-From: edenhofer@zammad.example
+X-Spam-Status: No
+
+
+----==_mimepart_55e319872c918_39d5375468c949db
+Content-Type: multipart/alternative;
+ boundary="--==_mimepart_55e319872c5bd_39d5375468c94778";
+ charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+
+----==_mimepart_55e319872c5bd_39d5375468c94778
+Content-Type: text/plain;
+ charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+Hello,
+
+is somebody there?
+
+Martin Edenhofer
+
+--
+ Super Support - Waterford Business Park
+ 5201 Blue Lagoon Drive - 8th Floor & 9th Floor - Miami, 33126 USA
+ Email: [1] hot@example.com - Web: [2] http://www.example.com/
+--
+
+
+[1] mailto:hot@example.com
+[2] http://www.example.com/
+----==_mimepart_55e319872c5bd_39d5375468c94778
+Content-Type: multipart/related;
+ boundary="--==_mimepart_55e319872c7db_39d5375468c9486e";
+ charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+
+----==_mimepart_55e319872c7db_39d5375468c9486e
+Content-Type: text/html;
+ charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <style type="text/css">
+    body {
+      width:90% !important;
+      -webkit-text-size-adjust:90%;
+      -ms-text-size-adjust:90%;
+      font-family:'Helvetica Neue', Helvetica, Arial, Geneva, sans-serif; font-size: 12px;;
+    }
+    img {
+      outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;
+    }
+    a img {
+      border:none;
+    }
+    table td {
+      border-collapse: collapse;
+    }
+    table {
+      border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;
+    }
+    p, table, div, td {
+      max-width: 600px;
+    }
+    p {
+      margin: 0;
+    }
+    blockquote, pre {
+      margin: 0px;
+      padding: 8px 12px 8px 12px;
+    }
+
+    </style>
+  <head>
+  <body style="font-family:'Helvetica Neue', Helvetica, Arial, Geneva, sans-serif; font-size: 12px;"><div>Hello,</div><div><br></div><div>is somebody there?</div><br><div data-signature="true" data-signature-id="1"><div>Martin Edenhofer</div><div><br></div><div>--</div><div> Super Support - Waterford Business Park</div><div> 5201 Blue Lagoon Drive - 8th Floor &amp; 9th Floor - Miami, 33126 USA</div><div> Email: <a href="mailto:hot@example.com" title="mailto:hot@example.com" target="_blank">hot@example.com</a> - Web: <a href="http://www.example.com/" title="http://www.example.com/" target="_blank">http://www.example.com/</a></div><div>--</div></div></body>
+</html>
+
+----==_mimepart_55e319872c7db_39d5375468c9486e--
+
+----==_mimepart_55e319872c5bd_39d5375468c94778--
+
+----==_mimepart_55e319872c918_39d5375468c949db--
+
+--EF1A820C3E97.1440946566/mx1.zammad.com--

+ 90 - 0
test/data/mail/mail033-undelivered-mail-returned-to-sender-notification.yml

@@ -0,0 +1,90 @@
+--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess
+from: MAILER-DAEMON@mx1.zammad.com (Mail Delivery System)
+from_email: MAILER-DAEMON@mx1.zammad.com
+from_display_name: Mail Delivery System
+to: edenhofer@zammad.example
+subject: Undelivered Mail Returned to Sender
+body: |
+  This is the mail system at host mx1.zammad.com.
+
+  I'm sorry to have to inform you that your message could not
+  be delivered to one or more recipients. It's attached below.
+
+  For further assistance, please send mail to postmaster.
+
+  If you do so, please include this problem report. You can
+  delete your own text from the attached returned message.
+
+                     The mail system
+
+  <not_existing@znuny.com>: host arber.znuny.com[88.198.51.81] said: 550 5.1.1
+      <not_existing@znuny.com>: Recipient address rejected: User unknown (in
+      reply to RCPT TO command)
+content_type: text/plain
+attachments:
+- !ruby/hash:ActiveSupport::HashWithIndifferentAccess
+  data: |
+    Reporting-MTA: dns; mx1.zammad.com
+    X-Postfix-Queue-ID: EF1A820C3E97
+    X-Postfix-Sender: rfc822; edenhofer@zammad.example
+    Arrival-Date: Sun, 30 Aug 2015 16:56:02 +0200 (CEST)
+
+    Final-Recipient: rfc822; not_existing@znuny.com
+    Original-Recipient: rfc822;not_existing@znuny.com
+    Action: failed
+    Status: 5.1.1
+    Remote-MTA: dns; arber.znuny.com
+    Diagnostic-Code: smtp; 550 5.1.1 <not_existing@znuny.com>: Recipient address
+        rejected: User unknown
+  filename: Delivery report.txt
+  preferences: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
+    Content-Type: message/delivery-status
+    Content-Description: Delivery report
+    Mime-Type: message/delivery-status
+    Charset: UTF-8
+- !ruby/hash:ActiveSupport::HashWithIndifferentAccess
+  data: "Return-Path: <edenhofer@zammad.example>\nReceived: from appnode2.dc.zammad.com
+    (appnode2.dc.zammad.com [144.1.1.1])\n\tby mx1.zammad.com (Postfix) with ESMTP
+    id 4B9BB20C3E96\n\tfor <not_existing@znuny.com>; Sun, 30 Aug 2015 16:56:02 +0200
+    (CEST)\nReceived: by appnode2.dc.zammad.com (Postfix, from userid 1001)\n\tid
+    36680141408; Sun, 30 Aug 2015 16:56:07 +0200 (CEST)\nDate: Sun, 30 Aug 2015 16:56:07
+    +0200\nFrom: Martin Edenhofer via Zammad Helpdesk <edenhofer@zammad.example>\nTo:
+    Martin Edenhofer <not_existing@znuny.com>\nMessage-ID: <notification.20150830145600.1.4.5f4fd063-f69b-4e64-a92d-5e7128bd6682@127.0.0.1:3000>\nIn-Reply-To:\nSubject:
+    test [Ticket#10010]\nMime-Version: 1.0\nContent-Type: multipart/mixed;\n boundary=\"--==_mimepart_55e319872c918_39d5375468c949db\";\n
+    charset=UTF-8\nContent-Transfer-Encoding: 7bit\nOrganization:\nX-Mailer: Zammad
+    Mail Service (1.x)\nX-znuny-MailScanner-Information: Please contact the ISP for
+    more information\nX-znuny-MailScanner-ID: 4B9BB20C3E96.A3EE2\nX-znuny-MailScanner:
+    Found to be clean\nX-znuny-MailScanner-From: edenhofer@zammad.example\nX-Spam-Status:
+    No\n\n\n----==_mimepart_55e319872c918_39d5375468c949db\nContent-Type: multipart/alternative;\n
+    boundary=\"--==_mimepart_55e319872c5bd_39d5375468c94778\";\n charset=UTF-8\nContent-Transfer-Encoding:
+    7bit\n\n\n----==_mimepart_55e319872c5bd_39d5375468c94778\nContent-Type: text/plain;\n
+    charset=UTF-8\nContent-Transfer-Encoding: 7bit\n\nHello,\n\nis somebody there?\n\nMartin
+    Edenhofer\n\n--\n Super Support - Waterford Business Park\n 5201 Blue Lagoon Drive
+    - 8th Floor & 9th Floor - Miami, 33126 USA\n Email: [1] hot@example.com - Web:
+    [2] http://www.example.com/\n--\n\n\n[1] mailto:hot@example.com\n[2] http://www.example.com/\n----==_mimepart_55e319872c5bd_39d5375468c94778\nContent-Type:
+    multipart/related;\n boundary=\"--==_mimepart_55e319872c7db_39d5375468c9486e\";\n
+    charset=UTF-8\nContent-Transfer-Encoding: 7bit\n\n\n----==_mimepart_55e319872c7db_39d5375468c9486e\nContent-Type:
+    text/html;\n charset=UTF-8\nContent-Transfer-Encoding: 7bit\n\n<!DOCTYPE html>\n<html>\n
+    \ <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n
+    \   <style type=\"text/css\">\n    body {\n      width:90% !important;\n      -webkit-text-size-adjust:90%;\n
+    \     -ms-text-size-adjust:90%;\n      font-family:'Helvetica Neue', Helvetica,
+    Arial, Geneva, sans-serif; font-size: 12px;;\n    }\n    img {\n      outline:none;
+    text-decoration:none; -ms-interpolation-mode: bicubic;\n    }\n    a img {\n      border:none;\n
+    \   }\n    table td {\n      border-collapse: collapse;\n    }\n    table {\n
+    \     border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;\n
+    \   }\n    p, table, div, td {\n      max-width: 600px;\n    }\n    p {\n      margin:
+    0;\n    }\n    blockquote, pre {\n      margin: 0px;\n      padding: 8px 12px
+    8px 12px;\n    }\n\n    </style>\n  <head>\n  <body style=\"font-family:'Helvetica
+    Neue', Helvetica, Arial, Geneva, sans-serif; font-size: 12px;\"><div>Hello,</div><div><br></div><div>is
+    somebody there?</div><br><div data-signature=\"true\" data-signature-id=\"1\"><div>Martin
+    Edenhofer</div><div><br></div><div>--</div><div> Super Support - Waterford Business
+    Park</div><div> 5201 Blue Lagoon Drive - 8th Floor &amp; 9th Floor - Miami, 33126
+    USA</div><div> Email: <a href=\"mailto:hot@example.com\" title=\"mailto:hot@example.com\"
+    target=\"_blank\">hot@example.com</a> - Web: <a href=\"http://www.example.com/\"
+    title=\"http://www.example.com/\" target=\"_blank\">http://www.example.com/</a></div><div>--</div></div></body>\n</html>\n\n----==_mimepart_55e319872c7db_39d5375468c9486e--\n\n----==_mimepart_55e319872c5bd_39d5375468c94778--\n\n----==_mimepart_55e319872c918_39d5375468c949db--\n"
+  filename: test [Ticket#10010].eml
+  preferences: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
+    Content-Type: message/rfc822
+    Content-Description: Undelivered Message
+    Mime-Type: message/rfc822
+    Charset: UTF-8