Resend
Anymail integrates Django with the Resend transactional email service, using their send-email API endpoint.
Added in version 10.2.
Installation
Anymail uses the svix package to validate Resend webhook signatures.
If you will use Anymail’s status tracking webhook
with Resend, and you want to use webhook signature validation, be sure
to include the [resend]
option when you install Anymail:
$ python -m pip install 'django-anymail[resend]'
(Or separately run python -m pip install svix
.)
The svix package pulls in several other dependencies, so its use
is optional in Anymail. See Status tracking webhooks below for details.
To avoid installing svix with Anymail, just omit the [resend]
option.
Settings
EMAIL_BACKEND
To use Anymail’s Resend backend, set:
EMAIL_BACKEND = "anymail.backends.resend.EmailBackend"
in your settings.py.
RESEND_API_KEY
Required for sending. An API key from your Resend API Keys. Anymail needs only “sending access” permission; “full access” is not recommended.
ANYMAIL = { ... "RESEND_API_KEY": "re_...", }
Anymail will also look for RESEND_API_KEY
at the
root of the settings file if neither ANYMAIL["RESEND_API_KEY"]
nor ANYMAIL_RESEND_API_KEY
is set.
RESEND_SIGNING_SECRET
The Resend webhook signing secret used to verify webhook posts.
Recommended if you are using activity tracking, otherwise not necessary.
(This is separate from Anymail’s
WEBHOOK_SECRET
setting.)
Find this in your Resend Webhooks settings: after adding a webhook, click into its management page and look for “signing secret” near the top.
ANYMAIL = { ... "RESEND_SIGNING_SECRET": "whsec_...", }
If you provide this setting, the svix package is required. See Installation above.
RESEND_API_URL
The base url for calling the Resend API.
The default is RESEND_API_URL = "https://api.resend.com/"
.
(It’s unlikely you would need to change this.)
Limitations and quirks
Resend does not support a few features offered by some other ESPs, and can have unexpected behavior for some common use cases.
Anymail normally raises an AnymailUnsupportedFeature
error when you try to send a message using features that Resend doesn’t support.
You can tell Anymail to suppress these errors and send the messages
anyway—see Unsupported features.
- Restricted characters in ``from_email`` display names
Resend’s API does not accept many email address display names (a.k.a. “friendly names” or “real names”) formatted according to the relevant standard (RFC 5322). Anymail implements a workaround for the
to
,cc
,bcc
andreply_to
fields, but Resend rejects attempts to use this workaround forfrom_email
display names.These characters will cause problems in a From address display name:
Double quotes (
"
) and some other punctuation characters can cause a “Resend API response 422” error complaining of an “Invalid `from` field”, or can result in a garbled From name (missing segments, additional punctuation inserted) in the resulting message.A question mark immediately followed by any alphabetic character (e.g.,
?u
) will cause a “Resend API response 451” security error complaining that “The email payload contain invalid characters”. (This behavior prevents use of standard RFC 2047 encoded words in From display names—which is the workaround Anymail implements for other address fields.)
There may be other character combinations that also cause problems. If you need to include punctuation in a From display name, be sure to verify the results. (The issues were reported to Resend in October, 2023.)
- Attachment filename determines content type
Resend determines the content type of an attachment from its filename extension.
If you try to send an attachment without a filename, Anymail will substitute “attachment.ext” using an appropriate .ext for the content type.
If you try to send an attachment whose content type doesn’t match its filename extension, Resend will change the content type to match the extension. (E.g., the filename “data.txt” will always be sent as “text/plain”, even if you specified a “text/csv” content type.)
- No inline images
Resend’s API does not provide a mechanism to send inline content or to specify Content-ID for an attachment.
- Anymail tags and metadata are exposed to recipient
Anymail implements its normalized
tags
andmetadata
features for Resend using custom email headers. That means they can be visible to recipients via their email app’s “show original message” (or similar) command. Do not include sensitive data in tags or metadata.Resend also offers a feature it calls “tags”, which allows arbitrary key-value data to be tracked with a sent message (similar Anymail’s
metadata
). Resend’s native tags are not exposed to recipients, but they have significant restrictions on character set and length (for both keys and values).If you want to use Resend’s native tags with Anymail, you can send them using esp_extra, and retrieve them in a status tracking webhook using esp_event. (The linked sections below include examples.)
- No click/open tracking overrides
Resend does not support
track_clicks
ortrack_opens
. Its tracking features can only be configured at the domain level in Resend’s control panel.- No attachments with delayed sending
Resend does not support attachments or batch sending features when using
send_at
.Changed in version 12.0: Resend now supports
send_at
.- No envelope sender
Resend does not support specifying the
envelope_sender
.- Status tracking does not identify recipient
If you send a message with multiple recipients (to, cc, and/or bcc), Resend’s status webhooks do not identify which recipient applies for an event. See the note below.
API rate limits
Resend provides rate limit headers with each API call response.
To access them after a successful send, use (e.g.,)
message.anymail_status.esp_response.headers["ratelimit-remaining"]
.
If you exceed a rate limit, you’ll get an AnymailAPIError
with error.status_code == 429
, and can determine how many seconds to wait
from error.response.headers["retry-after"]
.
exp_extra support
Anymail’s Resend backend will pass esp_extra
values directly to Resend’s send-email API. Example:
message = AnymailMessage(...) message.esp_extra = { # Use Resend's native "tags" feature # (be careful about character set restrictions): "tags": [ {"name": "Co_Brand", "value": "Acme_Inc"}, {"name": "Feature_Flag_1", "value": "test_22_a"}, ], }
Batch sending/merge and ESP templates
Added in version 10.3: Support for batch sending with
merge_metadata
.
Resend supports batch sending (where each To recipient sees only their own email address). It also supports per-recipient metadata with batch sending.
Set Anymail’s normalized merge_metadata
attribute to use Resend’s batch-send API:
message = EmailMessage( to=["[email protected]", "Bob <[email protected]>"], from_email="...", subject="...", body="..." ) message.merge_metadata = { '[email protected]': {'user_id': "12345"}, '[email protected]': {'user_id': "54321"}, }
Resend does not currently offer ESP stored templates
or merge capabilities, so does not support Anymail’s
merge_data
,
merge_global_data
, or
template_id
message attributes.
(Resend’s current template feature is only supported in node.js,
using templates that are rendered in their API client.)
(Setting merge_data
to an empty
dict will also invoke batch send, but trying to supply merge data for
any recipient will raise an AnymailUnsupportedFeature
error.)
Status tracking webhooks
Anymail’s normalized status tracking works with Resend’s webhooks.
Resend implements webhook signing, using the svix package for signature validation (see Installation above). You have three options for securing the status tracking webhook:
Use Resend’s webhook signature validation, by setting
RESEND_SIGNING_SECRET
(requires the svix package)Use Anymail’s shared secret validation, by setting
WEBHOOK_SECRET
(does not require svix)Use both
Signature validation is recommended, unless you do not want to add svix to your dependencies.
To configure Anymail status tracking for Resend, add a new webhook endpoint to your Resend Webhooks settings:
For the “Endpoint URL”, enter one of these (where yoursite.example.com is your Django site).
If are not using Anymail’s shared webhook secret:
https://yoursite.example.com/anymail/resend/tracking/
Or if you are using Anymail’s
WEBHOOK_SECRET
, include the random:random shared secret in the URL:https://random:random@yoursite.example.com/resend/tracking/
For “Events to listen”, select any or all events you want to track.
Click the “Add” button.
Then, if you are using Resend’s webhook signature validation (with svix), add the webhook signing secret to your Anymail settings:
Still on the Resend Webhooks settings page, click into the webhook endpoint URL you added above, and copy the “signing secret” listed near the top of the page.
Add that to your settings.py
ANYMAIL
settings asRESEND_SIGNING_SECRET
:ANYMAIL = { # ... "RESEND_SIGNING_SECRET": "whsec_..." }
Resend will report these Anymail
event_type
s:
sent, delivered, bounced, deferred, complained, opened, and clicked.
Note
Multiple recipients not recommended with tracking
If you send a message with multiple recipients (to, cc, and/or bcc), you will receive separate events (delivered, bounced, opened, etc.) for every recipient. But Resend does not identify which recipient applies for a particular event.
The event.recipient
will always be the first to
email, but the event might actually have been
generated by some other recipient.
To avoid confusion, it’s best to send each message to exactly one to
address, and avoid using cc or bcc.
The status tracking event’s esp_event
field will be the parsed Resend webhook payload. For example, if you provided
Resend’s native “tags” via esp_extra when sending,
you can retrieve them in your tracking signal receiver like this:
@receiver(tracking)
def handle_tracking(sender, event, esp_name, **kwargs):
...
resend_tags = event.esp_event.get("tags", {})
# resend_tags will be a flattened dict (not
# the name/value list used when sending). E.g.:
# {"Co_Brand": "Acme_Inc", "Feature_Flag_1": "test_22_a"}
Inbound
Resend does not currently support inbound email.
Troubleshooting
If Anymail’s Resend integration isn’t behaving like you expect, Resend’s dashboard includes diagnostic logs that can help isolate the problem:
Resend Logs page lists every call received by Resend’s API
Resend Emails page shows every event related to email sent through Resend
Resend Webhooks page shows every attempt by Resend to call your webhook (click into a webhook endpoint url to see the logs for that endpoint)
See Anymail’s Troubleshooting docs for additional suggestions.