mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
Resend: add support for send_at
Resend's new `scheduled_at` API field allows delayed sending (though not with attachments or batch sending). Closes #396.
This commit is contained in:
@@ -36,6 +36,11 @@ Breaking changes
|
|||||||
|
|
||||||
* Require **Django 4.0 or later** and Python 3.8 or later.
|
* Require **Django 4.0 or later** and Python 3.8 or later.
|
||||||
|
|
||||||
|
Features
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
* **Resend:** Add support for ``send_at``.
|
||||||
|
|
||||||
Other
|
Other
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -266,8 +266,16 @@ class ResendPayload(RequestsPayload):
|
|||||||
)
|
)
|
||||||
self.metadata = metadata # may be needed for batch send in serialize_data
|
self.metadata = metadata # may be needed for batch send in serialize_data
|
||||||
|
|
||||||
# Resend doesn't support delayed sending
|
def set_send_at(self, send_at):
|
||||||
# def set_send_at(self, send_at):
|
try:
|
||||||
|
# Resend can't handle microseconds; truncate to milliseconds if necessary.
|
||||||
|
send_at = send_at.isoformat(
|
||||||
|
timespec="milliseconds" if send_at.microsecond else "seconds"
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
# User is responsible for formatting their own string
|
||||||
|
pass
|
||||||
|
self.data["scheduled_at"] = send_at
|
||||||
|
|
||||||
def set_tags(self, tags):
|
def set_tags(self, tags):
|
||||||
# Send tags using a custom X-Tags header.
|
# Send tags using a custom X-Tags header.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Email Service Provider,:ref:`amazon-ses-backend`,:ref:`brevo-backend`,:ref:`mail
|
|||||||
:attr:`~AnymailMessage.merge_headers`,Yes [#caveats]_,Yes,No,Yes,Yes,No,No,Yes,Yes,Yes,Yes [#caveats]_,Yes [#caveats]_
|
:attr:`~AnymailMessage.merge_headers`,Yes [#caveats]_,Yes,No,Yes,Yes,No,No,Yes,Yes,Yes,Yes [#caveats]_,Yes [#caveats]_
|
||||||
:attr:`~AnymailMessage.metadata`,Yes,Yes,No,Yes,Yes,Yes,No,Yes,Yes,Yes,Yes,Yes
|
:attr:`~AnymailMessage.metadata`,Yes,Yes,No,Yes,Yes,Yes,No,Yes,Yes,Yes,Yes,Yes
|
||||||
:attr:`~AnymailMessage.merge_metadata`,Yes [#caveats]_,Yes,No,Yes,Yes,Yes,No,Yes,Yes,Yes,Yes,Yes
|
:attr:`~AnymailMessage.merge_metadata`,Yes [#caveats]_,Yes,No,Yes,Yes,Yes,No,Yes,Yes,Yes,Yes,Yes
|
||||||
:attr:`~AnymailMessage.send_at`,No,Yes,Yes,Yes,No,Yes,No,No,No,Yes,Yes,Yes
|
:attr:`~AnymailMessage.send_at`,No,Yes,Yes,Yes,No,Yes,No,No,Yes,Yes,Yes,Yes
|
||||||
:attr:`~AnymailMessage.tags`,Yes,Yes,Yes,Yes,Max 1 tag,Yes,Max 1 tag,Max 1 tag,Yes,Yes,Max 1 tag,Yes
|
:attr:`~AnymailMessage.tags`,Yes,Yes,Yes,Yes,Max 1 tag,Yes,Max 1 tag,Max 1 tag,Yes,Yes,Max 1 tag,Yes
|
||||||
:attr:`~AnymailMessage.track_clicks`,No [#nocontrol]_,No [#nocontrol]_,Yes,Yes,Yes,Yes,No,Yes,No,Yes,Yes,Yes
|
:attr:`~AnymailMessage.track_clicks`,No [#nocontrol]_,No [#nocontrol]_,Yes,Yes,Yes,Yes,No,Yes,No,Yes,Yes,Yes
|
||||||
:attr:`~AnymailMessage.track_opens`,No [#nocontrol]_,No [#nocontrol]_,Yes,Yes,Yes,Yes,No,Yes,No,Yes,Yes,Yes
|
:attr:`~AnymailMessage.track_opens`,No [#nocontrol]_,No [#nocontrol]_,Yes,Yes,Yes,Yes,No,Yes,No,Yes,Yes,Yes
|
||||||
|
|||||||
|
@@ -182,8 +182,12 @@ anyway---see :ref:`unsupported-features`.
|
|||||||
tracking features can only be configured at the domain level
|
tracking features can only be configured at the domain level
|
||||||
in Resend's control panel.
|
in Resend's control panel.
|
||||||
|
|
||||||
**No delayed sending**
|
**No attachments with delayed sending**
|
||||||
Resend does not support :attr:`~anymail.message.AnymailMessage.send_at`.
|
Resend does not support attachments or batch sending features when using
|
||||||
|
:attr:`~anymail.message.AnymailMessage.send_at`.
|
||||||
|
|
||||||
|
.. versionchanged:: 12.0
|
||||||
|
Resend now supports :attr:`~anymail.message.AnymailMessage.send_at`.
|
||||||
|
|
||||||
**No envelope sender**
|
**No envelope sender**
|
||||||
Resend does not support specifying the
|
Resend does not support specifying the
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from datetime import date, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
from email.mime.image import MIMEImage
|
from email.mime.image import MIMEImage
|
||||||
@@ -8,6 +9,10 @@ from email.utils import formataddr
|
|||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import SimpleTestCase, override_settings, tag
|
from django.test import SimpleTestCase, override_settings, tag
|
||||||
|
from django.utils.timezone import (
|
||||||
|
get_fixed_timezone,
|
||||||
|
override as override_current_timezone,
|
||||||
|
)
|
||||||
|
|
||||||
from anymail.exceptions import (
|
from anymail.exceptions import (
|
||||||
AnymailAPIError,
|
AnymailAPIError,
|
||||||
@@ -416,9 +421,41 @@ class ResendBackendAnymailFeatureTests(ResendBackendMockAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_send_at(self):
|
def test_send_at(self):
|
||||||
self.message.send_at = 1651820889 # 2022-05-06 07:08:09 UTC
|
utc_plus_6 = get_fixed_timezone(6 * 60)
|
||||||
with self.assertRaisesMessage(AnymailUnsupportedFeature, "send_at"):
|
utc_minus_8 = get_fixed_timezone(-8 * 60)
|
||||||
|
|
||||||
|
with override_current_timezone(utc_plus_6):
|
||||||
|
# Timezone-naive datetime assumed to be Django current_timezone
|
||||||
|
self.message.send_at = datetime(2022, 10, 11, 12, 13, 14, 123456)
|
||||||
self.message.send()
|
self.message.send()
|
||||||
|
data = self.get_api_call_json()
|
||||||
|
# (Resend can't handle microseconds; truncate to milliseconds.)
|
||||||
|
self.assertEqual(data["scheduled_at"], "2022-10-11T12:13:14.123+06:00")
|
||||||
|
|
||||||
|
# Timezone-aware datetime converted to UTC:
|
||||||
|
self.message.send_at = datetime(2016, 3, 4, 5, 6, 7, tzinfo=utc_minus_8)
|
||||||
|
self.message.send()
|
||||||
|
data = self.get_api_call_json()
|
||||||
|
self.assertEqual(data["scheduled_at"], "2016-03-04T05:06:07-08:00")
|
||||||
|
|
||||||
|
# Date-only treated as midnight in current timezone
|
||||||
|
# (which probably won't send since it's not in the future)
|
||||||
|
self.message.send_at = date(2022, 10, 22)
|
||||||
|
self.message.send()
|
||||||
|
data = self.get_api_call_json()
|
||||||
|
self.assertEqual(data["scheduled_at"], "2022-10-22T00:00:00+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["scheduled_at"], "2022-05-06T07:08:09+00:00")
|
||||||
|
|
||||||
|
# String passed unchanged (this is *not* portable between ESPs)
|
||||||
|
self.message.send_at = "2013-11-12T01:02:03Z"
|
||||||
|
self.message.send()
|
||||||
|
data = self.get_api_call_json()
|
||||||
|
self.assertEqual(data["scheduled_at"], "2013-11-12T01:02:03Z")
|
||||||
|
|
||||||
def test_tags(self):
|
def test_tags(self):
|
||||||
self.message.tags = ["receipt", "reorder test 12"]
|
self.message.tags = ["receipt", "reorder test 12"]
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ class ResendBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
|
|||||||
headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3},
|
headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3},
|
||||||
metadata={"meta1": "simple string", "meta2": 2},
|
metadata={"meta1": "simple string", "meta2": 2},
|
||||||
tags=["tag 1", "tag 2"],
|
tags=["tag 1", "tag 2"],
|
||||||
|
# Resend supports send_at or attachments, but not both at once.
|
||||||
|
# send_at=datetime.now() + timedelta(minutes=2),
|
||||||
)
|
)
|
||||||
message.attach_alternative("<p>HTML content</p>", "text/html")
|
message.attach_alternative("<p>HTML content</p>", "text/html")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user