Skip to content

Commit

Permalink
feat: Scheduled emails, cancel and update (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
drish authored Aug 14, 2024
1 parent 4a2a470 commit 10769c6
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
41 changes: 41 additions & 0 deletions examples/scheduled_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os

import resend

if not os.environ["RESEND_API_KEY"]:
raise EnvironmentError("RESEND_API_KEY is missing")

params: resend.Emails.SendParams = {
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "hi",
"html": "<strong>hello, scheduled email!</strong>",
"scheduled_at": "2024-09-05T11:52:01.858Z",
}

# Throws a resend.exceptions.ValidationError
# when scheduled_at is not in ISO 8601 format.
#
# Here is an example on how to create a date in the ISO 8601 format:
# from datetime import datetime
# datetime.now().isoformat()
email: resend.Email = resend.Emails.send(params)

print(f"Email scheduled: {email['id']}")

update_params: resend.Emails.UpdateParams = {
"id": email["id"],
"scheduled_at": "2024-09-07T11:52:01.858Z",
}

updated_email: resend.Emails.UpdateEmailResponse = resend.Emails.update(
params=update_params
)

print(f"Email updated: {updated_email['id']}")

cancel_resp: resend.Emails.CancelScheduledEmailResponse = resend.Emails.cancel(
email_id=email["id"]
)

print(f"Email canceled: {cancel_resp['id']}")
109 changes: 109 additions & 0 deletions resend/emails/_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,41 @@
from resend.emails._email import Email
from resend.emails._tag import Tag


class _UpdateParams(TypedDict):
id: str
"""
The ID of the email to update.
"""
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).
"""


class _UpdateEmailResponse(TypedDict):
object: str
"""
The object type: email
"""
id: str
"""
The ID of the scheduled email that was canceled.
"""


class _CancelScheduledEmailResponse(TypedDict):
object: str
"""
The object type: email
"""
id: str
"""
The ID of the scheduled email that was canceled.
"""


# SendParamsFrom is declared with functional TypedDict syntax here because
# "from" is a reserved keyword in Python, and this is the best way to
# support type-checking for it.
Expand Down Expand Up @@ -59,9 +94,43 @@ class _SendParamsDefault(_SendParamsFrom):
"""
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).
"""


class Emails:

class CancelScheduledEmailResponse(_CancelScheduledEmailResponse):
"""
CancelScheduledEmailResponse is the type that wraps the response of the email that was canceled
Attributes:
object (str): The object type
id (str): The ID of the scheduled email that was canceled
"""

class UpdateEmailResponse(_UpdateEmailResponse):
"""
UpdateEmailResponse is the type for the updated email response.
Attributes:
object (str): The object type
id (str): The ID of the updated email.
"""

class UpdateParams(_UpdateParams):
"""
UpdateParams is the class that wraps the parameters for the update method.
Attributes:
id (str): The ID of the email to update.
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).
"""

class SendParams(_SendParamsDefault):
"""SendParams is the class that wraps the parameters for the send method.
Expand Down Expand Up @@ -118,3 +187,43 @@ def get(cls, email_id: str) -> Email:
verb="get",
).perform_with_content()
return resp

@classmethod
def cancel(cls, email_id: str) -> CancelScheduledEmailResponse:
"""
Cancel a scheduled email.
see more: https://resend.com/docs/api-reference/emails/cancel-email
Args:
email_id (str): The ID of the scheduled email to cancel
Returns:
CancelScheduledEmailResponse: The response object that contains the ID of the scheduled email that was canceled
"""
path = f"/emails/{email_id}/cancel"
resp = request.Request[_CancelScheduledEmailResponse](
path=path,
params={},
verb="post",
).perform_with_content()
return resp

@classmethod
def update(cls, params: UpdateParams) -> UpdateEmailResponse:
"""
Update an email.
see more: https://resend.com/docs/api-reference/emails/update-email
Args:
params (UpdateParams): The email parameters to update
Returns:
Email: The email object that was updated
"""
path = f"/emails/{params['id']}"
resp = request.Request[_UpdateEmailResponse](
path=path,
params=cast(Dict[Any, Any], params),
verb="patch",
).perform_with_content()
return resp
2 changes: 1 addition & 1 deletion resend/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.3.0"
__version__ = "2.4.0"


def get_version() -> str:
Expand Down
26 changes: 26 additions & 0 deletions tests/emails_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,29 @@ def test_email_response_html(self) -> None:
_ = resend.Emails.send(params)
except ResendError as e:
assert e.message == "Failed to parse Resend API response. Please try again."

def test_update_email(self) -> None:
self.set_mock_json(
{
"id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794",
}
)
update_params: resend.Emails.UpdateParams = {
"id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794",
"scheduled_at": "2024-09-07T11:52:01.858Z",
}
updated_email: resend.Emails.UpdateEmailResponse = resend.Emails.update(
params=update_params
)
assert updated_email["id"] == "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"

def test_cancel_scheduled_email(self) -> None:
self.set_mock_json(
{
"id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794",
}
)
email: resend.Emails.CancelScheduledEmailResponse = resend.Emails.cancel(
email_id="49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
)
assert email["id"] == "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"

0 comments on commit 10769c6

Please sign in to comment.