Skip to content

Commit

Permalink
Update changelog, update docs and comments
Browse files Browse the repository at this point in the history
  • Loading branch information
medmunds committed Jul 25, 2024
1 parent c0e732d commit 0f874f2
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 28 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ Release history
.. This extra heading level keeps the ToC from becoming unmanageably long
vNext
-----

*unreleased changes*

Features
~~~~~~~~

* **Brevo:** Support Brevo's new "Complaint," "Error" and "Loaded by proxy"
tracking events. (Thanks to `@originell`_ for the update.)


v11.0.1
-------

Expand Down Expand Up @@ -1695,6 +1707,7 @@ Features
.. _@mounirmesselmeni: https://github.com/mounirmesselmeni
.. _@mwheels: https://github.com/mwheels
.. _@nuschk: https://github.com/nuschk
.. _@originell: https://github.com/originell
.. _@puru02: https://github.com/puru02
.. _@RignonNoel: https://github.com/RignonNoel
.. _@sblondon: https://github.com/sblondon
Expand Down
22 changes: 12 additions & 10 deletions anymail/webhooks/brevo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ def parse_events(self, request):
)
return [self.esp_to_anymail_event(esp_event)]

# Map Brevo event type -> Anymail normalized (event type, reject reason).
event_types = {
# Map Brevo event type: Anymail normalized (event type, reject reason)
#
# received even if a message isn't sent (e.g., before "blocked" or "delivered"):
# Treat "request" as QUEUED rather than SENT, because it may be received
# even if message won't actually be sent (e.g., before "blocked").
"request": (EventType.QUEUED, None),
"delivered": (EventType.DELIVERED, None),
"hard_bounce": (EventType.BOUNCED, RejectReason.BOUNCED),
Expand All @@ -53,18 +53,20 @@ def parse_events(self, request):
"complaint": (EventType.COMPLAINED, RejectReason.SPAM),
"invalid_email": (EventType.BOUNCED, RejectReason.INVALID),
"deferred": (EventType.DEFERRED, None),
# see also unique_opened below
"opened": (EventType.OPENED, None),
# first open; see also opened above
# Brevo has four types of opened events:
# - "unique_opened": first time opened
# - "opened": subsequent opens
# - "unique_proxy_opened": first time opened via proxy (e.g., Apple Mail)
# - "proxy_open": subsequent opens via proxy
# Treat all of these as OPENED.
"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)
"opened": (EventType.OPENED, None),
"unique_proxy_open": (EventType.OPENED, None),
"proxy_open": (EventType.OPENED, None),
"click": (EventType.CLICKED, None),
"unsubscribe": (EventType.UNSUBSCRIBED, None),
"error": (EventType.FAILED, None),
# shouldn't occur for transactional messages:
# ("list_addition" shouldn't occur for transactional messages.)
"list_addition": (EventType.SUBSCRIBED, None),
}

Expand Down
48 changes: 30 additions & 18 deletions docs/esps/brevo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -332,29 +332,36 @@ Be sure to select the checkboxes for all the event types you want to receive. (A
sure you are in the "Transactional" section of their site; Brevo has a separate set
of "Campaign" webhooks, which don't apply to messages sent through Anymail.)

If you are interested in tracking opens, note that Brevo has both "First opening"
and an "Known open" event types. The latter seems to be generated only for the second
and subsequent opens. Anymail normalizes both types to "opened." To track unique opens
enable only "First opening," or to track all message opens enable both. (Brevo used to
deliver both events for the first open, so be sure to check their current behavior
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 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".
If you are interested in tracking opens, note that Brevo has four different
open event types:

* "First opening": the first time a message is opened by a particular recipient.
(Brevo event type "opened")
* "Known open": the second and subsequent opens. (Brevo event type "unique_opened")
* "Loaded by proxy": a message's tracking pixel is loaded by a proxy service
intended to protect users' IP addresses. See Brevo's article on
`Apple's Mail Privacy Protection`_ for more details. As of July, 2024, Brevo
seems to deliver this event only for the second and subsequent loads by the
proxy service. (Brevo event type "proxy_open")
* "First open but loaded by proxy": the first time a message's tracking pixel
is loaded by a proxy service for a particular recipient. As of July, 2024,
this event has not yet been exposed in Brevo's webhook control panel, and
you must contact Brevo support to enable it. (Brevo event type "unique_proxy_opened")

Anymail normalizes all of these to "opened." If you need to distinguish the
specific Brevo event types, examine the raw
:attr:`~anymail.signals.AnymailTrackingEvent.esp_event`, e.g.:
``if event.esp_event["event"] == "unique_opened": …``.

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).
failed, unsubscribed, subscribed (though subscribed should never occur for transactional email).

For events that occur in rapid succession, Brevo frequently delivers them out of order.
For example, it's not uncommon to receive a "delivered" event before the corresponding "queued."
Also, note that "queued" may be received even if Brevo will not actually send the message.
(E.g., if a recipient is on your blocked list due to a previous bounce, you may receive
"queued" followed by "rejected.")

The event's :attr:`~anymail.signals.AnymailTrackingEvent.esp_event` field will be
a `dict` of raw webhook data received from Brevo.
Expand All @@ -365,9 +372,14 @@ a `dict` of raw webhook data received from Brevo.
than "brevo". The old URL will still work, but is deprecated. See :ref:`brevo-rename`
below.

.. versionchanged:: 11.1

Added support for Brevo's "Complaint," "Error" and "Loaded by proxy" events.


.. _Transactional > Email > Settings > Webhook: https://app-smtp.brevo.com/webhook
.. _Brevo docs on this: https://help.brevo.com/hc/en-us/articles/4406537065618-How-to-handle-changes-in-Apple-s-Mail-Privacy-Protection
.. _Apple's Mail Privacy Protection:
https://help.brevo.com/hc/en-us/articles/4406537065618-How-to-handle-changes-in-Apple-s-Mail-Privacy-Protection


.. _brevo-inbound:
Expand Down

0 comments on commit 0f874f2

Please sign in to comment.