mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Feature: Add envelope_sender
New EmailMessage attribute `envelope_sender` controls ESP's sender, sending domain, or return path where supported: * Mailgun: overrides SENDER_DOMAIN on individual message (domain portion only) * Mailjet: becomes `Sender` API param * Mandrill: becomes `return_path_domain` API param (domain portion only) * SparkPost: becomes `return_path` API param * Other ESPs: not believed to be supported Also support undocumented Django SMTP backend behavior, where envelope sender is given by `message.from_email` when `message.extra_headers["From"]` is set. Fixes #91.
This commit is contained in:
@@ -351,3 +351,23 @@ class SpecialHeaderTests(TestBackendTestCase):
|
||||
params = self.get_send_params()
|
||||
self.assertEqual(flatten_emails(params['reply_to']), ["header@example.com"])
|
||||
self.assertEqual(params['extra_headers'], {"X-Extra": "extra"}) # Reply-To no longer there
|
||||
|
||||
def test_envelope_sender(self):
|
||||
"""Django treats message.from_email as envelope-sender if messsage.extra_headers['From'] is set"""
|
||||
# Using Anymail's envelope_sender extension
|
||||
self.message.from_email = "Header From <header@example.com>"
|
||||
self.message.envelope_sender = "Envelope From <envelope@bounces.example.com>" # Anymail extension
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertEqual(params['from'].address, "Header From <header@example.com>")
|
||||
self.assertEqual(params['envelope_sender'], "envelope@bounces.example.com")
|
||||
|
||||
# Using Django's undocumented message.extra_headers['From'] extension
|
||||
# (see https://code.djangoproject.com/ticket/9214)
|
||||
self.message.from_email = "Envelope From <envelope@bounces.example.com>"
|
||||
self.message.extra_headers = {"From": "Header From <header@example.com>"}
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertEqual(params['from'].address, "Header From <header@example.com>")
|
||||
self.assertEqual(params['envelope_sender'], "envelope@bounces.example.com")
|
||||
self.assertNotIn("From", params.get('extra_headers', {})) # From was removed from extra-headers
|
||||
|
||||
@@ -417,13 +417,19 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
|
||||
})
|
||||
|
||||
def test_sender_domain(self):
|
||||
"""Mailgun send domain can come from from_email or esp_extra"""
|
||||
"""Mailgun send domain can come from from_email, envelope_sender, or esp_extra"""
|
||||
# You could also use MAILGUN_SENDER_DOMAIN in your ANYMAIL settings, as in the next test.
|
||||
# (The mailgun_integration_tests also do that.)
|
||||
self.message.from_email = "Test From <from@from-email.example.com>"
|
||||
self.message.send()
|
||||
self.assert_esp_called('/from-email.example.com/messages') # API url includes the sender-domain
|
||||
|
||||
self.message.from_email = "Test From <from@from-email.example.com>"
|
||||
self.message.envelope_sender = "anything@bounces.example.com" # only the domain part is used
|
||||
self.message.send()
|
||||
self.assert_esp_called('/bounces.example.com/messages') # overrides from_email
|
||||
|
||||
self.message.from_email = "Test From <from@from-email.example.com>"
|
||||
self.message.esp_extra = {'sender_domain': 'esp-extra.example.com'}
|
||||
self.message.send()
|
||||
self.assert_esp_called('/esp-extra.example.com/messages') # overrides from_email
|
||||
|
||||
@@ -357,6 +357,12 @@ class MailjetBackendStandardEmailTests(MailjetBackendMockAPITestCase):
|
||||
class MailjetBackendAnymailFeatureTests(MailjetBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
self.message.envelope_sender = "bounce-handler@bounces.example.com"
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['Sender'], "bounce-handler@bounces.example.com")
|
||||
|
||||
def test_metadata(self):
|
||||
# Mailjet expects the payload to be a single string
|
||||
# https://dev.mailjet.com/guides/#tagging-email-messages
|
||||
|
||||
@@ -270,6 +270,12 @@ class MandrillBackendStandardEmailTests(MandrillBackendMockAPITestCase):
|
||||
class MandrillBackendAnymailFeatureTests(MandrillBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
self.message.envelope_sender = "anything@bounces.example.com"
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['message']['return_path_domain'], "bounces.example.com")
|
||||
|
||||
def test_metadata(self):
|
||||
self.message.metadata = {'user_id': "12345", 'items': 6}
|
||||
self.message.send()
|
||||
|
||||
@@ -321,6 +321,13 @@ class PostmarkBackendStandardEmailTests(PostmarkBackendMockAPITestCase):
|
||||
class PostmarkBackendAnymailFeatureTests(PostmarkBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
# Postmark doesn't allow overriding envelope sender on individual messages.
|
||||
# You can configure a custom return-path domain for each server in their control panel.
|
||||
self.message.envelope_sender = "anything@bounces.example.com"
|
||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, 'envelope_sender'):
|
||||
self.message.send()
|
||||
|
||||
def test_metadata(self):
|
||||
self.message.metadata = {'user_id': "12345", 'items': 6}
|
||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, 'metadata'):
|
||||
|
||||
@@ -335,6 +335,12 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
|
||||
class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
# SendGrid does not have a way to change envelope sender.
|
||||
self.message.envelope_sender = "anything@bounces.example.com"
|
||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, 'envelope_sender'):
|
||||
self.message.send()
|
||||
|
||||
def test_metadata(self):
|
||||
self.message.metadata = {'user_id': "12345", 'items': 6, 'float': 98.6, 'long': longtype(123)}
|
||||
self.message.send()
|
||||
|
||||
@@ -343,6 +343,12 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
|
||||
class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
# SendGrid does not have a way to change envelope sender.
|
||||
self.message.envelope_sender = "anything@bounces.example.com"
|
||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, 'envelope_sender'):
|
||||
self.message.send()
|
||||
|
||||
def test_metadata(self):
|
||||
# Note: SendGrid doesn't handle complex types in metadata
|
||||
self.message.metadata = {'user_id': "12345", 'items': 6}
|
||||
|
||||
@@ -270,6 +270,12 @@ class SendinBlueBackendStandardEmailTests(SendinBlueBackendMockAPITestCase):
|
||||
class SendinBlueBackendAnymailFeatureTests(SendinBlueBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
# SendinBlue does not have a way to change envelope sender.
|
||||
self.message.envelope_sender = "anything@bounces.example.com"
|
||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, 'envelope_sender'):
|
||||
self.message.send()
|
||||
|
||||
def test_metadata(self):
|
||||
self.message.metadata = {'user_id': "12345", 'items': 6, 'float': 98.6, 'long': longtype(123)}
|
||||
self.message.send()
|
||||
|
||||
@@ -327,6 +327,12 @@ class SparkPostBackendStandardEmailTests(SparkPostBackendMockAPITestCase):
|
||||
class SparkPostBackendAnymailFeatureTests(SparkPostBackendMockAPITestCase):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
def test_envelope_sender(self):
|
||||
self.message.envelope_sender = "bounce-handler@bounces.example.com"
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertEqual(params['return_path'], "bounce-handler@bounces.example.com")
|
||||
|
||||
def test_metadata(self):
|
||||
self.message.metadata = {'user_id': "12345", 'items': 'spark, post'}
|
||||
self.message.send()
|
||||
|
||||
@@ -20,7 +20,7 @@ except ImportError:
|
||||
|
||||
from anymail.exceptions import AnymailInvalidAddress
|
||||
from anymail.utils import (
|
||||
parse_address_list, EmailAddress,
|
||||
parse_address_list, parse_single_address, EmailAddress,
|
||||
is_lazy, force_non_lazy, force_non_lazy_dict, force_non_lazy_list,
|
||||
update_deep,
|
||||
get_request_uri, get_request_basic_auth, parse_rfc2822date, querydict_getfirst)
|
||||
@@ -150,6 +150,16 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
self.assertEqual(parsed_list[0].display_name, "")
|
||||
self.assertEqual(parsed_list[0].addr_spec, "one@example.com")
|
||||
|
||||
def test_parse_one(self):
|
||||
parsed = parse_single_address("one@example.com")
|
||||
self.assertEqual(parsed.address, "one@example.com")
|
||||
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Only one email address is allowed; found 2"):
|
||||
parse_single_address("one@example.com, two@example.com")
|
||||
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Invalid email address"):
|
||||
parse_single_address(" ")
|
||||
|
||||
|
||||
class LazyCoercionTests(SimpleTestCase):
|
||||
"""Test utils.is_lazy and force_non_lazy*"""
|
||||
|
||||
Reference in New Issue
Block a user