Added bounce follow up feature (postmaster bounce emails will now issue an followup of origin ticket).
This commit is contained in:
parent
98cba4239c
commit
775c4f7d06
7 changed files with 314 additions and 37 deletions
|
@ -12,50 +12,50 @@ class Channel::EmailParser
|
|||
mail = parse( msg_as_string )
|
||||
|
||||
mail = {
|
||||
:from => 'Some Name <some@example.com>',
|
||||
:from_email => 'some@example.com',
|
||||
:from_local => 'some',
|
||||
:from_domain => 'example.com',
|
||||
:from_display_name => 'Some Name',
|
||||
:message_id => 'some_message_id@example.com',
|
||||
:to => 'Some System <system@example.com>',
|
||||
:cc => 'Somebody <somebody@example.com>',
|
||||
:subject => 'some message subject',
|
||||
:body => 'some message body',
|
||||
:attachments => [
|
||||
from: 'Some Name <some@example.com>',
|
||||
from_email: 'some@example.com',
|
||||
from_local: 'some',
|
||||
from_domain: 'example.com',
|
||||
from_display_name: 'Some Name',
|
||||
message_id: 'some_message_id@example.com',
|
||||
to: 'Some System <system@example.com>',
|
||||
cc: 'Somebody <somebody@example.com>',
|
||||
subject: 'some message subject',
|
||||
body: 'some message body',
|
||||
attachments: [
|
||||
{
|
||||
:data => 'binary of attachment',
|
||||
:filename => 'file_name_of_attachment.txt',
|
||||
:preferences => {
|
||||
:content-alternative => true,
|
||||
:Mime-Type => 'text/plain',
|
||||
:Charset => 'iso-8859-1',
|
||||
data: 'binary of attachment',
|
||||
filename: 'file_name_of_attachment.txt',
|
||||
preferences: {
|
||||
content-alternative: true,
|
||||
Mime-Type: 'text/plain',
|
||||
Charset: 'iso-8859-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
# ignore email header
|
||||
:x-zammad-ignore => 'false',
|
||||
x-zammad-ignore: 'false',
|
||||
|
||||
# customer headers
|
||||
:x-zammad-customer-login => '',
|
||||
:x-zammad-customer-email => '',
|
||||
:x-zammad-customer-firstname => '',
|
||||
:x-zammad-customer-lastname => '',
|
||||
x-zammad-customer-login: '',
|
||||
x-zammad-customer-email: '',
|
||||
x-zammad-customer-firstname: '',
|
||||
x-zammad-customer-lastname: '',
|
||||
|
||||
# ticket headers
|
||||
:x-zammad-ticket-group => 'some_group',
|
||||
:x-zammad-ticket-state => 'some_state',
|
||||
:x-zammad-ticket-priority => 'some_priority',
|
||||
:x-zammad-ticket-owner => 'some_owner_login',
|
||||
x-zammad-ticket-group: 'some_group',
|
||||
x-zammad-ticket-state: 'some_state',
|
||||
x-zammad-ticket-priority: 'some_priority',
|
||||
x-zammad-ticket-owner: 'some_owner_login',
|
||||
|
||||
# article headers
|
||||
:x-zammad-article-internal => false,
|
||||
:x-zammad-article-type => 'agent',
|
||||
:x-zammad-article-sender => 'customer',
|
||||
x-zammad-article-internal: false,
|
||||
x-zammad-article-type: 'agent',
|
||||
x-zammad-article-sender: 'customer',
|
||||
|
||||
# all other email headers
|
||||
:some-header => 'some_value',
|
||||
some-header: 'some_value',
|
||||
}
|
||||
|
||||
=end
|
||||
|
@ -243,6 +243,9 @@ class Channel::EmailParser
|
|||
data[:body].gsub!( /\r\n/, "\n" )
|
||||
data[:body].gsub!( /\r/, "\n" )
|
||||
|
||||
# remember original mail instance
|
||||
data[:mail_instance] = mail
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
|
@ -325,19 +328,32 @@ class Channel::EmailParser
|
|||
[attach]
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
parser = Channel::EmailParser.new
|
||||
ticket, article, user = parser.process(channel, email_raw_string)
|
||||
|
||||
retrns
|
||||
|
||||
[ticket, article, user]
|
||||
|
||||
=end
|
||||
|
||||
def process(channel, msg)
|
||||
mail = parse( msg )
|
||||
|
||||
# run postmaster pre filter
|
||||
filters = {
|
||||
'0010' => Channel::Filter::Trusted,
|
||||
'0100' => Channel::Filter::FollowUpCheck,
|
||||
'0900' => Channel::Filter::BounceCheck,
|
||||
'1000' => Channel::Filter::Database,
|
||||
}
|
||||
|
||||
# filter( channel, mail )
|
||||
filters.each {|_prio, backend|
|
||||
begin
|
||||
backend.run( channel, mail )
|
||||
backend.run(channel, mail)
|
||||
rescue => e
|
||||
Rails.logger.error "can't run postmaster pre filter #{backend}"
|
||||
Rails.logger.error e.inspect
|
||||
|
@ -393,8 +409,13 @@ class Channel::EmailParser
|
|||
# set current user
|
||||
UserInfo.current_user_id = user.id
|
||||
|
||||
# get ticket# from subject
|
||||
ticket = Ticket::Number.check( mail[:subject] )
|
||||
# get ticket# based on email headers
|
||||
if mail[ 'x-zammad-ticket-id'.to_sym ]
|
||||
ticket = Ticket.find_by( id: mail[ 'x-zammad-ticket-id'.to_sym ] )
|
||||
end
|
||||
if mail[ 'x-zammad-ticket-number'.to_sym ]
|
||||
ticket = Ticket.find_by( number: mail[ 'x-zammad-ticket-number'.to_sym ] )
|
||||
end
|
||||
|
||||
# set ticket state to open if not new
|
||||
if ticket
|
||||
|
|
27
app/models/channel/filter/bounce_check.rb
Normal file
27
app/models/channel/filter/bounce_check.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Channel::Filter::BounceCheck
|
||||
|
||||
def self.run( _channel, mail )
|
||||
|
||||
return if !mail[:mail_instance]
|
||||
return if !mail[:mail_instance].bounced?
|
||||
return if !mail[:attachments]
|
||||
return if mail[ 'x-zammad-ticket-id'.to_sym ]
|
||||
|
||||
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('id DESC').limit(1).first
|
||||
if article
|
||||
mail[ 'x-zammad-ticket-id'.to_sym ] = article.ticket_id
|
||||
break
|
||||
end
|
||||
}
|
||||
|
||||
end
|
||||
end
|
14
app/models/channel/filter/follow_up_check.rb
Normal file
14
app/models/channel/filter/follow_up_check.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Channel::Filter::FollowUpCheck
|
||||
|
||||
def self.run( _channel, mail )
|
||||
|
||||
return if mail[ 'x-zammad-ticket-id'.to_sym ]
|
||||
|
||||
# get ticket# from subject
|
||||
ticket = Ticket::Number.check( mail[:subject] )
|
||||
return if !ticket
|
||||
mail[ 'x-zammad-ticket-id'.to_sym ] = ticket.id
|
||||
end
|
||||
end
|
|
@ -13,8 +13,9 @@ class Ticket::Article < ApplicationModel
|
|||
belongs_to :created_by, class_name: 'User'
|
||||
belongs_to :updated_by, class_name: 'User'
|
||||
store :preferences
|
||||
before_create :check_subject
|
||||
before_update :check_subject
|
||||
before_create :check_subject, :check_message_id_md5
|
||||
before_update :check_subject, :check_message_id_md5
|
||||
|
||||
notify_clients_support
|
||||
|
||||
activity_stream_support ignore_attributes: {
|
||||
|
@ -31,13 +32,19 @@ class Ticket::Article < ApplicationModel
|
|||
|
||||
private
|
||||
|
||||
# strip not wanted chars
|
||||
def check_subject
|
||||
|
||||
return if !subject
|
||||
|
||||
subject.gsub!(/\s|\t|\r/, ' ')
|
||||
end
|
||||
|
||||
# fillup md5 of message id to search easier on very long message ids
|
||||
def check_message_id_md5
|
||||
return if !message_id
|
||||
return if message_id_md5
|
||||
self.message_id_md5 = Digest::MD5.hexdigest(message_id)
|
||||
end
|
||||
|
||||
class Flag < ApplicationModel
|
||||
end
|
||||
|
||||
|
|
10
db/migrate/20150830000001_update_message_id_md5.rb
Normal file
10
db/migrate/20150830000001_update_message_id_md5.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class UpdateMessageIdMd5 < ActiveRecord::Migration
|
||||
def up
|
||||
Ticket::Article.all.each {|article|
|
||||
next if !article.message_id
|
||||
next if article.message_id_md5
|
||||
message_id_md5 = Digest::MD5.hexdigest(article.message_id)
|
||||
article.update_columns({ message_id_md5: message_id_md5 })
|
||||
}
|
||||
end
|
||||
end
|
168
test/fixtures/mail33-undelivered-mail-returned-to-sender.box
vendored
Normal file
168
test/fixtures/mail33-undelivered-mail-returned-to-sender.box
vendored
Normal file
|
@ -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: <20150830145601.30.608881@edenhofer.zammad.com>
|
||||
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 & 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--
|
|
@ -2023,6 +2023,36 @@ Some Text',
|
|||
process(files)
|
||||
end
|
||||
|
||||
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: 'some subject',
|
||||
message_id: '<20150830145601.30.608881@edenhofer.zammad.com>',
|
||||
body: 'some message article',
|
||||
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,
|
||||
)
|
||||
|
||||
email_raw_string = IO.read('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_p.id, ticket.id)
|
||||
end
|
||||
|
||||
test 'process with postmaster filter' do
|
||||
group1 = Group.create_if_not_exists(
|
||||
name: 'Test Group1',
|
||||
|
|
Loading…
Reference in a new issue