From bb257152bedfaac8952e08997356a45c41b90a6d Mon Sep 17 00:00:00 2001 From: Leo Antunes Date: Thu, 1 Nov 2018 20:26:49 +0100 Subject: [PATCH] Mailgun: treat temporary failure as deferred in tracking webhook Map Mailgun severity: temporary failure event to Anymail "deferred" event, to distinguish it from severity: permanent failures which will show up as Anymail "bounced". Also remap Mailgun reason: generic failure to Anymail "other" reject reason (rather than "bounced"). Closes #130 --- anymail/webhooks/mailgun.py | 14 +++++++---- tests/test_mailgun_webhooks.py | 44 ++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/anymail/webhooks/mailgun.py b/anymail/webhooks/mailgun.py index 86ce825..5506ad4 100644 --- a/anymail/webhooks/mailgun.py +++ b/anymail/webhooks/mailgun.py @@ -87,16 +87,20 @@ class MailgunTrackingWebhookView(MailgunBaseWebhookView): # (these appear in webhook doc examples, but aren't actually documented anywhere) "bounce": RejectReason.BOUNCED, "suppress-bounce": RejectReason.BOUNCED, - "generic": RejectReason.BOUNCED, # ??? appears to be used for any temporary failure? + "generic": RejectReason.OTHER, # ??? appears to be used for any temporary failure? + } + + severities = { + # Remap some event types based on "severity" payload field + (EventType.BOUNCED, 'temporary'): EventType.DEFERRED } def esp_to_anymail_event(self, esp_event): event_data = esp_event.get('event-data', {}) - try: - event_type = self.event_types[event_data['event']] - except KeyError: - event_type = EventType.UNKNOWN + event_type = self.event_types.get(event_data['event'], EventType.UNKNOWN) + + event_type = self.severities.get((EventType.BOUNCED, event_data.get('severity')), event_type) # Use signature.token for event_id, rather than event_data.id, # because the latter is only "guaranteed to be unique within a day". diff --git a/tests/test_mailgun_webhooks.py b/tests/test_mailgun_webhooks.py index 828ff98..7aa898c 100644 --- a/tests/test_mailgun_webhooks.py +++ b/tests/test_mailgun_webhooks.py @@ -251,12 +251,52 @@ class MailgunTestCase(WebhookTestCase): 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.event_type, "deferred") self.assertEqual(event.recipient, "undeliverable@nomx.example.com") - self.assertEqual(event.reject_reason, "bounced") + self.assertEqual(event.reject_reason, "other") self.assertEqual(event.description, "No MX for nomx.example.com") self.assertEqual(event.mta_response, "No MX for nomx.example.com") + def test_failed_greylisted_event(self): + raw_event = mailgun_sign_payload({ + "event-data": { + "event": "failed", + "severity": "temporary", + "reason": "greylisted", + "timestamp": 1534111899.659519, + "log-level": "warn", + "message": { + "headers": { + "to": "undeliverable@nomx.example.com", + "message-id": "20180812214638.1.4A7D468E9BC18C5D@example.org", + "from": "Test Sender ", + "subject": "Testing" + }, + }, + "recipient": "undeliverable@mx.example.com", + "delivery-status": { + "mx-host": "mx.example.com", + "attempt-no": 1, + "description": "Recipient address rejected: Greylisted", + "session-seconds": 0.0, + "retry-seconds": 300, + "code": 450, + "message": "Recipient address rejected: Greylisted" + } + }, + }) + response = self.client.post('/anymail/mailgun/tracking/', + data=json.dumps(raw_event), content_type='application/json') + 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, "deferred") + self.assertEqual(event.recipient, "undeliverable@mx.example.com") + self.assertEqual(event.reject_reason, "other") + self.assertEqual(event.description, "Recipient address rejected: Greylisted") + self.assertEqual(event.mta_response, "Recipient address rejected: Greylisted") + def test_rejected_event(self): # (The "rejected" event is documented and appears in Mailgun dashboard logs, # but it doesn't appear to be delivered through webhooks as of 8/2018.)