import json from datetime import datetime from django.utils.timezone import utc from .base import AnymailBaseWebhookView from ..signals import AnymailTrackingEvent, EventType, RejectReason, tracking class SendinBlueTrackingWebhookView(AnymailBaseWebhookView): """Handler for SendinBlue delivery and engagement tracking webhooks""" esp_name = "SendinBlue" signal = tracking def parse_events(self, request): esp_event = json.loads(request.body.decode('utf-8')) 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) "request": (EventType.QUEUED, None), # received even if message won't be sent (e.g., before "blocked") "delivered": (EventType.DELIVERED, None), "hard_bounce": (EventType.BOUNCED, RejectReason.BOUNCED), "soft_bounce": (EventType.BOUNCED, RejectReason.BOUNCED), "blocked": (EventType.REJECTED, RejectReason.BLOCKED), "spam": (EventType.COMPLAINED, RejectReason.SPAM), "invalid_email": (EventType.BOUNCED, RejectReason.INVALID), "deferred": (EventType.DEFERRED, None), "opened": (EventType.OPENED, None), # see also unique_opened below "click": (EventType.CLICKED, None), "unsubscribe": (EventType.UNSUBSCRIBED, None), "list_addition": (EventType.SUBSCRIBED, None), # shouldn't occur for transactional messages "unique_opened": (EventType.OPENED, None), # you'll *also* receive an "opened" } def esp_to_anymail_event(self, esp_event): esp_type = esp_event.get("event") event_type, reject_reason = self.event_types.get(esp_type, (EventType.UNKNOWN, None)) recipient = esp_event.get("email") try: # SendinBlue 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 timestamp = datetime.fromtimestamp(esp_event["ts_epoch"] / 1000.0, tz=utc) except (KeyError, ValueError): timestamp = None try: tags = [esp_event["tag"]] except KeyError: tags = [] try: metadata = json.loads(esp_event["X-Mailin-custom"]) except (KeyError, TypeError): metadata = {} return AnymailTrackingEvent( description=None, esp_event=esp_event, event_id=None, # SendinBlue doesn't provide a unique event id event_type=event_type, message_id=esp_event.get("message-id"), metadata=metadata, mta_response=esp_event.get("reason"), recipient=recipient, reject_reason=reject_reason, tags=tags, timestamp=timestamp, user_agent=None, click_url=esp_event.get("link"), )