mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
Fix: Postmark tracking webhook handle SubscriptionChange events
Handle Postmark SubscriptionChange events as Anymail unsubscribe, subscribe, or bounce Anymail tracking events.
This commit is contained in:
@@ -25,6 +25,19 @@ Release history
|
|||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
.. This extra heading level keeps the ToC from becoming unmanageably long
|
.. This extra heading level keeps the ToC from becoming unmanageably long
|
||||||
|
|
||||||
|
vNext
|
||||||
|
-----
|
||||||
|
|
||||||
|
*Unreleased changes*
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
* **Postmark:** Handle Postmark's SubscriptionChange events as Anymail
|
||||||
|
unsubscribe, subscribe, or bounce tracking events, rather than "unknown".
|
||||||
|
(Thanks to `@puru02`_ for the fix.)
|
||||||
|
|
||||||
|
|
||||||
v8.6 LTS
|
v8.6 LTS
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -1344,6 +1357,7 @@ Features
|
|||||||
.. _@mbk-ok: https://github.com/mbk-ok
|
.. _@mbk-ok: https://github.com/mbk-ok
|
||||||
.. _@mwheels: https://github.com/mwheels
|
.. _@mwheels: https://github.com/mwheels
|
||||||
.. _@nuschk: https://github.com/nuschk
|
.. _@nuschk: https://github.com/nuschk
|
||||||
|
.. _@puru02: https://github.com/puru02
|
||||||
.. _@RignonNoel: https://github.com/RignonNoel
|
.. _@RignonNoel: https://github.com/RignonNoel
|
||||||
.. _@sebashwa: https://github.com/sebashwa
|
.. _@sebashwa: https://github.com/sebashwa
|
||||||
.. _@sebbacon: https://github.com/sebbacon
|
.. _@sebbacon: https://github.com/sebbacon
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class PostmarkTrackingWebhookView(PostmarkBaseWebhookView):
|
|||||||
'Delivery': EventType.DELIVERED,
|
'Delivery': EventType.DELIVERED,
|
||||||
'Open': EventType.OPENED,
|
'Open': EventType.OPENED,
|
||||||
'SpamComplaint': EventType.COMPLAINED,
|
'SpamComplaint': EventType.COMPLAINED,
|
||||||
|
'SubscriptionChange': EventType.UNSUBSCRIBED,
|
||||||
'Inbound': EventType.INBOUND, # future, probably
|
'Inbound': EventType.INBOUND, # future, probably
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ class PostmarkTrackingWebhookView(PostmarkBaseWebhookView):
|
|||||||
'InboundError': (EventType.INBOUND_FAILED, None),
|
'InboundError': (EventType.INBOUND_FAILED, None),
|
||||||
'DMARCPolicy': (EventType.REJECTED, RejectReason.BLOCKED),
|
'DMARCPolicy': (EventType.REJECTED, RejectReason.BLOCKED),
|
||||||
'TemplateRenderingFailed': (EventType.FAILED, None),
|
'TemplateRenderingFailed': (EventType.FAILED, None),
|
||||||
|
'ManualSuppression': (EventType.UNSUBSCRIBED, RejectReason.UNSUBSCRIBED),
|
||||||
}
|
}
|
||||||
|
|
||||||
def esp_to_anymail_event(self, esp_event):
|
def esp_to_anymail_event(self, esp_event):
|
||||||
@@ -87,11 +89,21 @@ class PostmarkTrackingWebhookView(PostmarkBaseWebhookView):
|
|||||||
event_type, reject_reason = self.event_types[esp_event['Type']]
|
event_type, reject_reason = self.event_types[esp_event['Type']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
if event_type == EventType.UNSUBSCRIBED:
|
||||||
|
if esp_event['SuppressSending']:
|
||||||
|
# Postmark doesn't provide a way to distinguish between
|
||||||
|
# explicit unsubscribes and bounces
|
||||||
|
try:
|
||||||
|
event_type, reject_reason = self.event_types[esp_event['SuppressionReason']]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
event_type, reject_reason = self.event_types['Subscribe']
|
||||||
|
|
||||||
recipient = getfirst(esp_event, ['Email', 'Recipient'], None) # Email for bounce; Recipient for open
|
recipient = getfirst(esp_event, ['Email', 'Recipient'], None) # Email for bounce; Recipient for open
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestr = getfirst(esp_event, ['DeliveredAt', 'BouncedAt', 'ReceivedAt'])
|
timestr = getfirst(esp_event, ['DeliveredAt', 'BouncedAt', 'ReceivedAt', 'ChangedAt'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
timestamp = None
|
timestamp = None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -220,3 +220,96 @@ class PostmarkDeliveryTestCase(WebhookTestCase):
|
|||||||
with self.assertRaisesMessage(AnymailConfigurationError, errmsg):
|
with self.assertRaisesMessage(AnymailConfigurationError, errmsg):
|
||||||
self.client.post('/anymail/postmark/tracking/', content_type='application/json',
|
self.client.post('/anymail/postmark/tracking/', content_type='application/json',
|
||||||
data=json.dumps({"RecordType": "Inbound"}))
|
data=json.dumps({"RecordType": "Inbound"}))
|
||||||
|
|
||||||
|
def test_unsubscribe(self):
|
||||||
|
raw_event = {
|
||||||
|
"RecordType": "SubscriptionChange",
|
||||||
|
"MessageID": "a4909a96-73d7-4c49-b148-a54522d3f7ac",
|
||||||
|
"ServerID": 23,
|
||||||
|
"MessageStream": "outbound",
|
||||||
|
"ChangedAt": "2022-06-05T17:17:32Z",
|
||||||
|
"Recipient": "john@example.com",
|
||||||
|
"Origin": "Recipient",
|
||||||
|
"SuppressSending": True,
|
||||||
|
"SuppressionReason": "ManualSuppression",
|
||||||
|
"Tag": "welcome-email",
|
||||||
|
"Metadata": {
|
||||||
|
"example": "value",
|
||||||
|
"example_2": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = self.client.post('/anymail/postmark/tracking/',
|
||||||
|
content_type='application/json', data=json.dumps(raw_event))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostmarkTrackingWebhookView,
|
||||||
|
event=ANY, esp_name='Postmark')
|
||||||
|
event = kwargs['event']
|
||||||
|
self.assertIsInstance(event, AnymailTrackingEvent)
|
||||||
|
self.assertEqual(event.event_type, "unsubscribed")
|
||||||
|
self.assertEqual(event.esp_event, raw_event)
|
||||||
|
self.assertEqual(event.timestamp, datetime(2022, 6, 5, 17, 17, 32, tzinfo=utc))
|
||||||
|
self.assertEqual(event.message_id, "a4909a96-73d7-4c49-b148-a54522d3f7ac")
|
||||||
|
self.assertEqual(event.recipient, "john@example.com",)
|
||||||
|
self.assertEqual(event.reject_reason, "unsubscribed")
|
||||||
|
|
||||||
|
def test_resubscribe(self):
|
||||||
|
raw_event = {
|
||||||
|
"RecordType": "SubscriptionChange",
|
||||||
|
"MessageID": "a4909a96-73d7-4c49-b148-a54522d3f7ac",
|
||||||
|
"ServerID": 23,
|
||||||
|
"MessageStream": "outbound",
|
||||||
|
"ChangedAt": "2022-06-05T17:17:32Z",
|
||||||
|
"Recipient": "john@example.com",
|
||||||
|
"Origin": "Recipient",
|
||||||
|
"SuppressSending": False,
|
||||||
|
"SuppressionReason": None,
|
||||||
|
"Tag": "welcome-email",
|
||||||
|
"Metadata": {
|
||||||
|
"example": "value",
|
||||||
|
"example_2": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = self.client.post('/anymail/postmark/tracking/',
|
||||||
|
content_type='application/json', data=json.dumps(raw_event))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostmarkTrackingWebhookView,
|
||||||
|
event=ANY, esp_name='Postmark')
|
||||||
|
event = kwargs['event']
|
||||||
|
self.assertIsInstance(event, AnymailTrackingEvent)
|
||||||
|
self.assertEqual(event.event_type, "subscribed")
|
||||||
|
self.assertEqual(event.esp_event, raw_event)
|
||||||
|
self.assertEqual(event.timestamp, datetime(2022, 6, 5, 17, 17, 32, tzinfo=utc))
|
||||||
|
self.assertEqual(event.message_id, "a4909a96-73d7-4c49-b148-a54522d3f7ac")
|
||||||
|
self.assertEqual(event.recipient, "john@example.com",)
|
||||||
|
self.assertEqual(event.reject_reason, None)
|
||||||
|
|
||||||
|
def test_subscription_change_bounce(self):
|
||||||
|
raw_event = {
|
||||||
|
"RecordType": "SubscriptionChange",
|
||||||
|
"MessageID": "b4cb783d-78ed-43f2-983b-63f55c712dc8",
|
||||||
|
"ServerID": 23,
|
||||||
|
"MessageStream": "outbound",
|
||||||
|
"ChangedAt": "2022-06-05T17:17:32Z",
|
||||||
|
"Recipient": "john@example.com",
|
||||||
|
"Origin": "Recipient",
|
||||||
|
"SuppressSending": True,
|
||||||
|
"SuppressionReason": "HardBounce",
|
||||||
|
"Tag": "my-tag",
|
||||||
|
"Metadata": {
|
||||||
|
"example": "value",
|
||||||
|
"example_2": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = self.client.post('/anymail/postmark/tracking/',
|
||||||
|
content_type='application/json', data=json.dumps(raw_event))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostmarkTrackingWebhookView,
|
||||||
|
event=ANY, esp_name='Postmark')
|
||||||
|
event = kwargs['event']
|
||||||
|
self.assertIsInstance(event, AnymailTrackingEvent)
|
||||||
|
self.assertEqual(event.event_type, "bounced")
|
||||||
|
self.assertEqual(event.esp_event, raw_event)
|
||||||
|
self.assertEqual(event.timestamp, datetime(2022, 6, 5, 17, 17, 32, tzinfo=utc))
|
||||||
|
self.assertEqual(event.message_id, "b4cb783d-78ed-43f2-983b-63f55c712dc8")
|
||||||
|
self.assertEqual(event.recipient, "john@example.com",)
|
||||||
|
self.assertEqual(event.reject_reason, "bounced")
|
||||||
|
|||||||
Reference in New Issue
Block a user