From ba6ccdb13ae2fa2b949043b841e69691892fcbc0 Mon Sep 17 00:00:00 2001 From: medmunds Date: Mon, 22 May 2017 11:09:26 -0700 Subject: [PATCH] Mailgun: handle x.y.z status code in webhooks Mailgun sometimes (though not usually) gives the 'code' event field as an RFC-3463 extended SMTP status code. Handle either format. Fixes #62 --- anymail/webhooks/mailgun.py | 9 +++++++++ tests/test_mailgun_webhooks.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/anymail/webhooks/mailgun.py b/anymail/webhooks/mailgun.py index bb0a785..94026ca 100644 --- a/anymail/webhooks/mailgun.py +++ b/anymail/webhooks/mailgun.py @@ -92,6 +92,15 @@ class MailgunTrackingWebhookView(MailgunBaseWebhookView): mta_status = int(esp_event['code']) except (KeyError, TypeError): pass + except ValueError: + # RFC-3463 extended SMTP status code (class.subject.detail, where class is "2", "4" or "5") + try: + status_class = esp_event['code'].split('.')[0] + except (TypeError, IndexError): + # illegal SMTP status code format + pass + else: + reject_reason = RejectReason.BOUNCED if status_class in ("4", "5") else RejectReason.OTHER else: reject_reason = self.reject_reasons.get( mta_status, diff --git a/tests/test_mailgun_webhooks.py b/tests/test_mailgun_webhooks.py index 8d5e86b..be9917b 100644 --- a/tests/test_mailgun_webhooks.py +++ b/tests/test_mailgun_webhooks.py @@ -204,6 +204,25 @@ class MailgunDeliveryTestCase(WebhookTestCase): self.assertEqual(event.reject_reason, "bounced") self.assertIn("The email account that you tried to reach does not exist", event.mta_response) + def test_alt_smtp_code(self): + # In some cases, Mailgun uses RFC-3463 extended SMTP status codes (x.y.z, rather than nnn). + # See issue #62. + raw_event = mailgun_sign({ + 'code': '5.1.1', + 'error': 'smtp;550 5.1.1 RESOLVER.ADR.RecipNotFound; not found', + 'event': 'bounced', + 'recipient': 'noreply@example.com', + # (omitting some fields that aren't relevant to the test) + }) + response = self.client.post('/anymail/mailgun/tracking/', data=raw_event) + self.assertEqual(response.status_code, 200) + kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=MailgunTrackingWebhookView, + event=ANY, esp_name='Mailgun') + event = kwargs['event'] + self.assertEqual(event.event_type, "bounced") + self.assertEqual(event.reject_reason, "bounced") + self.assertIn("RecipNotFound", event.mta_response) + def test_metadata(self): # Metadata fields are interspersed with other data, but also in message-headers raw_event = mailgun_sign({