From 48e3da57ee06227c9b57f96a5afb56f132325aa1 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 30 May 2017 08:55:51 +0200 Subject: [PATCH] Improved email loop protection. Remember permanent delivery failed on user to prevent email loops. --- .../bounce_delivery_permanent_failed.rb | 73 +++++ ...nce_check.rb => bounce_follow_up_check.rb} | 6 +- app/models/ticket.rb | 13 + ...00002_setting_delivery_permanent_failed.rb | 35 +++ db/seeds/settings.rb | 13 +- test/fixtures/mail55.box | 230 ++++++++++++++++ ...s_bounce_delivery_permanent_failed_test.rb | 220 +++++++++++++++ test/unit/email_process_bounce_follow_test.rb | 254 ++++++++++++++++++ test/unit/email_process_bounce_test.rb | 39 --- 9 files changed, 841 insertions(+), 42 deletions(-) create mode 100644 app/models/channel/filter/bounce_delivery_permanent_failed.rb rename app/models/channel/filter/{bounce_check.rb => bounce_follow_up_check.rb} (90%) create mode 100644 db/migrate/20170529000002_setting_delivery_permanent_failed.rb create mode 100644 test/fixtures/mail55.box create mode 100644 test/unit/email_process_bounce_delivery_permanent_failed_test.rb create mode 100644 test/unit/email_process_bounce_follow_test.rb delete mode 100644 test/unit/email_process_bounce_test.rb diff --git a/app/models/channel/filter/bounce_delivery_permanent_failed.rb b/app/models/channel/filter/bounce_delivery_permanent_failed.rb new file mode 100644 index 000000000..ecd75f39f --- /dev/null +++ b/app/models/channel/filter/bounce_delivery_permanent_failed.rb @@ -0,0 +1,73 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +module Channel::Filter::BounceDeliveryPermanentFailed + + def self.run(_channel, mail) + + 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 + mail[:attachments].each { |attachment| + next if !attachment[:preferences] + next if attachment[:preferences]['Mime-Type'] != 'message/rfc822' + next if !attachment[:data] + + result = Channel::EmailParser.new.parse(attachment[:data]) + next if !result[:message_id] + message_id_md5 = Digest::MD5.hexdigest(result[:message_id]) + article = Ticket::Article.where(message_id_md5: message_id_md5).order('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' + + # 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' + %w(to cc).each { |line| + next if article[line].blank? + recipients = [] + begin + list = Mail::AddressList.new(article[line]) + list.addresses.each { |address| + next if address.address.blank? + recipients.push address.address.downcase + } + rescue + Rails.logger.info "Unable to parse email address in '#{article[line]}'" + end + } + if recipients.count > 1 + recipients = [] + end + end + + # get recipient bounce mail, mark this user to not sent notifications anymore + final_recipient = mail[:mail_instance].final_recipient + if final_recipient.present? + final_recipient.sub!(/rfc822;\s{0,10}/, '') + if final_recipient.present? + recipients.push final_recipient.downcase + end + end + + # set user preferences + recipients.each { |recipient| + users = User.where(email: recipient) + users.each { |user| + next if !user + user.preferences[:mail_delivery_failed] = true + user.preferences[:mail_delivery_failed_data] = Time.zone.now + user.save! + } + } + } + + true + + end +end diff --git a/app/models/channel/filter/bounce_check.rb b/app/models/channel/filter/bounce_follow_up_check.rb similarity index 90% rename from app/models/channel/filter/bounce_check.rb rename to app/models/channel/filter/bounce_follow_up_check.rb index 1835254b6..872053564 100644 --- a/app/models/channel/filter/bounce_check.rb +++ b/app/models/channel/filter/bounce_follow_up_check.rb @@ -1,6 +1,6 @@ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ -module Channel::Filter::BounceCheck +module Channel::Filter::BounceFollowUpCheck def self.run(_channel, mail) @@ -13,13 +13,17 @@ module Channel::Filter::BounceCheck next if !attachment[:preferences] next if attachment[:preferences]['Mime-Type'] != 'message/rfc822' next if !attachment[:data] + result = Channel::EmailParser.new.parse(attachment[:data]) next if !result[:message_id] message_id_md5 = Digest::MD5.hexdigest(result[:message_id]) article = Ticket::Article.where(message_id_md5: message_id_md5).order('created_at DESC, id DESC').limit(1).first next if !article + Rails.logger.debug "Follow up for '##{article.ticket.number}' in bounce email." mail[ 'x-zammad-ticket-id'.to_sym ] = article.ticket_id + mail[ 'x-zammad-is-auto-response'.to_sym ] = true + return true } diff --git a/app/models/ticket.rb b/app/models/ticket.rb index ec000154e..668f43c86 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -813,6 +813,19 @@ perform changes on ticket recipients_checked = [] recipients_raw.each { |recipient_email| + skip_user = false + users = User.where(email: recipient_email) + users.each { |user| + next if user.preferences[:mail_delivery_failed] != true + next if !user.preferences[:mail_delivery_failed_data] + till_blocked = ((user.preferences[:mail_delivery_failed_data] - Time.zone.now - 60.days) / 60 / 60 / 24).round + next if till_blocked.positive? + logger.info "Send no trigger based notification to #{recipient_email} because email is marked as mail_delivery_failed for #{till_blocked} days" + skip_user = true + break + } + next if skip_user + # send notifications only to email adresses next if !recipient_email next if recipient_email !~ /@/ diff --git a/db/migrate/20170529000002_setting_delivery_permanent_failed.rb b/db/migrate/20170529000002_setting_delivery_permanent_failed.rb new file mode 100644 index 000000000..aa299bb42 --- /dev/null +++ b/db/migrate/20170529000002_setting_delivery_permanent_failed.rb @@ -0,0 +1,35 @@ +class SettingDeliveryPermanentFailed < ActiveRecord::Migration + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + setting = Setting.find_by(name: '0900_postmaster_filter_bounce_check') + if setting + setting.name = '0900_postmaster_filter_bounce_follow_up_check' + setting.state = 'Channel::Filter::BounceFollowUpCheck' + setting.save! + else + Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0900_postmaster_filter_bounce_follow_up_check', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to identify postmaster bounced - to handle it as follow-up of the original ticket.', + options: {}, + state: 'Channel::Filter::BounceFollowUpCheck', + frontend: false + ) + end + Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0950_postmaster_filter_bounce_delivery_permanent_failed', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to identify postmaster bounced - disable sending notification on permanent deleivery failed.', + options: {}, + state: 'Channel::Filter::BounceDeliveryPermanentFailed', + frontend: false + ) + + end + +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 53ffb728a..777dc1aa1 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -2327,11 +2327,20 @@ Setting.create_if_not_exists( ) Setting.create_if_not_exists( title: 'Defines postmaster filter.', - name: '0900_postmaster_filter_bounce_check', + name: '0900_postmaster_filter_bounce_follow_up_check', area: 'Postmaster::PreFilter', description: 'Defines postmaster filter to identify postmaster bounced - to handle it as follow-up of the original ticket.', options: {}, - state: 'Channel::Filter::BounceCheck', + state: 'Channel::Filter::BounceFollowUpCheck', + frontend: false +) +Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0950_postmaster_filter_bounce_delivery_permanent_failed', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to identify postmaster bounced - disable sending notification on permanent deleivery failed.', + options: {}, + state: 'Channel::Filter::BounceDeliveryPermanentFailed', frontend: false ) Setting.create_if_not_exists( diff --git a/test/fixtures/mail55.box b/test/fixtures/mail55.box new file mode 100644 index 000000000..de2b25785 --- /dev/null +++ b/test/fixtures/mail55.box @@ -0,0 +1,230 @@ +Return-Path: +Delivered-To: example@zammad.com +Received: by mx1.zammad.loc (Postfix) + id 738F920A13B2; Fri, 26 May 2017 17:01:45 +0200 (CEST) +Date: Fri, 26 May 2017 17:01:45 +0200 (CEST) +From: MAILER-DAEMON@mx1.zammad.loc (Mail Delivery System) +Subject: Undelivered Mail Returned to Sender +To: example@zammad.com +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="207FF20398ED.1495810905/mx1.zammad.loc" +Message-Id: <20170526150145.738F920A13B2@mx1.zammad.loc> + +This is a MIME-encapsulated message. + +--207FF20398ED.1495810905/mx1.zammad.loc +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + +This is the mail system at host mx1.zammad.loc. + +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 + + : host aspmx.l.example.com[108.177.96.26] said: + 550-5.1.1 The email account that you tried to reach does not exist. Please + try 550-5.1.1 double-checking the recipient's email address for typos or + 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1 + https://support.example.com/mail/?p=NoSuchUser l59si1635011edl.281 - gsmtp + (in reply to RCPT TO command) + +--207FF20398ED.1495810905/mx1.zammad.loc +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mx1.zammad.loc +X-Postfix-Queue-ID: 207FF20398ED +X-Postfix-Sender: rfc822; example@zammad.com +Arrival-Date: Fri, 26 May 2017 17:01:45 +0200 (CEST) + +Final-Recipient: rfc822; ticket-bounce-trigger2@example.com +Original-Recipient: rfc822;ticket-bounce-trigger2@example.com +Action: failed +Status: 5.1.1 +Remote-MTA: dns; aspmx.l.example.com +Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach does + not exist. Please try 550-5.1.1 double-checking the recipient's email + address for typos or 550-5.1.1 unnecessary spaces. Learn more at 550 5.1.1 + https://support.example.com/mail/?p=NoSuchUser l59si1635011edl.281 - gsmtp + +--207FF20398ED.1495810905/mx1.zammad.loc +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: from apn0000.dc.zammad.com (apn0000.dc.zammad.com [88.0.0.0]) + by mx1.zammad.loc (Postfix) with ESMTP id 207FF20398ED + for ; Fri, 26 May 2017 17:01:45 +0200 (CEST) +Received: by apn0000.dc.zammad.com (Postfix, from userid 1050) + id 08443420973; Fri, 26 May 2017 17:01:45 +0200 (CEST) +Date: Fri, 26 May 2017 17:01:45 +0200 +From: Twelve SaaS GmbH Helpdesk +To: ticket-bounce-trigger2@example.com +Message-ID: <20170526150141.232.13312@example.zammad.loc> +In-Reply-To: +References: <20170526150142.232.819805@example.zammad.loc> + <20170526150119.6C5E520A13B2@mx1.zammad.loc> + <20170526150141.232.799457@example.zammad.loc> + <20170526150117.0560820A13B3@mx1.zammad.loc> + <20170526150115.232.175460@example.zammad.loc> + <20170526150108.232.482766@example.zammad.loc> + <20170526150041.F3D2C20A13B3@mx1.zammad.loc> + <20170526150036.232.513248@example.zammad.loc> + <20170526150008.6AE8A20A13B8@mx1.zammad.loc> + <20170526150004.232.103372@example.zammad.loc> + <20170526145940.D799220A13B3@mx1.zammad.loc> + <20170526145932.232.91897@example.zammad.loc> + <20170526145906.8FCA520A13B2@mx1.zammad.loc> + <20170526145901.232.269971@example.zammad.loc> + + Subject: =?UTF-8?Q?[Ticket#1705265400361]_RE:_Thanks_for_your_follow_up?= + =?UTF-8?Q?_=28G_Suite:_Benachrichtigung_=C3=BCber_Verl=C3=A4ngerung_in_30?= + =?UTF-8?Q?_Tagen=29?= + Mime-Version: 1.0 +Content-Type: multipart/mixed; + boundary="--==_mimepart_5928435950b1_22d42086504355bc"; + charset=UTF-8 + Content-Transfer-Encoding: 7bit +Organization: Twelve SaaS GmbH +X-Loop: yes +Precedence: bulk +Auto-Submitted: auto-generated +X-Auto-Response-Suppress: All +X-Powered-By: Zammad - Helpdesk/Support (https://zammad.org/) +X-Mailer: Zammad Mail Service + + +----==_mimepart_5928435950b1_22d42086504355bc +Content-Type: multipart/alternative; + boundary="--==_mimepart_592843594d35_22d4208650435394"; + charset=UTF-8 + Content-Transfer-Encoding: 7bit + + +----==_mimepart_592843594d35_22d4208650435394 +Content-Type: text/plain; + charset=UTF-8 + Content-Transfer-Encoding: 7bit + +Your follow up for (Ticket#1705265400361) has been received and will be reviewed by our support staff. + +To provide additional information, please reply to this email or click on the following link:[1] https://example.zammad.loc/#ticket/zoom/232 + +Your Twelve SaaS Helpdesk Team + +[2] Zammad, your customer support system + +[1] https://example.zammad.loc/#ticket/zoom/232 +[2] https://zammad.com +----==_mimepart_592843594d35_22d4208650435394 +Content-Type: multipart/related; + boundary="--==_mimepart_592843594e47_22d4208650435484"; + charset=UTF-8 + Content-Transfer-Encoding: 7bit + + +----==_mimepart_592843594e47_22d4208650435484 +Content-Type: text/html; + charset=UTF-8 + Content-Transfer-Encoding: 7bit + + + + + + + +
Your follow up for (Ticket#1705265400361) has been received and will be reviewed by our support staff.
+
+
To provide additional information, please reply to this email or click on the following link: +https://example.zammad.loc/#ticket/zoom/232 +
+
+
Your Twelve SaaS Helpdesk Team
+
+
Zammad, your customer support system
+ + +----==_mimepart_592843594e47_22d4208650435484-- + +----==_mimepart_592843594d35_22d4208650435394-- + +----==_mimepart_5928435950b1_22d42086504355bc-- + +--207FF20398ED.1495810905/mx1.zammad.loc-- diff --git a/test/unit/email_process_bounce_delivery_permanent_failed_test.rb b/test/unit/email_process_bounce_delivery_permanent_failed_test.rb new file mode 100644 index 000000000..f62210e55 --- /dev/null +++ b/test/unit/email_process_bounce_delivery_permanent_failed_test.rb @@ -0,0 +1,220 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessBounceDeliveryPermanentFailedTest < ActiveSupport::TestCase + + test 'process with bounce trigger email loop check - article based blocker' do + roles = Role.where(name: %w(Customer)) + customer1 = User.create_or_update( + login: 'ticket-bounce-trigger1@example.com', + firstname: 'Notification', + lastname: 'Customer1', + email: 'ticket-bounce-trigger1@example.com', + active: true, + roles: roles, + preferences: {}, + updated_by_id: 1, + created_by_id: 1, + ) + + Trigger.create_or_update( + name: 'auto reply new ticket', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'create', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your inquiry (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + Trigger.create_or_update( + name: 'auto reply followup', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'update', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your follow up (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + + ticket = Ticket.create( + title: 'bounce check', + group: Group.lookup(name: 'Users'), + customer: customer1, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check', + message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: customer1.email, + subject: 'bounce check 2', + message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', + body: 'some message bounce check 2', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal(4, ticket.articles.count) + + travel 1.second + email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box') + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string) + assert_equal(ticket.id, ticket_p.id) + assert_equal('open', ticket_p.state.name) + assert_equal(5, ticket_p.articles.count) + travel_back + ticket.destroy + end + + test 'process with bounce trigger email loop check - bounce based blocker' do + roles = Role.where(name: %w(Customer)) + customer2 = User.create_or_update( + login: 'ticket-bounce-trigger2@example.com', + firstname: 'Notification', + lastname: 'Customer2', + email: 'ticket-bounce-trigger2@example.com', + active: true, + roles: roles, + preferences: {}, + updated_by_id: 1, + created_by_id: 1, + ) + + Trigger.create_or_update( + name: 'auto reply new ticket', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'create', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your inquiry (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + Trigger.create_or_update( + name: 'auto reply followup', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'update', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your follow up (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + + ticket = Ticket.create( + title: 'bounce check', + group: Group.lookup(name: 'Users'), + customer: customer2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check', + message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check 2', + message_id: '<20170526150141.232.13312@example.zammad.loc>', + body: 'some message bounce check 2', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal(4, ticket.articles.count) + + travel 1.second + email_raw_string = IO.binread('test/fixtures/mail55.box') + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string) + assert_equal(ticket.id, ticket_p.id) + assert_equal('open', ticket_p.state.name) + assert_equal(5, ticket_p.articles.count) + travel_back + ticket.destroy + end + +end diff --git a/test/unit/email_process_bounce_follow_test.rb b/test/unit/email_process_bounce_follow_test.rb new file mode 100644 index 000000000..242c95845 --- /dev/null +++ b/test/unit/email_process_bounce_follow_test.rb @@ -0,0 +1,254 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessBounceFollowUpTest < ActiveSupport::TestCase + + test 'process with bounce follow up check' do + + ticket = Ticket.create( + title: 'bounce check', + group: Group.lookup(name: 'Users'), + customer_id: 2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check', + message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Customer').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + travel 1.second + email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box') + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string) + assert_equal(ticket.id, ticket_p.id) + assert_equal('new', ticket_p.state.name) + travel_back + ticket.destroy + + end + + test 'process with bounce trigger email loop check - article based blocker' do + roles = Role.where(name: %w(Customer)) + customer1 = User.create_or_update( + login: 'ticket-bounce-trigger1@example.com', + firstname: 'Notification', + lastname: 'Customer1', + email: 'ticket-bounce-trigger1@example.com', + active: true, + roles: roles, + preferences: {}, + updated_by_id: 1, + created_by_id: 1, + ) + + Trigger.create_or_update( + name: 'auto reply new ticket', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'create', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your inquiry (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + Trigger.create_or_update( + name: 'auto reply followup', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'update', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your follow up (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + + ticket = Ticket.create( + title: 'bounce check', + group: Group.lookup(name: 'Users'), + customer: customer1, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check', + message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: customer1.email, + subject: 'bounce check 2', + message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', + body: 'some message bounce check 2', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal(4, ticket.articles.count) + + travel 1.second + email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box') + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string) + assert_equal(ticket.id, ticket_p.id) + assert_equal('open', ticket_p.state.name) + assert_equal(5, ticket_p.articles.count) + travel_back + ticket.destroy + end + + test 'process with bounce trigger email loop check - bounce based blocker' do + roles = Role.where(name: %w(Customer)) + customer2 = User.create_or_update( + login: 'ticket-bounce-trigger2@example.com', + firstname: 'Notification', + lastname: 'Customer2', + email: 'ticket-bounce-trigger2@example.com', + active: true, + roles: roles, + preferences: {}, + updated_by_id: 1, + created_by_id: 1, + ) + + Trigger.create_or_update( + name: 'auto reply new ticket', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'create', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your inquiry (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + Trigger.create_or_update( + name: 'auto reply followup', + condition: { + 'ticket.action' => { + 'operator' => 'is', + 'value' => 'update', + }, + }, + perform: { + 'notification.email' => { + 'body' => 'some text
#{ticket.customer.lastname}
#{ticket.title}
#{article.body}', + 'recipient' => 'ticket_customer', + 'subject' => 'Thanks for your follow up (#{ticket.title})!', + }, + }, + disable_notification: true, + active: true, + created_by_id: 1, + updated_by_id: 1, + ) + + ticket = Ticket.create( + title: 'bounce check', + group: Group.lookup(name: 'Users'), + customer: customer2, + state: Ticket::State.lookup(name: 'new'), + priority: Ticket::Priority.lookup(name: '2 normal'), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check', + message_id: '<20150830145601.30.6088xx@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal('new', ticket.state.name) + assert_equal(2, ticket.articles.count) + + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check 2', + message_id: '<20170526150141.232.13312@example.zammad.loc>', + body: 'some message bounce check 2', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + Observer::Transaction.commit + assert_equal(4, ticket.articles.count) + + travel 1.second + email_raw_string = IO.binread('test/fixtures/mail55.box') + ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string) + assert_equal(ticket.id, ticket_p.id) + assert_equal('open', ticket_p.state.name) + assert_equal(5, ticket_p.articles.count) + travel_back + ticket.destroy + end + +end diff --git a/test/unit/email_process_bounce_test.rb b/test/unit/email_process_bounce_test.rb deleted file mode 100644 index 048f16d01..000000000 --- a/test/unit/email_process_bounce_test.rb +++ /dev/null @@ -1,39 +0,0 @@ -# encoding: utf-8 -require 'test_helper' - -class EmailProcessBounceTest < ActiveSupport::TestCase - - test 'process with bounce check' do - - ticket = Ticket.create( - title: 'bounce check', - group: Group.lookup( name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup( name: 'new' ), - priority: Ticket::Priority.lookup( name: '2 normal' ), - updated_by_id: 1, - created_by_id: 1, - ) - article = Ticket::Article.create( - ticket_id: ticket.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'bounce check', - message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', - body: 'some message bounce check', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Customer').first, - type: Ticket::Article::Type.where(name: 'email').first, - updated_by_id: 1, - created_by_id: 1, - ) - travel 1.second - email_raw_string = IO.binread('test/fixtures/mail33-undelivered-mail-returned-to-sender.box') - ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email_raw_string) - assert_equal(ticket.id, ticket_p.id) - assert_equal('new', ticket_p.state.name) - travel_back - ticket.destroy - end - -end