mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Amazon SES: fix bcc
Set SendRawEmail Destinations param to pick up all recipients, including bcc (which doesn't appear in message headers). Fixes #189
This commit is contained in:
@@ -25,6 +25,19 @@ Release history
|
|||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
.. This extra heading level keeps the ToC from becoming unmanageably long
|
.. 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
|
v7.1
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -1064,6 +1077,7 @@ Features
|
|||||||
.. _@Lekensteyn: https://github.com/Lekensteyn
|
.. _@Lekensteyn: https://github.com/Lekensteyn
|
||||||
.. _@lewistaylor: https://github.com/lewistaylor
|
.. _@lewistaylor: https://github.com/lewistaylor
|
||||||
.. _@mbk-ok: https://github.com/mbk-ok
|
.. _@mbk-ok: https://github.com/mbk-ok
|
||||||
|
.. _@mwheels: https://github.com/mwheels
|
||||||
.. _@nuschk: https://github.com/nuschk
|
.. _@nuschk: https://github.com/nuschk
|
||||||
.. _@RignonNoel: https://github.com/RignonNoel
|
.. _@RignonNoel: https://github.com/RignonNoel
|
||||||
.. _@sebashwa: https://github.com/sebashwa
|
.. _@sebashwa: https://github.com/sebashwa
|
||||||
|
|||||||
@@ -150,9 +150,11 @@ class AmazonSESSendRawEmailPayload(AmazonSESBasePayload):
|
|||||||
MIMEText.set_payload(part, content, charset=qp_charset)
|
MIMEText.set_payload(part, content, charset=qp_charset)
|
||||||
|
|
||||||
def call_send_api(self, ses_client):
|
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"] = {
|
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()
|
"Data": self.mime_message.as_bytes()
|
||||||
}
|
}
|
||||||
return ses_client.send_raw_email(**self.params)
|
return ses_client.send_raw_email(**self.params)
|
||||||
@@ -225,8 +227,14 @@ class AmazonSESSendRawEmailPayload(AmazonSESBasePayload):
|
|||||||
def set_spoofed_to_header(self, header_to):
|
def set_spoofed_to_header(self, header_to):
|
||||||
# django.core.mail.EmailMessage.message() has already set
|
# django.core.mail.EmailMessage.message() has already set
|
||||||
# self.mime_message["To"] = header_to
|
# self.mime_message["To"] = header_to
|
||||||
# and performed any necessary header sanitization
|
# and performed any necessary header sanitization.
|
||||||
self.params["Destinations"] = [email.addr_spec for email in self.all_recipients]
|
#
|
||||||
|
# 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):
|
def set_metadata(self, metadata):
|
||||||
# Amazon SES has two mechanisms for adding custom data to a message:
|
# Amazon SES has two mechanisms for adding custom data to a message:
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
|
|||||||
self.assertIn(b"\nTo: to@example.com\n", raw_mime)
|
self.assertIn(b"\nTo: to@example.com\n", raw_mime)
|
||||||
self.assertIn(b"\nSubject: Subject here\n", raw_mime)
|
self.assertIn(b"\nSubject: Subject here\n", raw_mime)
|
||||||
self.assertIn(b"\n\nHere is the message", 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
|
# Since the SES backend generates the MIME message using Django's
|
||||||
# EmailMessage.message().to_string(), there's not really a need
|
# EmailMessage.message().to_string(), there's not really a need
|
||||||
@@ -134,6 +136,20 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
|
|||||||
# (EmailMessage.message() is well tested in the Django codebase.)
|
# (EmailMessage.message() is well tested in the Django codebase.)
|
||||||
# Instead, just spot-check a few things...
|
# 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):
|
def test_non_ascii_headers(self):
|
||||||
self.message.subject = "Thử tin nhắn" # utf-8 in subject header
|
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
|
self.message.to = ['"Người nhận" <to@example.com>'] # utf-8 in display name
|
||||||
@@ -149,6 +165,11 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
|
|||||||
self.assertIn(b"\nCc: cc@xn--th-e0a.example.com\n", raw_mime)
|
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)
|
# 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):
|
def test_attachments(self):
|
||||||
text_content = "• Item one\n• Item two\n• Item three" # those are \u2022 bullets ("\N{BULLET}")
|
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
|
self.message.attach(filename="Une pièce jointe.txt", # utf-8 chars in filename
|
||||||
@@ -336,7 +357,7 @@ class AmazonSESBackendAnymailFeatureTests(AmazonSESBackendMockAPITestCase):
|
|||||||
self.message.send()
|
self.message.send()
|
||||||
params = self.get_send_params()
|
params = self.get_send_params()
|
||||||
raw_mime = params['RawMessage']['Data']
|
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.assertIn(b"\nTo: Spoofed <spoofed-to@elsewhere.example.org>\n", raw_mime)
|
||||||
self.assertNotIn(b"envelope-to@example.com", raw_mime)
|
self.assertNotIn(b"envelope-to@example.com", raw_mime)
|
||||||
|
|
||||||
@@ -533,7 +554,6 @@ class AmazonSESBackendAnymailFeatureTests(AmazonSESBackendMockAPITestCase):
|
|||||||
self.assertNotIn('ConfigurationSetName', params)
|
self.assertNotIn('ConfigurationSetName', params)
|
||||||
self.assertNotIn('DefaultTags', params)
|
self.assertNotIn('DefaultTags', params)
|
||||||
self.assertNotIn('DefaultTemplateData', params)
|
self.assertNotIn('DefaultTemplateData', params)
|
||||||
self.assertNotIn('Destinations', params)
|
|
||||||
self.assertNotIn('FromArn', params)
|
self.assertNotIn('FromArn', params)
|
||||||
self.assertNotIn('Message', params)
|
self.assertNotIn('Message', params)
|
||||||
self.assertNotIn('ReplyToAddresses', params)
|
self.assertNotIn('ReplyToAddresses', params)
|
||||||
|
|||||||
Reference in New Issue
Block a user