Skip to content

Commit

Permalink
Resend: add support for send_at
Browse files Browse the repository at this point in the history
Resend's new `scheduled_at` API field allows delayed sending
(though not with attachments or batch sending).

Closes #396.
  • Loading branch information
medmunds committed Sep 6, 2024
1 parent af6eaea commit 2f2a888
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 7 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ Breaking changes

* Require **Django 4.0 or later** and Python 3.8 or later.

Features
~~~~~~~~

* **Resend:** Add support for ``send_at``.

Other
~~~~~

Expand Down
12 changes: 10 additions & 2 deletions anymail/backends/resend.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,16 @@ def set_metadata(self, metadata):
)
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):
# Send tags using a custom X-Tags header.
Expand Down
2 changes: 1 addition & 1 deletion docs/esps/esp-feature-matrix.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.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.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
Expand Down
8 changes: 6 additions & 2 deletions docs/esps/resend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,12 @@ anyway---see :ref:`unsupported-features`.
tracking features can only be configured at the domain level
in Resend's control panel.

**No delayed sending**
Resend does not support :attr:`~anymail.message.AnymailMessage.send_at`.
**No attachments with delayed sending**
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**
Resend does not support specifying the
Expand Down
41 changes: 39 additions & 2 deletions tests/test_resend_backend.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from base64 import b64encode
from datetime import date, datetime
from decimal import Decimal
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
Expand All @@ -8,6 +9,10 @@
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
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 (
AnymailAPIError,
Expand Down Expand Up @@ -416,9 +421,41 @@ def test_metadata(self):
)

def test_send_at(self):
self.message.send_at = 1651820889 # 2022-05-06 07:08:09 UTC
with self.assertRaisesMessage(AnymailUnsupportedFeature, "send_at"):
utc_plus_6 = get_fixed_timezone(6 * 60)
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()
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):
self.message.tags = ["receipt", "reorder test 12"]
Expand Down
2 changes: 2 additions & 0 deletions tests/test_resend_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def test_all_options(self):
headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3},
metadata={"meta1": "simple string", "meta2": 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")

Expand Down

0 comments on commit 2f2a888

Please sign in to comment.