mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Sendinblue: Support send_at
Add support for delayed sending via Sendinblue's public beta "scheduledAt" parameter. Closes #280
This commit is contained in:
@@ -40,6 +40,9 @@ Breaking changes
|
||||
Features
|
||||
~~~~~~~~
|
||||
|
||||
* **Sendinblue:** Support delayed sending using Anymail's `send_at` option.
|
||||
(Thanks to `@dimitrisor`_ for noting Sendinblue's public beta release
|
||||
of this capability.)
|
||||
* Support customizing the requests.Session for requests-based backends,
|
||||
and document how this can be used to mount an adapter that simplifies
|
||||
automatic retry logic. (Thanks to `@dgilmanAIDENTIFIED`_.)
|
||||
@@ -1366,6 +1369,7 @@ Features
|
||||
.. _@coupa-anya: https://github.com/coupa-anya
|
||||
.. _@decibyte: https://github.com/decibyte
|
||||
.. _@dgilmanAIDENTIFIED: https://github.com/dgilmanAIDENTIFIED
|
||||
.. _@dimitrisor: https://github.com/dimitrisor
|
||||
.. _@dominik-lekse: https://github.com/dominik-lekse
|
||||
.. _@erikdrums: https://github.com/erikdrums
|
||||
.. _@ewingrj: https://github.com/ewingrj
|
||||
|
||||
@@ -162,3 +162,10 @@ class SendinBluePayload(RequestsPayload):
|
||||
def set_metadata(self, metadata):
|
||||
# SendinBlue expects a single string payload
|
||||
self.data['headers']["X-Mailin-custom"] = self.serialize_json(metadata)
|
||||
|
||||
def set_send_at(self, send_at):
|
||||
try:
|
||||
start_time_iso = send_at.isoformat(timespec="milliseconds")
|
||||
except (AttributeError, TypeError):
|
||||
start_time_iso = send_at # assume user already formatted
|
||||
self.data['scheduledAt'] = start_time_iso
|
||||
|
||||
@@ -40,7 +40,7 @@ Email Service Provider |Amazon SES| |Mailgun| |Mailje
|
||||
:attr:`~AnymailMessage.envelope_sender` Yes Domain only Yes Domain only Yes No No No Yes
|
||||
:attr:`~AnymailMessage.metadata` Yes Yes Yes Yes No Yes Yes Yes Yes
|
||||
:attr:`~AnymailMessage.merge_metadata` No Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.send_at` No Yes No Yes No No Yes No Yes
|
||||
:attr:`~AnymailMessage.send_at` No Yes No Yes No No Yes Yes Yes
|
||||
:attr:`~AnymailMessage.tags` Yes Yes Max 1 tag Yes Max 1 tag Max 1 tag Yes Yes Max 1 tag
|
||||
:attr:`~AnymailMessage.track_clicks` No Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.track_opens` No Yes Yes Yes No Yes Yes No Yes
|
||||
|
||||
@@ -78,18 +78,20 @@ set a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to
|
||||
a `dict` that will be merged into the json sent to Sendinblue's
|
||||
`smtp/email API`_.
|
||||
|
||||
Example:
|
||||
For example, you could set Sendinblue's *batchId* for use with
|
||||
their `batched scheduled sending`_:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message.esp_extra = {
|
||||
'hypotheticalFutureSendinblueParam': '2022', # merged into send params
|
||||
'batchId': '275d3289-d5cb-4768-9460-a990054b6c81', # merged into send params
|
||||
}
|
||||
|
||||
|
||||
(You can also set `"esp_extra"` in Anymail's :ref:`global send defaults <send-defaults>`
|
||||
to apply it to all messages.)
|
||||
|
||||
.. _batched scheduled sending: https://developers.sendinblue.com/docs/schedule-batch-sendings
|
||||
.. _smtp/email API: https://developers.sendinblue.com/v3.0/reference#sendtransacemail
|
||||
|
||||
|
||||
@@ -141,8 +143,10 @@ Sendinblue can handle.
|
||||
as a JSON-encoded string using their :mailheader:`X-Mailin-custom` email header.
|
||||
The metadata is available in tracking webhooks.
|
||||
|
||||
**No delayed sending**
|
||||
Sendinblue does not support :attr:`~anymail.message.AnymailMessage.send_at`.
|
||||
**Delayed sending**
|
||||
.. versionadded:: 9.0
|
||||
Earlier versions of Anymail did not support :attr:`~anymail.message.AnymailMessage.send_at`
|
||||
with Sendinblue.
|
||||
|
||||
**No click-tracking or open-tracking options**
|
||||
Sendinblue does not provide a way to control open or click tracking for individual
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
from base64 import b64encode, b64decode
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.image import MIMEImage
|
||||
@@ -294,10 +294,41 @@ class SendinBlueBackendAnymailFeatureTests(SendinBlueBackendMockAPITestCase):
|
||||
|
||||
with override_current_timezone(utc_plus_6):
|
||||
# Timezone-aware datetime converted to UTC:
|
||||
self.message.send_at = datetime(2016, 3, 4, 5, 6, 7, tzinfo=utc_minus_8)
|
||||
self.message.send_at = datetime(2016, 3, 4, 5, 6, 7, 8000, tzinfo=utc_minus_8)
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['scheduledAt'], "2016-03-04T05:06:07.008-08:00")
|
||||
|
||||
with self.assertRaises(AnymailUnsupportedFeature):
|
||||
self.message.send()
|
||||
# Explicit UTC:
|
||||
self.message.send_at = datetime(2016, 3, 4, 5, 6, 7, tzinfo=timezone.utc)
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['scheduledAt'], "2016-03-04T05:06:07.000+00:00")
|
||||
|
||||
# Timezone-naive datetime assumed to be Django current_timezone
|
||||
# (also checks stripping microseconds)
|
||||
self.message.send_at = datetime(2022, 10, 11, 12, 13, 14, 567)
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['scheduledAt'], "2022-10-11T12:13:14.000+06:00")
|
||||
|
||||
# Date-only treated as midnight in current timezone
|
||||
self.message.send_at = date(2022, 10, 22)
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['scheduledAt'], "2022-10-22T00:00:00.000+06:00")
|
||||
|
||||
# POSIX timestamp
|
||||
self.message.send_at = 1651820889 # 2022-05-06 07:08:09 UTC
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['scheduledAt'], "2022-05-06T07:08:09.000+00:00")
|
||||
|
||||
# String passed unchanged (this is *not* portable between ESPs)
|
||||
self.message.send_at = "2022-10-13T18:02:00.123-11:30"
|
||||
self.message.send()
|
||||
data = self.get_api_call_json()
|
||||
self.assertEqual(data['scheduledAt'], "2022-10-13T18:02:00.123-11:30")
|
||||
|
||||
def test_tag(self):
|
||||
self.message.tags = ["receipt", "multiple"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from email.utils import formataddr
|
||||
|
||||
from django.test import SimpleTestCase, override_settings, tag
|
||||
@@ -55,6 +56,7 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
self.assertEqual(anymail_status.message_id, message_id)
|
||||
|
||||
def test_all_options(self):
|
||||
send_at = datetime.now() + timedelta(minutes=2)
|
||||
message = AnymailMessage(
|
||||
subject="Anymail SendinBlue all-options integration test",
|
||||
body="This is the text body",
|
||||
@@ -66,6 +68,7 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
||||
headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3},
|
||||
|
||||
metadata={"meta1": "simple string", "meta2": 2},
|
||||
send_at=send_at,
|
||||
tags=["tag 1", "tag 2"],
|
||||
)
|
||||
message.attach_alternative('<p>HTML content</p>', "text/html") # SendinBlue requires an HTML body
|
||||
|
||||
Reference in New Issue
Block a user