Skip to content

Commit 412a1b7

Browse files
committed
Mailgun: Better error message for invalid sender domains
Try to catch cases where Mailgun will return HTTP 200-OK with the text "Mailgun Magnificent API" rather than sending the email. See #144.
1 parent a283def commit 412a1b7

File tree

3 files changed

+28
-2
lines changed

3 files changed

+28
-2
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ Breaking changes
4242
Fixes
4343
~~~~~
4444

45+
* **Mailgun:** Better error message for invalid sender domains (that caused a cryptic
46+
"Mailgun API response 200: OK Mailgun Magnificent API" error in earlier releases).
47+
4548
* **Postmark:** Don't error if a message is sent with only Cc and/or Bcc recipients
4649
(but no To addresses). Also, `message.anymail_status.recipients[email]` now includes
4750
send status for Cc and Bcc recipients. (Thanks to `@ailionx`_ for reporting the error.)

anymail/backends/mailgun.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime
22
from email.utils import encode_rfc2231
3+
from six.moves.urllib.parse import quote_plus
34

45
from requests import Request
56

@@ -79,7 +80,12 @@ def get_api_endpoint(self):
7980
"Either provide valid `from_email`, "
8081
"or set `message.esp_extra={'sender_domain': 'example.com'}`",
8182
backend=self.backend, email_message=self.message, payload=self)
82-
return "%s/messages" % self.sender_domain
83+
if '/' in self.sender_domain or '%' in self.sender_domain:
84+
# Mailgun returns a cryptic 200-OK "Mailgun Magnificent API" response
85+
# if '/' (or even %-encoded '/') confuses it about the API endpoint.
86+
raise AnymailError("Invalid sender domain '%s'" % self.sender_domain,
87+
backend=self.backend, email_message=self.message, payload=self)
88+
return "%s/messages" % quote_plus(self.sender_domain)
8389

8490
def get_request_params(self, api_url):
8591
params = super(MailgunPayload, self).get_request_params(api_url)

tests/test_mailgun_backend.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def message_from_bytes(s):
2020
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
2121

2222
from anymail.exceptions import (
23-
AnymailAPIError, AnymailInvalidAddress,
23+
AnymailError, AnymailAPIError, AnymailInvalidAddress,
2424
AnymailRequestsAPIError, AnymailUnsupportedFeature)
2525
from anymail.message import attach_inline_image_file
2626

@@ -507,6 +507,23 @@ def test_sender_domain_setting(self):
507507
self.message.send()
508508
self.assert_esp_called('/mg.example.com/messages') # setting overrides from_email
509509

510+
def test_invalid_sender_domain(self):
511+
# Make sure we won't construct an invalid API endpoint like
512+
# `https://api.mailgun.net/v3/example.com/INVALID/messages`
513+
# (which returns a cryptic 200-OK "Mailgun Magnificent API" response).
514+
self.message.from_email = "<[email protected]/invalid>"
515+
with self.assertRaisesMessage(AnymailError,
516+
"Invalid sender domain 'example.com/invalid'"):
517+
self.message.send()
518+
519+
@override_settings(ANYMAIL_MAILGUN_SENDER_DOMAIN='example.com%2Finvalid')
520+
def test_invalid_sender_domain_setting(self):
521+
# See previous test. Also, note that Mailgun unquotes % encoding *before*
522+
# extracting the sender domain (so %2f is just as bad as '/')
523+
with self.assertRaisesMessage(AnymailError,
524+
"Invalid sender domain 'example.com%2Finvalid'"):
525+
self.message.send()
526+
510527
def test_default_omits_options(self):
511528
"""Make sure by default we don't send any ESP-specific options.
512529

0 commit comments

Comments
 (0)