Added out of office detection and auto responder detection.

This commit is contained in:
Martin Edenhofer 2015-09-20 02:16:22 +02:00
parent edab5b9197
commit 5d871673be
10 changed files with 369 additions and 266 deletions

View file

@ -29,6 +29,7 @@ module Channel::EmailBuild
attr['X-Loop'] = 'yes'
attr['Precedence'] = 'bulk'
attr['Auto-Submitted'] = 'auto-generated'
attr['X-Auto-Response-Suppress'] = 'All'
end
#attr['X-Powered-BY'] = 'Zammad - Support/Helpdesk (http://www.zammad.org/)'

View file

@ -345,6 +345,8 @@ retrns
# run postmaster pre filter
filters = {
'0010' => Channel::Filter::Trusted,
'0020' => Channel::Filter::AutoResponseCheck,
'0030' => Channel::Filter::OutOfOfficeCheck,
'0100' => Channel::Filter::FollowUpCheck,
'0900' => Channel::Filter::BounceCheck,
'1000' => Channel::Filter::Database,
@ -427,7 +429,8 @@ retrns
end
if state_type.name != 'new'
# set ticket to open again
if state_type.name != 'new' && !mail[ 'x-zammad-out-of-office'.to_sym ]
ticket.state = Ticket::State.find_by(name: 'open')
ticket.save
end
@ -515,7 +518,7 @@ retrns
}
# return new objects
[ticket, article, user]
[ticket, article, user, mail]
end
def user_create(data)

View file

@ -0,0 +1,18 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
module Channel::Filter::AutoResponseCheck
def self.run( _channel, mail )
# if header is available, do not generate auto response
mail[ 'x-zammad-send-auto-response'.to_sym ] = false
return if mail[ 'x-loop'.to_sym ] && mail[ 'x-loop'.to_sym ] =~ /(yes|true)/i
return if mail[ 'precedence'.to_sym ] && mail[ 'precedence'.to_sym ] =~ /bulk/i
return if mail[ 'auto-submitted'.to_sym ] && mail[ 'auto-submitted'.to_sym ] =~ /auto-(generated|replied)/i
return if mail[ 'x-auto-response-suppress'.to_sym ] && mail[ 'x-auto-response-suppress'.to_sym ] =~ /all/i
mail[ 'x-zammad-send-auto-response'.to_sym ] = true
end
end

View file

@ -0,0 +1,17 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
module Channel::Filter::OutOfOfficeCheck
def self.run( _channel, mail )
mail[ 'x-zammad-out-of-office'.to_sym ] = false
# check ms out of office characteristics
return if !mail[ 'x-auto-response-suppress'.to_sym ]
return if mail[ 'x-auto-response-suppress'.to_sym ] !~ /all/i
return if !mail[ 'x-ms-exchange-inbox-rules-loop'.to_sym ]
mail[ 'x-zammad-out-of-office'.to_sym ] = true
end
end

View file

@ -302,6 +302,7 @@ returns on fail
mail['X-Loop'] = 'yes'
mail['Precedence'] = 'bulk'
mail['Auto-Submitted'] = 'auto-generated'
mail['X-Auto-Response-Suppress'] = 'All'
# test connection
begin

View file

@ -0,0 +1,60 @@
# encoding: utf-8
require 'test_helper'
class EmailProcessAutoResponseTest < ActiveSupport::TestCase
test 'process with out of office check' do
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: some new subject
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(true, mail['x-zammad-send-auto-response'.to_sym])
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: some new subject
X-Loop: yes
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: some new subject
Precedence: Bulk
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: some new subject
Auto-Submitted: auto-generated
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: some new subject
X-Auto-Response-Suppress: All
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(false, mail['x-zammad-send-auto-response'.to_sym])
end
end

View file

@ -0,0 +1,37 @@
# 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: 'Agent').first,
type: Ticket::Article::Type.where(name: 'email').first,
updated_by_id: 1,
created_by_id: 1,
)
sleep 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.id, ticket_p.id)
assert_equal('new', ticket_p.state.name)
end
end

View file

@ -0,0 +1,130 @@
# encoding: utf-8
require 'test_helper'
class EmailProcessFollowUpTest < ActiveSupport::TestCase
test 'process with follow up check' do
ticket = Ticket.create(
title: 'follow up 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: 'follow up check',
message_id: '<20150830145601.30.608882@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_subject = "From: me@example.com
To: customer@example.com
Subject: #{ticket.subject_build('some new subject')}
Some Text"
email_raw_string_body = "From: me@example.com
To: customer@example.com
Subject: no reference
Some Text #{ticket.subject_build('some new subject')} "
email_raw_string_attachment = "From: me@example.com
Content-Type: multipart/mixed; boundary=\"Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2\"
Subject: no reference
Date: Sun, 30 Aug 2015 23:20:54 +0200
To: Martin Edenhofer <me@znuny.com>
Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\))
X-Mailer: Apple Mail (2.2104)
--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=us-ascii
no reference
--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
Content-Disposition: attachment;
filename=test1.txt
Content-Type: text/plain;
name=\"test.txt\"
Content-Transfer-Encoding: 7bit
Some Text #{ticket.subject_build('some new subject')}
--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--"
email_raw_string_references1 = "From: me@example.com
To: customer@example.com
Subject: no reference
In-Reply-To: <20150830145601.30.608882@edenhofer.zammad.com>
References: <DA918CD1-BE9A-4262-ACF6-5001E59291B6@znuny.com>
no reference "
email_raw_string_references2 = "From: me@example.com
To: customer@example.com
Subject: no reference
References: <DA918CD1-BE9A-4262-ACF6-5001E59291B6@znuny.com> <20150830145601.30.608882@edenhofer.zammad.com> <DA918CD1-BE9A-4262-ACF6-5001E59291XX@znuny.com>
no reference "
setting_orig = Setting.get('postmaster_follow_up_search_in')
Setting.set('postmaster_follow_up_search_in', %w(body attachment references))
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2)
assert_equal(ticket.id, ticket_p.id)
Setting.set('postmaster_follow_up_search_in', setting_orig)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body)
assert_not_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment)
assert_not_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1)
assert_not_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2)
assert_not_equal(ticket.id, ticket_p.id)
end
end

View file

@ -0,0 +1,92 @@
# encoding: utf-8
require 'test_helper'
class EmailProcessOutOfOfficeTest < ActiveSupport::TestCase
test 'process with out of office check' do
ticket = Ticket.create(
title: 'ooo check',
group: Group.lookup( name: 'Users'),
customer_id: 2,
state: Ticket::State.lookup( name: 'closed' ),
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: 'ooo check',
message_id: '<20150830145601.30.608881@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,
)
sleep 1
# exchange out of office example #1
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: #{ticket.subject_build('some new subject 1')}
X-MS-Has-Attach:
X-Auto-Response-Suppress: All
X-MS-Exchange-Inbox-Rules-Loop: aaa.bbb@example.com
X-MS-TNEF-Correlator:
x-olx-disclaimer: Done
x-tm-as-product-ver: SMEX-11.0.0.4179-8.000.1202-21706.006
x-tm-as-result: No--39.689200-0.000000-31
x-tm-as-user-approved-sender: Yes
x-tm-as-user-blocked-sender: No
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(true, mail['x-zammad-out-of-office'.to_sym])
ticket = Ticket.find(ticket.id)
assert_equal(ticket.id, ticket_p.id)
assert_equal('closed', ticket.state.name)
# normal follow up
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: #{ticket.subject_build('some new subject - none')}
X-MS-Exchange-Inbox-Rules-Loop: aaa.bbb@example.com
Some Text 2"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(false, mail['x-zammad-out-of-office'.to_sym])
ticket = Ticket.find(ticket.id)
assert_equal(ticket.id, ticket_p.id)
assert_equal('open', ticket_p.state.name)
ticket = Ticket.find(ticket.id)
ticket.state = Ticket::State.lookup(name: 'closed')
ticket.save
# exchange out of office example #2
email_raw_string = "From: me@example.com
To: customer@example.com
Subject: #{ticket.subject_build('some new subject 2')}
X-MS-Has-Attach:
X-Auto-Response-Suppress: All
X-MS-Exchange-Inbox-Rules-Loop: aaa.bbb@example.com
X-MS-TNEF-Correlator:
x-exclaimer-md-config: 8c10826d-4052-4c5c-a8e8-e09011276827
Some Text"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string)
assert_equal(true, mail['x-zammad-out-of-office'.to_sym])
ticket = Ticket.find(ticket.id)
assert_equal(ticket.id, ticket_p.id)
assert_equal('closed', ticket.state.name)
end
end

View file

@ -2026,262 +2026,6 @@ 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: 'bounce check',
message_id: '<20150830145601.30.608881@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,
)
sleep 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.id, ticket_p.id)
end
test 'process with follow up check' do
ticket = Ticket.create(
title: 'follow up 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: 'follow up check',
message_id: '<20150830145601.30.608882@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_subject = "From: me@example.com
To: customer@example.com
Subject: #{ticket.subject_build('some new subject')}
Some Text"
email_raw_string_body = "From: me@example.com
To: customer@example.com
Subject: no reference
Some Text #{ticket.subject_build('some new subject')} "
email_raw_string_attachment = "From: me@example.com
Content-Type: multipart/mixed; boundary=\"Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2\"
Subject: no reference
Date: Sun, 30 Aug 2015 23:20:54 +0200
To: Martin Edenhofer <me@znuny.com>
Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\))
X-Mailer: Apple Mail (2.2104)
--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=us-ascii
no reference
--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
Content-Disposition: attachment;
filename=test1.txt
Content-Type: text/plain;
name=\"test.txt\"
Content-Transfer-Encoding: 7bit
Some Text #{ticket.subject_build('some new subject')}
--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--"
email_raw_string_references1 = "From: me@example.com
To: customer@example.com
Subject: no reference
In-Reply-To: <20150830145601.30.608882@edenhofer.zammad.com>
References: <DA918CD1-BE9A-4262-ACF6-5001E59291B6@znuny.com>
no reference "
email_raw_string_references2 = "From: me@example.com
To: customer@example.com
Subject: no reference
References: <DA918CD1-BE9A-4262-ACF6-5001E59291B6@znuny.com> <20150830145601.30.608882@edenhofer.zammad.com> <DA918CD1-BE9A-4262-ACF6-5001E59291XX@znuny.com>
no reference "
setting_orig = Setting.get('postmaster_follow_up_search_in')
Setting.set('postmaster_follow_up_search_in', ['body', 'attachment', 'references'])
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2)
assert_equal(ticket.id, ticket_p.id)
Setting.set('postmaster_follow_up_search_in', setting_orig)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject)
assert_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body)
assert_not_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment)
assert_not_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1)
assert_not_equal(ticket.id, ticket_p.id)
sleep 1
ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2)
assert_not_equal(ticket.id, ticket_p.id)
end
test 'process with postmaster filter' do
group1 = Group.create_if_not_exists(
name: 'Test Group1',
created_by_id: 1,
updated_by_id: 1,
)
group2 = Group.create_if_not_exists(
name: 'Test Group2',
created_by_id: 1,
updated_by_id: 1,
)
PostmasterFilter.destroy_all
PostmasterFilter.create(
name: 'not used',
match: {
from: 'nobody@example.com',
},
perform: {
'X-Zammad-Ticket-priority' => '3 high',
},
channel: 'email',
active: true,
created_by_id: 1,
updated_by_id: 1,
)
PostmasterFilter.create(
name: 'used',
match: {
from: 'me@example.com',
},
perform: {
'X-Zammad-Ticket-group_id' => group1.id,
'x-Zammad-Article-Internal' => true,
},
channel: 'email',
active: true,
created_by_id: 1,
updated_by_id: 1,
)
PostmasterFilter.create(
name: 'used x-any-recipient',
match: {
'x-any-recipient' => 'any@example.com',
},
perform: {
'X-Zammad-Ticket-group_id' => group2.id,
'x-Zammad-Article-Internal' => true,
},
channel: 'email',
active: true,
created_by_id: 1,
updated_by_id: 1,
)
files = [
{
data: 'From: me@example.com
To: customer@example.com
Subject: some subject
Some Text',
trusted: false,
success: true,
result: {
0 => {
group: group1.name,
priority: '2 normal',
title: 'some subject',
},
1 => {
sender: 'Customer',
type: 'email',
internal: true,
},
},
},
{
data: 'From: Some Body <somebody@example.com>
To: Bob <bod@example.com>
Cc: any@example.com
Subject: some subject
Some Text',
trusted: false,
success: true,
result: {
0 => {
group: group2.name,
priority: '2 normal',
title: 'some subject',
},
1 => {
sender: 'Customer',
type: 'email',
internal: true,
},
},
},
]
process(files)
PostmasterFilter.destroy_all
end
def process(files)
files.each { |file|
parser = Channel::EmailParser.new