Implemented reply_to header as sender/customer feature.

This commit is contained in:
Martin Edenhofer 2017-05-26 15:34:32 +02:00
parent e9efae4b83
commit 79c398becb
5 changed files with 254 additions and 26 deletions

View file

@ -117,32 +117,7 @@ class Channel::EmailParser
}
# set extra headers
begin
data[:from_email] = Mail::Address.new(from).address
data[:from_local] = Mail::Address.new(from).local
data[:from_domain] = Mail::Address.new(from).domain
data[:from_display_name] = Mail::Address.new(from).display_name ||
(Mail::Address.new(from).comments && Mail::Address.new(from).comments[0])
rescue
from.strip!
if from =~ /^(.+?)<(.+?)@(.+?)>$/
data[:from_email] = "#{$2}@#{$3}"
data[:from_local] = $2
data[:from_domain] = $3
data[:from_display_name] = $1
else
data[:from_email] = from
data[:from_local] = from
data[:from_domain] = from
end
end
# do extra decoding because we needed to use field.value
data[:from_display_name] = Mail::Field.new('X-From', data[:from_display_name]).to_s
data[:from_display_name].delete!('"')
data[:from_display_name].strip!
data[:from_display_name].gsub!(/^'/, '')
data[:from_display_name].gsub!(/'$/, '')
data = data.merge(Channel::EmailParser.sender_properties(from))
# do extra encoding (see issue#1045)
if data[:subject].present?
@ -638,6 +613,39 @@ returns
true
end
def self.sender_properties(from)
data = {}
begin
data[:from_email] = Mail::Address.new(from).address
data[:from_local] = Mail::Address.new(from).local
data[:from_domain] = Mail::Address.new(from).domain
data[:from_display_name] = Mail::Address.new(from).display_name ||
(Mail::Address.new(from).comments && Mail::Address.new(from).comments[0])
rescue
from.strip!
if from =~ /^(.+?)<(.+?)@(.+?)>$/
data[:from_email] = "#{$2}@#{$3}"
data[:from_local] = $2
data[:from_domain] = $3
data[:from_display_name] = $1
else
data[:from_email] = from
data[:from_local] = from
data[:from_domain] = from
end
end
# do extra decoding because we needed to use field.value
data[:from_display_name] = Mail::Field.new('X-From', data[:from_display_name]).to_s
data[:from_display_name].delete!('"')
data[:from_display_name].strip!
data[:from_display_name].gsub!(/^'/, '')
data[:from_display_name].gsub!(/'$/, '')
data
end
def set_attributes_by_x_headers(item_object, header_name, mail, suffix = false)
# loop all x-zammad-hedaer-* headers

View file

@ -0,0 +1,36 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
module Channel::Filter::ReplyToBasedSender
def self.run(_channel, mail)
reply_to = mail['reply-to'.to_sym]
return if reply_to.blank?
setting = Setting.get('postmaster_sender_based_on_reply_to')
return if setting.blank?
# get properties of reply-to header
result = Channel::EmailParser.sender_properties(reply_to)
if setting == 'as_sender_of_email'
mail[:from] = reply_to
mail[:from_email] = result[:from_email]
mail[:from_local] = result[:from_local]
mail[:from_domain] = result[:from_domain]
mail[:from_display_name] = result[:from_display_name]
return
end
if setting == 'as_sender_of_email_use_from_realname'
mail[:from] = reply_to
mail[:from_email] = result[:from_email]
mail[:from_local] = result[:from_local]
mail[:from_domain] = result[:from_domain]
return
end
Rails.logger.error "Invalid setting value for 'postmaster_sender_based_on_reply_to' -> #{setting.inspect}"
end
end

View file

@ -0,0 +1,45 @@
class ReplyToSenderFeature < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
Setting.create_if_not_exists(
title: 'Sender based on Reply-To header',
name: 'postmaster_sender_based_on_reply_to',
area: 'Email::Base',
description: 'Set/overwrite sender/from of email based on reply-to header. Useful to set correct customer if email is received from a third party system on behalf of a customer.',
options: {
form: [
{
display: '',
null: true,
name: 'postmaster_sender_based_on_reply_to',
tag: 'select',
options: {
'' => '-',
'as_sender_of_email' => 'Take reply-to header as sender/from of email.',
'as_sender_of_email_use_from_realname' => 'Take reply-to header as sender/from of email and use realname of origin from.',
},
},
],
},
state: [],
preferences: {
permission: ['admin.channel_email'],
},
frontend: false
)
Setting.create_if_not_exists(
title: 'Defines postmaster filter.',
name: '0011_postmaster_sender_based_on_reply_to',
area: 'Postmaster::PreFilter',
description: 'Defines postmaster filter to set the sender/from of emails based on reply-to header.',
options: {},
state: 'Channel::Filter::ReplyToBasedSender',
frontend: false
)
end
end

View file

@ -1657,6 +1657,33 @@ Setting.create_if_not_exists(
frontend: false
)
Setting.create_if_not_exists(
title: 'Sender based on Reply-To header',
name: 'postmaster_sender_based_on_reply_to',
area: 'Email::Base',
description: 'Set/overwrite sender/from of email based on reply-to header. Useful to set correct customer if email is received from a third party system on behalf of a customer.',
options: {
form: [
{
display: '',
null: true,
name: 'postmaster_sender_based_on_reply_to',
tag: 'select',
options: {
'' => '-',
'as_sender_of_email' => 'Take reply-to header as sender/from of email.',
'as_sender_of_email_use_from_realname' => 'Take reply-to header as sender/from of email and use realname of origin from.',
},
},
],
},
state: [],
preferences: {
permission: ['admin.channel_email'],
},
frontend: false
)
Setting.create_if_not_exists(
title: 'Notification Sender',
name: 'notification_sender',
@ -2217,6 +2244,15 @@ Setting.create_if_not_exists(
state: 'Channel::Filter::Trusted',
frontend: false
)
Setting.create_if_not_exists(
title: 'Defines postmaster filter.',
name: '0011_postmaster_sender_based_on_reply_to',
area: 'Postmaster::PreFilter',
description: 'Defines postmaster filter to set the sender/from of emails based on reply-to header.',
options: {},
state: 'Channel::Filter::ReplyToBasedSender',
frontend: false
)
Setting.create_if_not_exists(
title: 'Defines postmaster filter.',
name: '0012_postmaster_filter_sender_is_system_address',

View file

@ -0,0 +1,103 @@
# encoding: utf-8
require 'test_helper'
class EmailProcessReplyToTest < ActiveSupport::TestCase
test 'normal processing' do
setting_orig = Setting.get('postmaster_sender_based_on_reply_to')
Setting.set('postmaster_sender_based_on_reply_to', '')
email = "From: Bob Smith <marketing_tool@example.com>
To: zammad@example.com
Subject: some new subject
Reply-To: replay_to_customer_process1@example.com
Some Text"
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email)
assert_equal('Bob Smith <marketing_tool@example.com>', article_p.from)
assert_equal('replay_to_customer_process1@example.com', article_p.reply_to)
assert_equal('marketing_tool@example.com', ticket_p.customer.email)
assert_equal('Bob', ticket_p.customer.firstname)
assert_equal('Smith', ticket_p.customer.lastname)
Setting.set('postmaster_sender_based_on_reply_to', setting_orig)
end
test 'normal processing - take reply to as customer' do
setting_orig = Setting.get('postmaster_sender_based_on_reply_to')
Setting.set('postmaster_sender_based_on_reply_to', 'as_sender_of_email')
email = "From: Bob Smith <marketing_tool@example.com>
To: zammad@example.com
Subject: some new subject
Reply-To: replay_to_customer_process2@example.com
Some Text"
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email)
assert_equal('replay_to_customer_process2@example.com', article_p.from)
assert_equal('replay_to_customer_process2@example.com', article_p.reply_to)
assert_equal('replay_to_customer_process2@example.com', ticket_p.customer.email)
assert_equal('', ticket_p.customer.firstname)
assert_equal('', ticket_p.customer.lastname)
email = "From: Bob Smith <marketing_tool@example.com>
To: zammad@example.com
Subject: some new subject
Reply-To: Some Name <replay_to_customer_process2-1@example.com>
Some Text"
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email)
assert_equal('Some Name <replay_to_customer_process2-1@example.com>', article_p.from)
assert_equal('Some Name <replay_to_customer_process2-1@example.com>', article_p.reply_to)
assert_equal('replay_to_customer_process2-1@example.com', ticket_p.customer.email)
assert_equal('Some', ticket_p.customer.firstname)
assert_equal('Name', ticket_p.customer.lastname)
Setting.set('postmaster_sender_based_on_reply_to', setting_orig)
end
test 'normal processing - take reply to as customer and use from as realname' do
setting_orig = Setting.get('postmaster_sender_based_on_reply_to')
Setting.set('postmaster_sender_based_on_reply_to', 'as_sender_of_email_use_from_realname')
email = "From: Bob Smith <marketing_tool@example.com>
To: zammad@example.com
Subject: some new subject
Reply-To: replay_to_customer_process3@example.com
Some Text"
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email)
assert_equal('replay_to_customer_process3@example.com', article_p.from)
assert_equal('replay_to_customer_process3@example.com', article_p.reply_to)
assert_equal('replay_to_customer_process3@example.com', ticket_p.customer.email)
assert_equal('Bob', ticket_p.customer.firstname)
assert_equal('Smith', ticket_p.customer.lastname)
email = "From: Bob Smith <marketing_tool@example.com>
To: zammad@example.com
Subject: some new subject
Reply-To: Some Name <replay_to_customer_process3-1@example.com>
Some Text"
ticket_p, article_p, user_p = Channel::EmailParser.new.process({}, email)
assert_equal('Some Name <replay_to_customer_process3-1@example.com>', article_p.from)
assert_equal('Some Name <replay_to_customer_process3-1@example.com>', article_p.reply_to)
assert_equal('replay_to_customer_process3-1@example.com', ticket_p.customer.email)
assert_equal('Bob', ticket_p.customer.firstname)
assert_equal('Smith', ticket_p.customer.lastname)
Setting.set('postmaster_sender_based_on_reply_to', setting_orig)
end
end