Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resend: add support for send_at #400

Merged
merged 1 commit into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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