From fb21c0d25b20fd688515d72053c6da96b6457c8c Mon Sep 17 00:00:00 2001 From: medmunds Date: Wed, 3 Aug 2016 14:19:35 -0700 Subject: [PATCH] Mailgun: Add MAILGUN_SENDER_DOMAIN setting Allow custom MAILGUN_SENDER_DOMAIN in Anymail settings. (Replaces need to use global esp_extra.) Improve docs to cover cases where this is needed. (esp_extra sender_domain is still supported for overriding individual messages.) Fixes #26. --- README.rst | 2 ++ anymail/backends/mailgun.py | 4 ++- docs/esps/mailgun.rst | 58 +++++++++++++++++++++---------- tests/test_mailgun_backend.py | 9 +++-- tests/test_mailgun_integration.py | 11 +++--- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/README.rst b/README.rst index 5ea21a5..aabd192 100644 --- a/README.rst +++ b/README.rst @@ -100,7 +100,9 @@ or SparkPost or any other supported ESP where you see "mailgun": ) ANYMAIL = { + # (exact settings here depend on your ESP...) "MAILGUN_API_KEY": "", + "MAILGUN_SENDER_DOMAIN": 'mg.example.com', # your Mailgun domain, if needed } EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" # or sendgrid.SendGridBackend, or... DEFAULT_FROM_EMAIL = "you@example.com" # if you don't already have this in settings diff --git a/anymail/backends/mailgun.py b/anymail/backends/mailgun.py index ae68cf3..82647e7 100644 --- a/anymail/backends/mailgun.py +++ b/anymail/backends/mailgun.py @@ -16,6 +16,8 @@ class MailgunBackend(AnymailRequestsBackend): """Init options from Django settings""" esp_name = self.esp_name self.api_key = get_anymail_setting('api_key', esp_name=esp_name, kwargs=kwargs, allow_bare=True) + self.sender_domain = get_anymail_setting('sender_domain', esp_name=esp_name, kwargs=kwargs, + allow_bare=True, default=None) api_url = get_anymail_setting('api_url', esp_name=esp_name, kwargs=kwargs, default="https://api.mailgun.net/v3") if not api_url.endswith("/"): @@ -54,7 +56,7 @@ class MailgunPayload(RequestsPayload): def __init__(self, message, defaults, backend, *args, **kwargs): auth = ("api", backend.api_key) - self.sender_domain = None + self.sender_domain = backend.sender_domain self.all_recipients = [] # used for backend.parse_recipient_status # late-binding of recipient-variables: diff --git a/docs/esps/mailgun.rst b/docs/esps/mailgun.rst index 5e852f8..2afb559 100644 --- a/docs/esps/mailgun.rst +++ b/docs/esps/mailgun.rst @@ -41,6 +41,20 @@ root of the settings file if neither ``ANYMAIL["MAILGUN_API_KEY"]`` nor ``ANYMAIL_MAILGUN_API_KEY`` is set. +.. setting:: ANYMAIL_MAILGUN_SENDER_DOMAIN + +.. rubric:: MAILGUN_SENDER_DOMAIN + +If you are using a specific `Mailgun sender domain`_ +that is *different* from your messages' `from_email` domains, +set this to the domain you've configured in your Mailgun account. + +If your messages' `from_email` domains always match a configured +Mailgun sender domain, this setting is not needed. + +See :ref:`mailgun-sender-domain` below for examples. + + .. setting:: ANYMAIL_MAILGUN_API_URL .. rubric:: MAILGUN_API_URL @@ -58,32 +72,40 @@ The default is ``MAILGUN_API_URL = "https://api.mailgun.net/v3"`` Email sender domain ------------------- -Mailgun's API requires a sender domain `in the API url `_. -By default, Anymail will use the domain of each email's from address -as the domain for the Mailgun API. +Mailgun's API requires identifying the sender domain. +By default, Anymail uses the domain of each messages's `from_email` +(e.g., "example.com" for "from\@example.com"). -If you need to override this default, you can use Anymail's -:attr:`esp_extra` dict, either on an individual message: - - .. code-block:: python - - message = EmailMessage(from_email="sales@europe.example.com", ...) - message.esp_extra = {"sender_domain": "example.com"} - - -... or as a global :ref:`send default ` setting that applies -to all messages: +You will need to override this default if you are using +a dedicated `Mailgun sender domain`_ that is different from +a message's `from_email` domain. + +For example, if you are sending from "orders\@example.com", but your +Mailgun account is configured for "*mail1*.example.com", you should provide +:setting:`MAILGUN_SENDER_DOMAIN ` in your settings.py: .. code-block:: python + :emphasize-lines: 4 ANYMAIL = { ... - "MAILGUN_SEND_DEFAULTS": { - "esp_extra": {"sender_domain": "example.com"} - } + "MAILGUN_API_KEY": "", + "MAILGUN_SENDER_DOMAIN": "mail1.example.com" } -.. _base-url: https://documentation.mailgun.com/api-intro.html#base-url + +If you need to override the sender domain for an individual message, +include `sender_domain` in Anymail's :attr:`~anymail.message.AnymailMessage.esp_extra` +for that message: + + .. code-block:: python + + message = EmailMessage(from_email="marketing@example.com", ...) + message.esp_extra = {"sender_domain": "mail2.example.com"} + + +.. _Mailgun sender domain: + https://help.mailgun.com/hc/en-us/articles/202256730-How-do-I-pick-a-domain-name-for-my-Mailgun-account- .. _mailgun-esp-extra: diff --git a/tests/test_mailgun_backend.py b/tests/test_mailgun_backend.py index 38ceafe..febe4b6 100644 --- a/tests/test_mailgun_backend.py +++ b/tests/test_mailgun_backend.py @@ -383,8 +383,8 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase): def test_sender_domain(self): """Mailgun send domain can come from from_email or esp_extra""" - # You could also use ANYMAIL_SEND_DEFAULTS={'esp_extra': {'sender_domain': 'your-domain.com'}} - # (The mailgun_integration_tests do that.) + # 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 " self.message.send() self.assert_esp_called('/from-email.example.com/messages') # API url includes the sender-domain @@ -393,6 +393,11 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase): self.message.send() self.assert_esp_called('/esp-extra.example.com/messages') # overrides from_email + @override_settings(ANYMAIL_MAILGUN_SENDER_DOMAIN='mg.example.com') + def test_sender_domain_setting(self): + self.message.send() + self.assert_esp_called('/mg.example.com/messages') # setting overrides from_email + def test_default_omits_options(self): """Make sure by default we don't send any ESP-specific options. diff --git a/tests/test_mailgun_integration.py b/tests/test_mailgun_integration.py index 0c0b448..0207b33 100644 --- a/tests/test_mailgun_integration.py +++ b/tests/test_mailgun_integration.py @@ -23,10 +23,9 @@ MAILGUN_TEST_DOMAIN = os.getenv('MAILGUN_TEST_DOMAIN') @unittest.skipUnless(MAILGUN_TEST_API_KEY and MAILGUN_TEST_DOMAIN, "Set MAILGUN_TEST_API_KEY and MAILGUN_TEST_DOMAIN environment variables " "to run Mailgun integration tests") -@override_settings(ANYMAIL_MAILGUN_API_KEY=MAILGUN_TEST_API_KEY, - ANYMAIL_MAILGUN_SEND_DEFAULTS={ - 'esp_extra': {'o:testmode': 'yes', - 'sender_domain': MAILGUN_TEST_DOMAIN}}, +@override_settings(ANYMAIL={'MAILGUN_API_KEY': MAILGUN_TEST_API_KEY, + 'MAILGUN_SENDER_DOMAIN': MAILGUN_TEST_DOMAIN, + 'MAILGUN_SEND_DEFAULTS': {'esp_extra': {'o:testmode': 'yes'}}}, EMAIL_BACKEND="anymail.backends.mailgun.MailgunBackend") class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """Mailgun API integration tests @@ -168,7 +167,9 @@ class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): self.assertEqual(err.status_code, 400) self.assertIn("'from' parameter is not a valid address", str(err)) - @override_settings(ANYMAIL_MAILGUN_API_KEY="Hey, that's not an API key!") + @override_settings(ANYMAIL={'MAILGUN_API_KEY': "Hey, that's not an API key", + 'MAILGUN_SENDER_DOMAIN': MAILGUN_TEST_DOMAIN, + 'MAILGUN_SEND_DEFAULTS': {'esp_extra': {'o:testmode': 'yes'}}}) def test_invalid_api_key(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send()