Add reply-to to std.email

Reply to address is useful when sending email from an unmonitored email
address and to give user a place to respond in order to contact us.

Add Reply-to as described in section 3.6.2 of RFC5222
https://tools.ietf.org/html/rfc5322#section-3.6.2

Change-Id: Ib6b2bdc130e4f9e5170eb88760d69c3e08d2a1c7
This commit is contained in:
Marc Gariepy 2019-04-10 11:27:21 -04:00 committed by Renat Akhmerov
parent 1c1923746e
commit 2cbf543103
4 changed files with 44 additions and 9 deletions

View File

@ -1058,6 +1058,7 @@ Sends an email message via SMTP protocol.
- **to_addrs** - Comma separated list of recipients. *Required*.
- **cc_addrs** - Comma separated list of CC recipients. *Optional*.
- **bcc_addrs** - Comma separated list of BCC recipients. *Optional*.
- **reply_to** - Comma separated list of email address. *Optional*.
- **subject** - Subject of the message. *Optional*.
- **body** - Text containing message body. *Optional*.
- **html_body** - Text containing the message in HTML format. *Optional*.

View File

@ -278,9 +278,9 @@ class MistralHTTPAction(HTTPAction):
class SendEmailAction(actions.Action):
def __init__(self, from_addr, to_addrs, smtp_server, cc_addrs=None,
bcc_addrs=None, smtp_password=None, subject=None, body=None,
html_body=None):
def __init__(self, from_addr, to_addrs, smtp_server, reply_to=None,
cc_addrs=None, bcc_addrs=None, smtp_password=None,
subject=None, body=None, html_body=None):
super(SendEmailAction, self).__init__()
# TODO(dzimine): validate parameters
@ -288,6 +288,7 @@ class SendEmailAction(actions.Action):
self.to = to_addrs
self.cc = cc_addrs or []
self.bcc = bcc_addrs or []
self.reply_to = reply_to or []
self.subject = subject or "<No subject>"
self.body = body or "<No body>"
self.html_body = html_body
@ -300,10 +301,11 @@ class SendEmailAction(actions.Action):
def run(self, context):
LOG.info(
"Sending email message "
"[from=%s, to=%s, cc=%s, bcc=%s, subject=%s, using smtp=%s, "
"body=%s...]",
"[from=%s, to=%s, reply_to=%s, cc=%s, bcc=%s, subject=%s, "
"using smtp=%s, body=%s...]",
self.sender,
self.to,
self.reply_to,
self.cc,
self.bcc,
self.subject,
@ -322,6 +324,7 @@ class SendEmailAction(actions.Action):
_charset='utf-8'))
message['Subject'] = header.Header(self.subject, 'utf-8')
message['From'] = self.sender
message['Reply-To'] = header.Header(', '.join(self.reply_to))
message['To'] = ', '.join(self.to)
if self.cc:
@ -351,10 +354,11 @@ class SendEmailAction(actions.Action):
# to return a result.
LOG.info(
"Sending email message "
"[from=%s, to=%s, cc=%s, bcc=%s, subject=%s, using smtp=%s, "
"body=%s...]",
"[from=%s, to=%s, reply_to=%s, cc=%s, bcc=%s, subject=%s, "
" using smtp=%s, body=%s...]",
self.sender,
self.to,
self.reply_to,
self.cc,
self.bcc,
self.subject,

View File

@ -54,6 +54,7 @@ class SendEmailActionTest(base.BaseTest):
super(SendEmailActionTest, self).setUp()
self.to_addrs = ["dz@example.com", "deg@example.com",
"xyz@example.com"]
self.reply_to = ['reply-to@example.com']
self.cc_addrs = ['copy@example.com']
self.bcc_addrs = ['hidden_copy@example.com']
self.subject = "Multi word subject с русскими буквами"
@ -64,6 +65,7 @@ class SendEmailActionTest(base.BaseTest):
self.from_addr = "bot@example.com"
self.to_addrs_str = ", ".join(self.to_addrs)
self.reply_to_str = ", ".join(self.reply_to)
self.ctx = mock.Mock()
@testtools.skipIf(not LOCAL_SMTPD, "Setup local smtpd to run it")
@ -84,7 +86,6 @@ class SendEmailActionTest(base.BaseTest):
self.smtp_server = 'mail.example.com:25'
self.from_addr = "bot@example.com"
self.smtp_password = 'secret'
action = std.SendEmailAction(
from_addr=self.from_addr,
to_addrs=self.to_addrs,
@ -235,6 +236,35 @@ class SendEmailActionTest(base.BaseTest):
self.assertEqual(self.from_addr, message['from'])
self.assertEqual(self.to_addrs_str, message['to'])
@mock.patch('smtplib.SMTP')
def test_send_email_with_reply_to(self, smtp):
action = std.SendEmailAction(
from_addr=self.from_addr,
to_addrs=self.to_addrs,
reply_to=self.reply_to,
bcc_addrs=self.bcc_addrs,
smtp_server=self.smtp_server,
smtp_password=None,
subject=self.subject,
body=self.body
)
action.run(self.ctx)
smtp.assert_called_once_with(self.smtp_server)
sendmail = smtp.return_value.sendmail
self.assertTrue(sendmail.called, "should call sendmail")
self.assertEqual(
self.from_addr, sendmail.call_args[1]['from_addr'])
message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
self.assertEqual(self.from_addr, message['from'])
self.assertEqual(self.to_addrs_str, message['to'])
self.assertEqual(self.reply_to_str, message['reply-to'])
@mock.patch('smtplib.SMTP')
def test_send_email_html(self, smtp):
action = std.SendEmailAction(

View File

@ -31,7 +31,7 @@ class ActionManagerTest(base.DbTestCase):
self.assertEqual(http_action_input, std_http.input)
std_email_input = (
"from_addr, to_addrs, smtp_server, cc_addrs=null, "
"from_addr, to_addrs, smtp_server, reply_to=null, cc_addrs=null, "
"bcc_addrs=null, smtp_password=null, subject=null, body=null, "
"html_body=null"
)