mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Brevo: Rename SendinBlue to Brevo
- Replace "SendinBlue" with "Brevo" throughout the code. - Maintain deprecated compatibility versions on the old names/URLs. (Split into separate commit to make renamed files more obvious.) - Update docs to reflect change, provide migration advice. - Update integration workflow.
This commit is contained in:
@@ -8,10 +8,10 @@ from .base_requests import AnymailRequestsBackend, RequestsPayload
|
||||
|
||||
class EmailBackend(AnymailRequestsBackend):
|
||||
"""
|
||||
SendinBlue v3 API Email Backend
|
||||
Brevo v3 API Email Backend
|
||||
"""
|
||||
|
||||
esp_name = "SendinBlue"
|
||||
esp_name = "Brevo"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Init options from Django settings"""
|
||||
@@ -33,11 +33,11 @@ class EmailBackend(AnymailRequestsBackend):
|
||||
super().__init__(api_url, **kwargs)
|
||||
|
||||
def build_message_payload(self, message, defaults):
|
||||
return SendinBluePayload(message, defaults, self)
|
||||
return BrevoPayload(message, defaults, self)
|
||||
|
||||
def parse_recipient_status(self, response, payload, message):
|
||||
# SendinBlue doesn't give any detail on a success
|
||||
# https://developers.sendinblue.com/docs/responses
|
||||
# Brevo doesn't give any detail on a success, other than messageId
|
||||
# https://developers.brevo.com/reference/sendtransacemail
|
||||
message_id = None
|
||||
message_ids = []
|
||||
|
||||
@@ -51,7 +51,7 @@ class EmailBackend(AnymailRequestsBackend):
|
||||
message_ids = parsed_response["messageIds"]
|
||||
except (KeyError, TypeError) as err:
|
||||
raise AnymailRequestsAPIError(
|
||||
"Invalid SendinBlue API response format",
|
||||
"Invalid Brevo API response format",
|
||||
email_message=message,
|
||||
payload=payload,
|
||||
response=response,
|
||||
@@ -70,7 +70,7 @@ class EmailBackend(AnymailRequestsBackend):
|
||||
return recipient_status
|
||||
|
||||
|
||||
class SendinBluePayload(RequestsPayload):
|
||||
class BrevoPayload(RequestsPayload):
|
||||
def __init__(self, message, defaults, backend, *args, **kwargs):
|
||||
self.all_recipients = [] # used for backend.parse_recipient_status
|
||||
self.to_recipients = [] # used for backend.parse_recipient_status
|
||||
@@ -124,7 +124,7 @@ class SendinBluePayload(RequestsPayload):
|
||||
|
||||
@staticmethod
|
||||
def email_object(email):
|
||||
"""Converts EmailAddress to SendinBlue API array"""
|
||||
"""Converts EmailAddress to Brevo API array"""
|
||||
email_object = dict()
|
||||
email_object["email"] = email.addr_spec
|
||||
if email.display_name:
|
||||
@@ -147,14 +147,14 @@ class SendinBluePayload(RequestsPayload):
|
||||
self.data["subject"] = subject
|
||||
|
||||
def set_reply_to(self, emails):
|
||||
# SendinBlue only supports a single address in the reply_to API param.
|
||||
# Brevo only supports a single address in the reply_to API param.
|
||||
if len(emails) > 1:
|
||||
self.unsupported_feature("multiple reply_to addresses")
|
||||
if len(emails) > 0:
|
||||
self.data["replyTo"] = self.email_object(emails[0])
|
||||
|
||||
def set_extra_headers(self, headers):
|
||||
# SendinBlue requires header values to be strings (not integers) as of 11/2022.
|
||||
# Brevo requires header values to be strings (not integers) as of 11/2022.
|
||||
# Stringify ints and floats; anything else is the caller's responsibility.
|
||||
self.data["headers"].update(
|
||||
{
|
||||
@@ -182,7 +182,7 @@ class SendinBluePayload(RequestsPayload):
|
||||
self.data["htmlContent"] = body
|
||||
|
||||
def add_attachment(self, attachment):
|
||||
"""Converts attachments to SendinBlue API {name, base64} array"""
|
||||
"""Converts attachments to Brevo API {name, base64} array"""
|
||||
att = {
|
||||
"name": attachment.name or "",
|
||||
"content": attachment.b64content,
|
||||
@@ -204,7 +204,7 @@ class SendinBluePayload(RequestsPayload):
|
||||
self.data["params"] = merge_global_data
|
||||
|
||||
def set_metadata(self, metadata):
|
||||
# SendinBlue expects a single string payload
|
||||
# Brevo expects a single string payload
|
||||
self.data["headers"]["X-Mailin-custom"] = self.serialize_json(metadata)
|
||||
self.metadata = metadata # needed in serialize_data for batch send
|
||||
|
||||
@@ -4,6 +4,7 @@ from .webhooks.amazon_ses import (
|
||||
AmazonSESInboundWebhookView,
|
||||
AmazonSESTrackingWebhookView,
|
||||
)
|
||||
from .webhooks.brevo import BrevoInboundWebhookView, BrevoTrackingWebhookView
|
||||
from .webhooks.mailersend import (
|
||||
MailerSendInboundWebhookView,
|
||||
MailerSendTrackingWebhookView,
|
||||
@@ -15,10 +16,6 @@ from .webhooks.postal import PostalInboundWebhookView, PostalTrackingWebhookView
|
||||
from .webhooks.postmark import PostmarkInboundWebhookView, PostmarkTrackingWebhookView
|
||||
from .webhooks.resend import ResendTrackingWebhookView
|
||||
from .webhooks.sendgrid import SendGridInboundWebhookView, SendGridTrackingWebhookView
|
||||
from .webhooks.sendinblue import (
|
||||
SendinBlueInboundWebhookView,
|
||||
SendinBlueTrackingWebhookView,
|
||||
)
|
||||
from .webhooks.sparkpost import (
|
||||
SparkPostInboundWebhookView,
|
||||
SparkPostTrackingWebhookView,
|
||||
@@ -32,6 +29,11 @@ urlpatterns = [
|
||||
AmazonSESInboundWebhookView.as_view(),
|
||||
name="amazon_ses_inbound_webhook",
|
||||
),
|
||||
path(
|
||||
"brevo/inbound/",
|
||||
BrevoInboundWebhookView.as_view(),
|
||||
name="brevo_inbound_webhook",
|
||||
),
|
||||
path(
|
||||
"mailersend/inbound/",
|
||||
MailerSendInboundWebhookView.as_view(),
|
||||
@@ -66,11 +68,6 @@ urlpatterns = [
|
||||
SendGridInboundWebhookView.as_view(),
|
||||
name="sendgrid_inbound_webhook",
|
||||
),
|
||||
path(
|
||||
"sendinblue/inbound/",
|
||||
SendinBlueInboundWebhookView.as_view(),
|
||||
name="sendinblue_inbound_webhook",
|
||||
),
|
||||
path(
|
||||
"sparkpost/inbound/",
|
||||
SparkPostInboundWebhookView.as_view(),
|
||||
@@ -81,6 +78,11 @@ urlpatterns = [
|
||||
AmazonSESTrackingWebhookView.as_view(),
|
||||
name="amazon_ses_tracking_webhook",
|
||||
),
|
||||
path(
|
||||
"brevo/tracking/",
|
||||
BrevoTrackingWebhookView.as_view(),
|
||||
name="brevo_tracking_webhook",
|
||||
),
|
||||
path(
|
||||
"mailersend/tracking/",
|
||||
MailerSendTrackingWebhookView.as_view(),
|
||||
@@ -116,11 +118,6 @@ urlpatterns = [
|
||||
SendGridTrackingWebhookView.as_view(),
|
||||
name="sendgrid_tracking_webhook",
|
||||
),
|
||||
path(
|
||||
"sendinblue/tracking/",
|
||||
SendinBlueTrackingWebhookView.as_view(),
|
||||
name="sendinblue_tracking_webhook",
|
||||
),
|
||||
path(
|
||||
"sparkpost/tracking/",
|
||||
SparkPostTrackingWebhookView.as_view(),
|
||||
|
||||
@@ -19,12 +19,14 @@ from ..utils import get_anymail_setting
|
||||
from .base import AnymailBaseWebhookView
|
||||
|
||||
|
||||
class SendinBlueBaseWebhookView(AnymailBaseWebhookView):
|
||||
esp_name = "SendinBlue"
|
||||
class BrevoBaseWebhookView(AnymailBaseWebhookView):
|
||||
esp_name = "Brevo"
|
||||
|
||||
|
||||
class SendinBlueTrackingWebhookView(SendinBlueBaseWebhookView):
|
||||
"""Handler for SendinBlue delivery and engagement tracking webhooks"""
|
||||
class BrevoTrackingWebhookView(BrevoBaseWebhookView):
|
||||
"""Handler for Brevo delivery and engagement tracking webhooks"""
|
||||
|
||||
# https://developers.brevo.com/docs/transactional-webhooks
|
||||
|
||||
signal = tracking
|
||||
|
||||
@@ -33,15 +35,13 @@ class SendinBlueTrackingWebhookView(SendinBlueBaseWebhookView):
|
||||
if "items" in esp_event:
|
||||
# This is an inbound webhook post
|
||||
raise AnymailConfigurationError(
|
||||
"You seem to have set SendinBlue's *inbound* webhook URL "
|
||||
"to Anymail's SendinBlue *tracking* webhook URL."
|
||||
f"You seem to have set Brevo's *inbound* webhook URL "
|
||||
f"to Anymail's {self.esp_name} *tracking* webhook URL."
|
||||
)
|
||||
return [self.esp_to_anymail_event(esp_event)]
|
||||
|
||||
# SendinBlue's webhook payload data doesn't seem to be documented anywhere.
|
||||
# There's a list of webhook events at https://apidocs.sendinblue.com/webhooks/#3.
|
||||
event_types = {
|
||||
# Map SendinBlue event type: Anymail normalized (event type, reject reason)
|
||||
# Map Brevo event type: Anymail normalized (event type, reject reason)
|
||||
# received even if message won't be sent (e.g., before "blocked"):
|
||||
"request": (EventType.QUEUED, None),
|
||||
"delivered": (EventType.DELIVERED, None),
|
||||
@@ -67,7 +67,7 @@ class SendinBlueTrackingWebhookView(SendinBlueBaseWebhookView):
|
||||
recipient = esp_event.get("email")
|
||||
|
||||
try:
|
||||
# SendinBlue supplies "ts", "ts_event" and "date" fields, which seem to be
|
||||
# Brevo supplies "ts", "ts_event" and "date" fields, which seem to be
|
||||
# based on the timezone set in the account preferences (and possibly with
|
||||
# inconsistent DST adjustment). "ts_epoch" is the only field that seems to
|
||||
# be consistently UTC; it's in milliseconds
|
||||
@@ -98,7 +98,7 @@ class SendinBlueTrackingWebhookView(SendinBlueBaseWebhookView):
|
||||
return AnymailTrackingEvent(
|
||||
description=None,
|
||||
esp_event=esp_event,
|
||||
# SendinBlue doesn't provide a unique event id:
|
||||
# Brevo doesn't provide a unique event id:
|
||||
event_id=None,
|
||||
event_type=event_type,
|
||||
message_id=esp_event.get("message-id"),
|
||||
@@ -113,8 +113,10 @@ class SendinBlueTrackingWebhookView(SendinBlueBaseWebhookView):
|
||||
)
|
||||
|
||||
|
||||
class SendinBlueInboundWebhookView(SendinBlueBaseWebhookView):
|
||||
"""Handler for SendinBlue inbound email webhooks"""
|
||||
class BrevoInboundWebhookView(BrevoBaseWebhookView):
|
||||
"""Handler for Brevo inbound email webhooks"""
|
||||
|
||||
# https://developers.brevo.com/docs/inbound-parse-webhooks#parsed-email-payload
|
||||
|
||||
signal = inbound
|
||||
|
||||
@@ -141,10 +143,10 @@ class SendinBlueInboundWebhookView(SendinBlueBaseWebhookView):
|
||||
try:
|
||||
esp_events = payload["items"]
|
||||
except KeyError:
|
||||
# This is not n inbound webhook post
|
||||
# This is not an inbound webhook post
|
||||
raise AnymailConfigurationError(
|
||||
"You seem to have set SendinBlue's *tracking* webhook URL "
|
||||
"to Anymail's SendinBlue *inbound* webhook URL."
|
||||
f"You seem to have set Brevo's *tracking* webhook URL "
|
||||
f"to Anymail's {self.esp_name} *inbound* webhook URL."
|
||||
)
|
||||
else:
|
||||
return [self.esp_to_anymail_event(esp_event) for esp_event in esp_events]
|
||||
@@ -199,7 +201,7 @@ class SendinBlueInboundWebhookView(SendinBlueBaseWebhookView):
|
||||
)
|
||||
|
||||
def _fetch_attachment(self, attachment):
|
||||
# Download attachment content from SendinBlue API.
|
||||
# Download attachment content from Brevo API.
|
||||
# FUTURE: somehow defer download until attachment is accessed?
|
||||
token = attachment["DownloadToken"]
|
||||
url = urljoin(self.api_url, f"inbound/attachments/{quote(token, safe='')}")
|
||||
Reference in New Issue
Block a user