mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Feature: Implement merge_headers
Implement and document `merge_headers` for all other ESPs that can support it. (See #371 for base and Amazon SES implementation.) Closes #374
This commit is contained in:
@@ -568,6 +568,43 @@ class BrevoBackendAnymailFeatureTests(BrevoBackendMockAPITestCase):
|
||||
{"notification_batch": "zx912"},
|
||||
)
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.set_mock_response(json_data=self._mock_batch_response)
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
}
|
||||
|
||||
self.message.send()
|
||||
|
||||
data = self.get_api_call_json()
|
||||
versions = data["messageVersions"]
|
||||
self.assertEqual(len(versions), 2)
|
||||
self.assertEqual(
|
||||
versions[0]["headers"], {"List-Unsubscribe": "<https://example.com/a/>"}
|
||||
)
|
||||
self.assertEqual(
|
||||
versions[1]["headers"], {"List-Unsubscribe": "<https://example.com/b/>"}
|
||||
)
|
||||
self.assertNotIn("params", versions[0]) # because no merge_data
|
||||
# non-merge headers still in base data
|
||||
self.assertEqual(
|
||||
data["headers"],
|
||||
{
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
)
|
||||
|
||||
def test_default_omits_options(self):
|
||||
"""Make sure by default we don't send any ESP-specific options.
|
||||
|
||||
|
||||
@@ -113,6 +113,18 @@ class BrevoBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
"test+to1@anymail.dev": {"customer-id": "ZXK9123"},
|
||||
"test+to2@anymail.dev": {"customer-id": "ZZT4192"},
|
||||
},
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
merge_headers={
|
||||
"test+to1@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"test+to2@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
message.attach("attachment1.txt", "Here is some\ntext", "text/plain")
|
||||
|
||||
@@ -111,7 +111,7 @@ class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
|
||||
cc=["cc1@example.com", "Also CC <cc2@example.com>"],
|
||||
headers={
|
||||
"Reply-To": "another@example.com",
|
||||
"X-MyHeader": "my value",
|
||||
"x-my-header": "my value",
|
||||
"Message-ID": "mycustommsgid@example.com",
|
||||
},
|
||||
)
|
||||
@@ -126,8 +126,8 @@ class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
|
||||
)
|
||||
self.assertEqual(data["cc"], ["cc1@example.com", "Also CC <cc2@example.com>"])
|
||||
self.assertEqual(data["h:Reply-To"], "another@example.com")
|
||||
self.assertEqual(data["h:X-MyHeader"], "my value")
|
||||
self.assertEqual(data["h:Message-ID"], "mycustommsgid@example.com")
|
||||
self.assertEqual(data["h:X-My-Header"], "my value")
|
||||
self.assertEqual(data["h:Message-Id"], "mycustommsgid@example.com")
|
||||
# multiple recipients, but not a batch send:
|
||||
self.assertNotIn("recipient-variables", data)
|
||||
|
||||
@@ -816,6 +816,51 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
|
||||
):
|
||||
self.message.send()
|
||||
|
||||
def test_merge_headers(self):
|
||||
# Per-recipient merge_headers uses the same recipient-variables mechanism
|
||||
# as above, using variable names starting with "h:"
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
"X-Custom": "custom-default",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
"X-No-Default": "custom-for-alice",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
"X-Custom": "custom-for-bob",
|
||||
},
|
||||
}
|
||||
self.message.send()
|
||||
|
||||
data = self.get_api_call_data()
|
||||
# non-merge header has fixed value:
|
||||
self.assertEqual(data["h:List-Unsubscribe-Post"], "List-Unsubscribe=One-Click")
|
||||
# merge headers refer to recipient-variables:
|
||||
self.assertEqual(data["h:List-Unsubscribe"], "%recipient.h:List-Unsubscribe%")
|
||||
self.assertEqual(data["h:X-Custom"], "%recipient.h:X-Custom%")
|
||||
self.assertEqual(data["h:X-No-Default"], "%recipient.h:X-No-Default%")
|
||||
# recipient-variables populates them:
|
||||
self.assertJSONEqual(
|
||||
data["recipient-variables"],
|
||||
{
|
||||
"alice@example.com": {
|
||||
"h:List-Unsubscribe": "<https://example.com/a/>",
|
||||
"h:X-Custom": "custom-default", # from extra_headers
|
||||
"h:X-No-Default": "custom-for-alice",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"h:List-Unsubscribe": "<https://example.com/b/>",
|
||||
"h:X-Custom": "custom-for-bob",
|
||||
"h:X-No-Default": "", # no default in extra_headers
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_force_batch(self):
|
||||
# Mailgun uses presence of recipient-variables to indicate batch send
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
|
||||
@@ -201,6 +201,36 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
# (We could try fetching the message from event["storage"]["url"]
|
||||
# to verify content and other headers.)
|
||||
|
||||
def test_per_recipient_options(self):
|
||||
message = AnymailMessage(
|
||||
from_email=formataddr(("Test From", self.from_email)),
|
||||
to=["test+to1@anymail.dev", '"Recipient 2" <test+to2@anymail.dev>'],
|
||||
subject="Anymail Mailgun per-recipient options test",
|
||||
body="This is the text body",
|
||||
merge_metadata={
|
||||
"test+to1@anymail.dev": {"meta1": "one", "meta2": "two"},
|
||||
"test+to2@anymail.dev": {"meta1": "recipient 2"},
|
||||
},
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
"X-Custom-Header": "default",
|
||||
},
|
||||
merge_headers={
|
||||
"test+to1@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
"X-Custom-Header": "custom",
|
||||
},
|
||||
"test+to2@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.send()
|
||||
recipient_status = message.anymail_status.recipients
|
||||
self.assertEqual(recipient_status["test+to1@anymail.dev"].status, "queued")
|
||||
self.assertEqual(recipient_status["test+to2@anymail.dev"].status, "queued")
|
||||
|
||||
def test_stored_template(self):
|
||||
message = AnymailMessage(
|
||||
# name of a real template named in Anymail's Mailgun test account:
|
||||
|
||||
@@ -562,6 +562,45 @@ class MailjetBackendAnymailFeatureTests(MailjetBackendMockAPITestCase):
|
||||
{"order_id": 678, "notification_batch": "zx912"},
|
||||
)
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
}
|
||||
self.message.send()
|
||||
|
||||
data = self.get_api_call_json()
|
||||
messages = data["Messages"]
|
||||
self.assertEqual(len(messages), 2)
|
||||
self.assertEqual(messages[0]["To"][0]["Email"], "alice@example.com")
|
||||
self.assertEqual(
|
||||
messages[0]["Headers"],
|
||||
{"List-Unsubscribe": "<https://example.com/a/>"},
|
||||
)
|
||||
self.assertEqual(messages[1]["To"][0]["Email"], "bob@example.com")
|
||||
self.assertEqual(
|
||||
messages[1]["Headers"],
|
||||
{"List-Unsubscribe": "<https://example.com/b/>"},
|
||||
)
|
||||
|
||||
# non-merge headers still in globals:
|
||||
self.assertEqual(
|
||||
data["Globals"]["Headers"],
|
||||
{
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
)
|
||||
|
||||
def test_default_omits_options(self):
|
||||
"""Make sure by default we don't send any ESP-specific options.
|
||||
|
||||
|
||||
@@ -113,6 +113,23 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
"test+to2@anymail.dev": {"value": "two"},
|
||||
},
|
||||
merge_global_data={"global": "global_value"},
|
||||
metadata={"customer-id": "unknown", "meta2": 2},
|
||||
merge_metadata={
|
||||
"test+to1@anymail.dev": {"customer-id": "ZXK9123"},
|
||||
"test+to2@anymail.dev": {"customer-id": "ZZT4192"},
|
||||
},
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
merge_headers={
|
||||
"test+to1@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"test+to2@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.send()
|
||||
recipient_status = message.anymail_status.recipients
|
||||
|
||||
@@ -712,6 +712,49 @@ class PostmarkBackendAnymailFeatureTests(PostmarkBackendMockAPITestCase):
|
||||
self.assertEqual(messages[1]["To"], "Bob <bob@example.com>")
|
||||
self.assertEqual(messages[1]["Metadata"], {"order_id": 678, "tier": "premium"})
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
}
|
||||
self.message.send()
|
||||
|
||||
self.assert_esp_called("/email/batch")
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(len(data), 2)
|
||||
# Global and merge headers are combined:
|
||||
self.assertEqual(data[0]["To"], "alice@example.com")
|
||||
self.assertCountEqual(
|
||||
data[0]["Headers"],
|
||||
[
|
||||
{"Name": "List-Unsubscribe", "Value": "<https://example.com/a/>"},
|
||||
{
|
||||
"Name": "List-Unsubscribe-Post",
|
||||
"Value": "List-Unsubscribe=One-Click",
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertEqual(data[1]["To"], "Bob <bob@example.com>")
|
||||
self.assertCountEqual(
|
||||
data[1]["Headers"],
|
||||
[
|
||||
{"Name": "List-Unsubscribe", "Value": "<https://example.com/b/>"},
|
||||
{
|
||||
"Name": "List-Unsubscribe-Post",
|
||||
"Value": "List-Unsubscribe=One-Click",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def test_default_omits_options(self):
|
||||
"""Make sure by default we don't send any ESP-specific options.
|
||||
|
||||
|
||||
@@ -68,13 +68,29 @@ class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
cc=["test+cc1@anymail.dev", "Copy 2 <test+cc2@anymail.dev>"],
|
||||
bcc=["test+bcc1@anymail.dev", "Blind Copy 2 <test+bcc2@anymail.dev>"],
|
||||
reply_to=["reply1@example.com", "Reply 2 <reply2@example.com>"],
|
||||
headers={"X-Anymail-Test": "value"},
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
# no send_at support
|
||||
metadata={"meta1": "simple string", "meta2": 2},
|
||||
tags=["tag 1"], # max one tag
|
||||
track_opens=True,
|
||||
track_clicks=True,
|
||||
merge_data={}, # force batch send (distinct message for each `to`)
|
||||
# either of these merge_ options will force batch send
|
||||
# (unique message for each "to" recipient)
|
||||
merge_metadata={
|
||||
"test+to1@anymail.dev": {"customer-id": "ZXK9123"},
|
||||
"test+to2@anymail.dev": {"customer-id": "ZZT4192"},
|
||||
},
|
||||
merge_headers={
|
||||
"test+to1@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"test+to2@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain")
|
||||
message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv")
|
||||
|
||||
@@ -533,6 +533,49 @@ class ResendBackendAnymailFeatureTests(ResendBackendMockAPITestCase):
|
||||
"faccb7a5-8a28-4e9a-ac64-8da1cc3bc1cb",
|
||||
)
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.set_mock_response(json_data=self._mock_batch_response)
|
||||
message = AnymailMessage(
|
||||
from_email="from@example.com",
|
||||
to=["alice@example.com", "Bob <bob@example.com>"],
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
merge_headers={
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.send()
|
||||
|
||||
# merge_headers forces batch send API:
|
||||
self.assert_esp_called("/emails/batch")
|
||||
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(len(data), 2)
|
||||
self.assertEqual(data[0]["to"], ["alice@example.com"])
|
||||
# global and recipient headers are combined:
|
||||
self.assertEqual(
|
||||
data[0]["headers"],
|
||||
{
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
},
|
||||
)
|
||||
self.assertEqual(data[1]["to"], ["Bob <bob@example.com>"])
|
||||
self.assertEqual(
|
||||
data[1]["headers"],
|
||||
{
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
},
|
||||
)
|
||||
|
||||
def test_track_opens(self):
|
||||
self.message.track_opens = True
|
||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, "track_opens"):
|
||||
|
||||
@@ -87,7 +87,7 @@ class ResendBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
) # non-empty string
|
||||
|
||||
def test_batch_send(self):
|
||||
# merge_metadata or merge_data will use batch send API
|
||||
# merge_metadata, merge_headers, or merge_data will use batch send API
|
||||
message = AnymailMessage(
|
||||
subject="Anymail Resend batch sendintegration test",
|
||||
body="This is the text body",
|
||||
@@ -99,6 +99,18 @@ class ResendBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
"test+to2@anymail.dev": {"meta3": "recipient 2"},
|
||||
},
|
||||
tags=["tag 1", "tag 2"],
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
merge_headers={
|
||||
"test+to1@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"test+to2@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.attach_alternative("<p>HTML content</p>", "text/html")
|
||||
message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain")
|
||||
|
||||
@@ -1016,6 +1016,45 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
}
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
personalizations = data["personalizations"]
|
||||
self.assertEqual(len(personalizations), 2)
|
||||
|
||||
self.assertEqual(personalizations[0]["to"][0]["email"], "alice@example.com")
|
||||
self.assertEqual(
|
||||
personalizations[0]["headers"],
|
||||
{"List-Unsubscribe": "<https://example.com/a/>"},
|
||||
)
|
||||
self.assertEqual(personalizations[1]["to"][0]["email"], "bob@example.com")
|
||||
self.assertEqual(
|
||||
personalizations[1]["headers"],
|
||||
{"List-Unsubscribe": "<https://example.com/b/>"},
|
||||
)
|
||||
|
||||
# non-merge headers still in globals:
|
||||
self.assertEqual(
|
||||
data["headers"],
|
||||
{
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
ANYMAIL_SENDGRID_GENERATE_MESSAGE_ID=False # else we force custom_args
|
||||
)
|
||||
|
||||
@@ -119,6 +119,23 @@ class SendGridBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
esp_extra={
|
||||
"merge_field_format": "%{}%",
|
||||
},
|
||||
metadata={"meta1": "simple string", "meta2": 2},
|
||||
merge_metadata={
|
||||
"to1@sink.sendgrid.net": {"meta3": "recipient 1"},
|
||||
"to2@sink.sendgrid.net": {"meta3": "recipient 2"},
|
||||
},
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
merge_headers={
|
||||
"to1@sink.sendgrid.net": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"to2@sink.sendgrid.net": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.send()
|
||||
recipient_status = message.anymail_status.recipients
|
||||
|
||||
@@ -605,6 +605,58 @@ class SparkPostBackendAnymailFeatureTests(SparkPostBackendMockAPITestCase):
|
||||
)
|
||||
self.assertEqual(data["metadata"], {"notification_batch": "zx912"})
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.set_mock_result(accepted=2)
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"X-Custom-1": "custom 1",
|
||||
"X-Custom-2": "custom 2 (default)",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"X-Custom-2": "custom 2 alice",
|
||||
"X-Custom-3": "custom 3 alice",
|
||||
},
|
||||
"bob@example.com": {"X-Custom-2": "custom 2 bob"},
|
||||
}
|
||||
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
recipients = data["recipients"]
|
||||
self.assertEqual(len(recipients), 2)
|
||||
self.assertEqual(recipients[0]["address"]["email"], "alice@example.com")
|
||||
self.assertEqual(
|
||||
recipients[0]["substitution_data"],
|
||||
{
|
||||
"Header__X_Custom_2": "custom 2 alice",
|
||||
"Header__X_Custom_3": "custom 3 alice",
|
||||
},
|
||||
)
|
||||
self.assertEqual(recipients[1]["address"]["email"], "bob@example.com")
|
||||
self.assertEqual(
|
||||
recipients[1]["substitution_data"],
|
||||
{
|
||||
"Header__X_Custom_2": "custom 2 bob",
|
||||
},
|
||||
)
|
||||
# Indirect merge_headers through template substitutions:
|
||||
self.assertEqual(
|
||||
data["content"]["headers"],
|
||||
{
|
||||
"X-Custom-1": "custom 1", # (not a merge_header, value unchanged)
|
||||
"X-Custom-2": "{{Header__X_Custom_2}}",
|
||||
"X-Custom-3": "{{Header__X_Custom_3}}",
|
||||
},
|
||||
)
|
||||
# Defaults for merge_headers in global substitution_data:
|
||||
self.assertEqual(
|
||||
data["substitution_data"],
|
||||
{
|
||||
"Header__X_Custom_2": "custom 2 (default)",
|
||||
# No default specified for X-Custom-3; SparkPost will use empty string
|
||||
},
|
||||
)
|
||||
|
||||
def test_default_omits_options(self):
|
||||
"""Make sure by default we don't send any ESP-specific options.
|
||||
|
||||
|
||||
@@ -116,6 +116,19 @@ class SparkPostBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
"to2@test.sink.sparkpostmail.com": {"value": "two"},
|
||||
},
|
||||
merge_global_data={"global": "global_value"},
|
||||
merge_metadata={
|
||||
"to1@test.sink.sparkpostmail.com": {"meta1": "one"},
|
||||
"to2@test.sink.sparkpostmail.com": {"meta1": "two"},
|
||||
},
|
||||
headers={
|
||||
"X-Custom": "custom header default",
|
||||
},
|
||||
merge_headers={
|
||||
# (Note that SparkPost doesn't support custom List-Unsubscribe headers)
|
||||
"to1@test.sink.sparkpostmail.com": {
|
||||
"X-Custom": "custom header one",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.send()
|
||||
recipient_status = message.anymail_status.recipients
|
||||
|
||||
@@ -602,12 +602,53 @@ class UnisenderGoBackendAnymailFeatureTests(UnisenderGoBackendMockAPITestCase):
|
||||
self.assertNotIn("to", headers)
|
||||
self.assertNotIn("cc", headers)
|
||||
|
||||
def test_merge_headers(self):
|
||||
self.message.to = ["alice@example.com", "Bob <bob@example.com>"]
|
||||
self.message.extra_headers = {
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
}
|
||||
self.message.merge_headers = {
|
||||
"alice@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"bob@example.com": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
}
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
headers = data["message"]["headers"]
|
||||
recipients = data["message"]["recipients"]
|
||||
|
||||
self.assertEqual(headers["List-Unsubscribe-Post"], "List-Unsubscribe=One-Click")
|
||||
|
||||
# merge_headers List-Unsubscribe is handled as substitution:
|
||||
self.assertEqual(headers["List-Unsubscribe"], "{{Header__List_Unsubscribe}}")
|
||||
self.assertEqual(
|
||||
recipients[0]["substitutions"],
|
||||
{"Header__List_Unsubscribe": "<https://example.com/a/>"},
|
||||
)
|
||||
self.assertEqual(
|
||||
recipients[1]["substitutions"],
|
||||
# Header substitutions merged with other substitutions:
|
||||
{"Header__List_Unsubscribe": "<https://example.com/b/>", "to_name": "Bob"},
|
||||
)
|
||||
|
||||
def test_unsupported_merge_headers(self):
|
||||
# Unisender Go only allows substitutions in the List-Unsubscribe header
|
||||
self.message.merge_headers = {"to@example.com": {"X-Other": "not supported"}}
|
||||
with self.assertRaisesMessage(
|
||||
AnymailUnsupportedFeature, "'X-Other' in merge_headers"
|
||||
):
|
||||
self.message.send()
|
||||
|
||||
def test_cc_unsupported_with_batch_send(self):
|
||||
self.message.merge_data = {}
|
||||
self.message.cc = ["cc@example.com"]
|
||||
with self.assertRaisesMessage(
|
||||
AnymailUnsupportedFeature,
|
||||
"cc with batch send (merge_data or merge_metadata)",
|
||||
"cc with batch send (merge_data, merge_metadata, or merge_headers)",
|
||||
):
|
||||
self.message.send()
|
||||
|
||||
|
||||
@@ -147,6 +147,18 @@ class UnisenderGoBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
"test+to1@anymail.dev": {"customer-id": "ZXK9123"},
|
||||
"test+to2@anymail.dev": {"customer-id": "ZZT4192"},
|
||||
},
|
||||
headers={
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
"List-Unsubscribe": "<mailto:unsubscribe@example.com>",
|
||||
},
|
||||
merge_headers={
|
||||
"test+to1@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/a/>",
|
||||
},
|
||||
"test+to2@anymail.dev": {
|
||||
"List-Unsubscribe": "<https://example.com/b/>",
|
||||
},
|
||||
},
|
||||
)
|
||||
message.from_email = None # use template sender
|
||||
message.attach("attachment1.txt", "Here is some\ntext", "text/plain")
|
||||
|
||||
Reference in New Issue
Block a user