From 2cbf543103fab2d9f6b7ab85f4bc82792840e4c3 Mon Sep 17 00:00:00 2001 From: Marc Gariepy Date: Wed, 10 Apr 2019 11:27:21 -0400 Subject: [PATCH] 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 --- doc/source/user/wf_lang_v2.rst | 1 + mistral/actions/std_actions.py | 18 +++++++---- .../unit/actions/test_std_email_action.py | 32 ++++++++++++++++++- .../unit/services/test_action_manager.py | 2 +- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/doc/source/user/wf_lang_v2.rst b/doc/source/user/wf_lang_v2.rst index 8339468b6..3b17e4101 100644 --- a/doc/source/user/wf_lang_v2.rst +++ b/doc/source/user/wf_lang_v2.rst @@ -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*. diff --git a/mistral/actions/std_actions.py b/mistral/actions/std_actions.py index 2b1706722..04e6d7e3f 100644 --- a/mistral/actions/std_actions.py +++ b/mistral/actions/std_actions.py @@ -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 "" self.body = body or "" 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, diff --git a/mistral/tests/unit/actions/test_std_email_action.py b/mistral/tests/unit/actions/test_std_email_action.py index 020d22dce..2644e4028 100644 --- a/mistral/tests/unit/actions/test_std_email_action.py +++ b/mistral/tests/unit/actions/test_std_email_action.py @@ -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( diff --git a/mistral/tests/unit/services/test_action_manager.py b/mistral/tests/unit/services/test_action_manager.py index 198134238..501bfd336 100644 --- a/mistral/tests/unit/services/test_action_manager.py +++ b/mistral/tests/unit/services/test_action_manager.py @@ -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" )