Skip to content
Draft
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
2 changes: 1 addition & 1 deletion examples/async/batch_email_send_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


async def main() -> None:
params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "onboarding@resend.dev",
"to": ["delivered@resend.dev"],
Expand Down
4 changes: 2 additions & 2 deletions examples/batch_email_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
raise EnvironmentError("RESEND_API_KEY is missing")


params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "onboarding@resend.dev",
"to": ["delivered@resend.dev"],
Expand Down Expand Up @@ -55,7 +55,7 @@
print("sending with permissive validation mode")

# Example with some invalid emails to demonstrate error handling
mixed_params: List[resend.Emails.SendParams] = [
mixed_params: List[resend.Batch.SendParams] = [
{
"from": "onboarding@resend.dev",
"to": ["delivered@resend.dev"],
Expand Down
28 changes: 24 additions & 4 deletions resend/emails/_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ class BatchValidationError(TypedDict):

class Batch:

class SendParams(Emails.SendParams):
"""SendParams is the class that wraps the parameters for each email in a batch send.

Attributes:
from (NotRequired[str]): The email address to send the email from.
to (Union[str, List[str]]): List of email addresses to send the email to.
subject (NotRequired[str]): The subject of the email.
bcc (NotRequired[Union[List[str], str]]): Bcc
cc (NotRequired[Union[List[str], str]]): Cc
reply_to (NotRequired[Union[List[str], str]]): Reply to
html (NotRequired[str]): The HTML content of the email.
text (NotRequired[str]): The text content of the email.
headers (NotRequired[Dict[str, str]]): Custom headers to be added to the email.
attachments (NotRequired[List[Union[Attachment, RemoteAttachment]]]): List of attachments to be added to the email.
tags (NotRequired[List[Tag]]): List of tags to be added to the email.
scheduled_at (NotRequired[str]): Schedule email to be sent later.
The date should be in ISO 8601 format (e.g: 2024-08-05T11:52:01.858Z).
template (NotRequired[EmailTemplate]): Template configuration for sending emails using predefined templates.
"""

class SendOptions(TypedDict):
"""
SendOptions is the class that wraps the options for the batch send method.
Expand Down Expand Up @@ -79,14 +99,14 @@ class SendResponse(BaseResponse):

@classmethod
def send(
cls, params: List[Emails.SendParams], options: Optional[SendOptions] = None
cls, params: List[SendParams], options: Optional[SendOptions] = None
) -> SendResponse:
"""
Trigger up to 100 batch emails at once.
see more: https://resend.com/docs/api-reference/emails/send-batch-emails

Args:
params (List[Emails.SendParams]): The list of emails to send
params (List[SendParams]): The list of emails to send
options (Optional[SendOptions]): Batch options, including batch_validation mode

Returns:
Expand All @@ -104,14 +124,14 @@ def send(

@classmethod
async def send_async(
cls, params: List[Emails.SendParams], options: Optional[SendOptions] = None
cls, params: List[SendParams], options: Optional[SendOptions] = None
) -> SendResponse:
"""
Trigger up to 100 batch emails at once (async).
see more: https://resend.com/docs/api-reference/emails/send-batch-emails

Args:
params (List[Emails.SendParams]): The list of emails to send
params (List[SendParams]): The list of emails to send
options (Optional[SendOptions]): Batch options, ie: idempotency_key

Returns:
Expand Down
2 changes: 2 additions & 0 deletions resend/emails/_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ class SendParams(_SendParamsDefault):
headers (NotRequired[Dict[str, str]]): Custom headers to be added to the email.
attachments (NotRequired[List[Union[Attachment, RemoteAttachment]]]): List of attachments to be added to the email.
tags (NotRequired[List[Tag]]): List of tags to be added to the email.
scheduled_at (NotRequired[str]): Schedule email to be sent later.
The date should be in ISO 8601 format (e.g: 2024-08-05T11:52:01.858Z).
template (NotRequired[EmailTemplate]): Template configuration for sending emails using predefined templates.
"""

Expand Down
6 changes: 3 additions & 3 deletions tests/batch_emails_async_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def test_batch_email_send_async(self) -> None:
}
)

params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand Down Expand Up @@ -52,7 +52,7 @@ async def test_batch_email_send_async_with_options(self) -> None:
}
)

params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand Down Expand Up @@ -82,7 +82,7 @@ async def test_should_send_batch_email_async_raise_exception_when_no_content(
self,
) -> None:
self.set_mock_json(None)
params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand Down
56 changes: 50 additions & 6 deletions tests/batch_emails_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import List
from typing import Any, List
from unittest.mock import patch

import resend
from resend import request
from resend.exceptions import NoContentError
from tests.conftest import ResendBaseTest

Expand All @@ -18,7 +20,7 @@ def test_batch_email_send(self) -> None:
}
)

params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand Down Expand Up @@ -48,7 +50,7 @@ def test_batch_email_send_with_options(self) -> None:
}
)

params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand All @@ -74,7 +76,7 @@ def test_batch_email_send_with_options(self) -> None:

def test_should_send_batch_email_raise_exception_when_no_content(self) -> None:
self.set_mock_json(None)
params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand All @@ -101,7 +103,7 @@ def test_batch_email_send_with_strict_validation_mode(self) -> None:
}
)

params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand Down Expand Up @@ -141,7 +143,7 @@ def test_batch_email_send_with_permissive_validation_mode(self) -> None:
}
)

params: List[resend.Emails.SendParams] = [
params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
Expand All @@ -167,3 +169,45 @@ def test_batch_email_send_with_permissive_validation_mode(self) -> None:
assert len(emails["errors"]) == 1
assert emails["errors"][0]["index"] == 1
assert emails["errors"][0]["message"] == "The `to` field is missing."

def test_batch_email_send_with_tags_attachments_and_scheduled_at(self) -> None:
self.set_mock_json(
{
"data": [{"id": "ae2014de-c168-4c61-8267-70d2662a1ce1"}]
}
)

attachment: resend.Attachment = {
"filename": "invoice.pdf",
"content": [1, 2, 3],
"content_type": "application/pdf",
}

params: List[resend.Batch.SendParams] = [
{
"from": "from@resend.dev",
"to": ["to@resend.dev"],
"subject": "hey",
"html": "<strong>hello, world!</strong>",
"tags": [{"name": "category", "value": "notification"}],
"scheduled_at": "2024-09-05T11:52:01.858Z",
"attachments": [attachment],
}
]

captured_params: List[Any] = []
original_init = request.Request.__init__

def capturing_init(instance: Any, *args: Any, **kwargs: Any) -> None:
captured_params.append(kwargs.get("params"))
original_init(instance, *args, **kwargs)

with patch.object(request.Request, "__init__", capturing_init):
emails: resend.Batch.SendResponse = resend.Batch.send(params)

assert emails["data"][0]["id"] == "ae2014de-c168-4c61-8267-70d2662a1ce1"

sent_params = captured_params[0]
assert sent_params[0]["tags"] == [{"name": "category", "value": "notification"}]
assert sent_params[0]["scheduled_at"] == "2024-09-05T11:52:01.858Z"
assert sent_params[0]["attachments"][0]["filename"] == "invoice.pdf"
Loading