Skip to content

Commit

Permalink
Amazon SES: fix bcc
Browse files Browse the repository at this point in the history
Set SendRawEmail Destinations param to pick up all
recipients, including bcc (which doesn't appear in
message headers).

Fixes #189
  • Loading branch information
medmunds committed Jul 22, 2020
1 parent c974c1e commit 3579235
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 6 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ Release history
^^^^^^^^^^^^^^^
.. This extra heading level keeps the ToC from becoming unmanageably long
vNext
-----

*Unreleased changes in master*

Fixes
~~~~~

* **Amazon SES:** Fix bcc, which wasn't working at all on non-template sends.
(Thanks to `@mwheels`_ for reporting the issue.)



v7.1
-----

Expand Down Expand Up @@ -1064,6 +1077,7 @@ Features
.. _@Lekensteyn: https://github.com/Lekensteyn
.. _@lewistaylor: https://github.com/lewistaylor
.. _@mbk-ok: https://github.com/mbk-ok
.. _@mwheels: https://github.com/mwheels
.. _@nuschk: https://github.com/nuschk
.. _@RignonNoel: https://github.com/RignonNoel
.. _@sebashwa: https://github.com/sebashwa
Expand Down
16 changes: 12 additions & 4 deletions anymail/backends/amazon_ses.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,11 @@ def init_payload(self):
MIMEText.set_payload(part, content, charset=qp_charset)

def call_send_api(self, ses_client):
# Set Destinations to make sure we pick up all recipients (including bcc).
# Any non-ASCII characters in recipient domains must be encoded with Punycode.
# (Amazon SES doesn't support non-ASCII recipient usernames.)
self.params["Destinations"] = [email.address for email in self.all_recipients]
self.params["RawMessage"] = {
# Note: "Destinations" is determined from message headers if not provided
# "Destinations": [email.addr_spec for email in self.all_recipients],
"Data": self.mime_message.as_bytes()
}
return ses_client.send_raw_email(**self.params)
Expand Down Expand Up @@ -225,8 +227,14 @@ def set_envelope_sender(self, email):
def set_spoofed_to_header(self, header_to):
# django.core.mail.EmailMessage.message() has already set
# self.mime_message["To"] = header_to
# and performed any necessary header sanitization
self.params["Destinations"] = [email.addr_spec for email in self.all_recipients]
# and performed any necessary header sanitization.
#
# The actual "to" is already in self.all_recipients,
# which is used as the SendRawEmail Destinations later.
#
# So, nothing to do here, except prevent the default
# "unsupported feature" error.
pass

def set_metadata(self, metadata):
# Amazon SES has two mechanisms for adding custom data to a message:
Expand Down
24 changes: 22 additions & 2 deletions tests/test_amazon_ses_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,29 @@ def test_send_mail(self):
self.assertIn(b"\nTo: to@example.com\n", raw_mime)
self.assertIn(b"\nSubject: Subject here\n", raw_mime)
self.assertIn(b"\n\nHere is the message", raw_mime)
# Destinations must include all recipients:
self.assertEqual(params['Destinations'], ['to@example.com'])

# Since the SES backend generates the MIME message using Django's
# EmailMessage.message().to_string(), there's not really a need
# to exhaustively test all the various standard email features.
# (EmailMessage.message() is well tested in the Django codebase.)
# Instead, just spot-check a few things...

def test_destinations(self):
self.message.to = ['to1@example.com', '"Recipient, second" <to2@example.com>']
self.message.cc = ['cc1@example.com', 'Also cc <cc2@example.com>']
self.message.bcc = ['bcc1@example.com', 'BCC 2 <bcc2@example.com>']
self.message.send()
params = self.get_send_params()
self.assertEqual(params['Destinations'], [
'to1@example.com', '"Recipient, second" <to2@example.com>',
'cc1@example.com', 'Also cc <cc2@example.com>',
'bcc1@example.com', 'BCC 2 <bcc2@example.com>',
])
# Bcc's shouldn't appear in the message itself:
self.assertNotIn(b'bcc', params['RawMessage']['Data'])

def test_non_ascii_headers(self):
self.message.subject = "Thử tin nhắn" # utf-8 in subject header
self.message.to = ['"Người nhận" <to@example.com>'] # utf-8 in display name
Expand All @@ -149,6 +165,11 @@ def test_non_ascii_headers(self):
self.assertIn(b"\nCc: cc@xn--th-e0a.example.com\n", raw_mime)
# SES doesn't support non-ASCII in the username@ part (RFC 6531 "SMTPUTF8" extension)

# Destinations must include all recipients:
self.assertEqual(params['Destinations'], [
'=?utf-8?b?TmfGsOG7nWkgbmjhuq1u?= <to@example.com>',
'cc@xn--th-e0a.example.com'])

def test_attachments(self):
text_content = "• Item one\n• Item two\n• Item three" # those are \u2022 bullets ("\N{BULLET}")
self.message.attach(filename="Une pièce jointe.txt", # utf-8 chars in filename
Expand Down Expand Up @@ -336,7 +357,7 @@ def test_spoofed_to(self):
self.message.send()
params = self.get_send_params()
raw_mime = params['RawMessage']['Data']
self.assertEqual(params['Destinations'], ["envelope-to@example.com"])
self.assertEqual(params['Destinations'], ["Envelope <envelope-to@example.com>"])
self.assertIn(b"\nTo: Spoofed <spoofed-to@elsewhere.example.org>\n", raw_mime)
self.assertNotIn(b"envelope-to@example.com", raw_mime)

Expand Down Expand Up @@ -533,7 +554,6 @@ def test_default_omits_options(self):
self.assertNotIn('ConfigurationSetName', params)
self.assertNotIn('DefaultTags', params)
self.assertNotIn('DefaultTemplateData', params)
self.assertNotIn('Destinations', params)
self.assertNotIn('FromArn', params)
self.assertNotIn('Message', params)
self.assertNotIn('ReplyToAddresses', params)
Expand Down

0 comments on commit 3579235

Please sign in to comment.