diff --git a/anymail/webhooks/brevo.py b/anymail/webhooks/brevo.py index 87eb3925..baf5eeb4 100644 --- a/anymail/webhooks/brevo.py +++ b/anymail/webhooks/brevo.py @@ -59,6 +59,8 @@ def parse_events(self, request): "unique_opened": (EventType.OPENED, None), # open, but "loaded via proxy" (e.g., Apple Mail) "proxy_open": (EventType.OPENED, None), + # first open; but "loaded via proxy" (e.g., Apple Mail) + "unique_proxy_open": (EventType.OPENED, None), "click": (EventType.CLICKED, None), "unsubscribe": (EventType.UNSUBSCRIBED, None), "error": (EventType.FAILED, None), diff --git a/docs/esps/brevo.rst b/docs/esps/brevo.rst index d60caaf1..cf190233 100644 --- a/docs/esps/brevo.rst +++ b/docs/esps/brevo.rst @@ -340,11 +340,15 @@ deliver both events for the first open, so be sure to check their current behavi if duplicate first open events might cause problems for you. You might be able to use the event timestamp to de-dupe.). -Furthermore, there is the "Loaded by Proxy" event. Primarily, it is sent for opens -that happen in Apple Mail, due to Apple proxying these to protect user's IP addresses. +Furthermore, there is the "Loaded by Proxy" event. Primarily, it is sent for every open +that happens in Apple Mail, due to Apple proxying these to protect user's IP addresses. Anymail normalizes this to "opened" as well. See the `Brevo docs on this`_ for more details. +Unofficially, there is also a "First open but loaded by Proxy" event. This is not sent +by default. Not even if you check the webhook event "loaded by Proxy" in the Brevo UI. +You can, however, let the support activate it. Anymail will then handle it as "open". + Brevo will report these Anymail :attr:`~anymail.signals.AnymailTrackingEvent.event_type`\s: queued, rejected, bounced, deferred, delivered, opened (see note above), clicked, complained, unsubscribed, subscribed (though this should never occur for transactional email). diff --git a/tests/test_brevo_webhooks.py b/tests/test_brevo_webhooks.py index 6c9099bf..ac2391d2 100644 --- a/tests/test_brevo_webhooks.py +++ b/tests/test_brevo_webhooks.py @@ -435,6 +435,39 @@ def test_proxy_open_event(self): event = kwargs["event"] self.assertEqual(event.event_type, "opened") + def test_unique_proxy_open_event(self): + # Sadly, undocumented in Brevo. + # Equivalent to "First Open but loaded via Proxy". + # This is sent when a tracking pixel is loaded via a 'privacy proxy server'. + # This technique is used by Apple Mail, for example, to protect user's IP + # addresses. + raw_event = { + "event": "unique_proxy_open", + "email": "example@domain.com", + "id": 1, + "date": "2020-10-09 00:00:00", + "message-id": "201798300811.5787683@relay.domain.com", + "subject": "My first Transactional", + "tag": ["transactionalTag"], + "sending_ip": "xxx.xxx.xxx.xxx", + "s_epoch": 1534486682000, + "template_id": 1, + } + response = self.client.post( + "/anymail/brevo/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=BrevoTrackingWebhookView, + event=ANY, + esp_name="Brevo", + ) + event = kwargs["event"] + self.assertEqual(event.event_type, "opened") + def test_clicked_event(self): raw_event = { "event": "click",