Skip to content
6 changes: 5 additions & 1 deletion examples/inboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@
"content": "UmVmdW5kIHJlY2VpcHQ=",
"filename": "receipt.txt",
"content_type": "text/plain",
}
},
{
"path": "https://example.com/invoice.pdf",
"filename": "invoice.pdf",
Comment thread
dielduarte marked this conversation as resolved.
Resolved
},
],
}
draft: resend.Inboxes.Drafts.CreateResponse = resend.Inboxes.Drafts.create(
Expand Down
68 changes: 48 additions & 20 deletions resend/inboxes/drafts/_inbox_drafts.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ class InboxDrafts:

class AttachmentParams(TypedDict):
"""
AttachmentParams is a file to store on a draft. Each one needs
``content``, ``filename`` and ``content_type``. A ``path`` or
``content_id`` returns a 422.
AttachmentParams is a file to store on a draft, sent inline.

Attributes:
content (str): The content of the file, encoded in base64.
Expand All @@ -46,6 +44,34 @@ class AttachmentParams(TypedDict):
The MIME type of the file, for example ``application/pdf``.
"""

class PathAttachmentParams(TypedDict):
"""
PathAttachmentParams is a file to store on a draft, downloaded from a URL
when the request is made.

Attributes:
path (str): The URL to download the file from.
filename (str): The name of the file.
content_type (NotRequired[str]): The MIME type of the file.
"""

path: str
"""
The URL to download the file from, starting with ``http`` or ``https``.
A file that cannot be downloaded returns a 422.
"""
filename: str
"""
The name of the file.
"""
content_type: NotRequired[str]
"""
The MIME type of the file. Defaults to the content type of the
downloaded file.
"""

DraftAttachmentParams = Union[AttachmentParams, PathAttachmentParams]

class ListParams(TypedDict):
"""
ListParams are the pagination parameters for listing drafts.
Expand Down Expand Up @@ -111,7 +137,7 @@ class CreateParams(TypedDict):
subject (NotRequired[Optional[str]]): The subject.
html (NotRequired[Optional[str]]): The HTML body.
text (NotRequired[Optional[str]]): The plain text body.
attachments (NotRequired[Optional[List[AttachmentParams]]]): Files to store on the draft.
attachments (NotRequired[Optional[List[DraftAttachmentParams]]]): Files to store on the draft.
thread_id (NotRequired[str]): The Thread ID to reply in.
reply_to_email_id (NotRequired[str]): The Email ID to reply to.
"""
Expand Down Expand Up @@ -146,12 +172,13 @@ class CreateParams(TypedDict):
"""
The plain text body.
"""
attachments: NotRequired[Optional[List["InboxDrafts.AttachmentParams"]]]
attachments: NotRequired[Optional[List["InboxDrafts.DraftAttachmentParams"]]]
"""
Files to store on the draft. Each one needs ``content``, ``filename`` and
``content_type``. A ``path`` or ``content_id`` returns a 422. The files on
a draft can total at most 25 MB. An empty file or a blocked file type
returns a 422.
Files to store on the draft. Each one needs a ``filename`` and either
Comment thread
dielduarte marked this conversation as resolved.
Resolved
``content`` with a ``content_type``, or a ``path`` URL that is downloaded
when the request is made. The files on a draft, downloaded ones included,
can total at most 25 MB. An empty file, a blocked file type, or a ``path``
that cannot be downloaded returns a 422.
"""
thread_id: NotRequired[str]
"""
Expand Down Expand Up @@ -220,7 +247,7 @@ class UpdateParams(TypedDict):
subject (NotRequired[Optional[str]]): The subject.
html (NotRequired[Optional[str]]): The HTML body.
text (NotRequired[Optional[str]]): The plain text body.
attachments (NotRequired[Optional[List[AttachmentParams]]]): Files that replace
attachments (NotRequired[Optional[List[DraftAttachmentParams]]]): Files that replace
every attachment on the draft.
"""

Expand Down Expand Up @@ -258,13 +285,14 @@ class UpdateParams(TypedDict):
"""
The plain text body. Pass None to clear.
"""
attachments: NotRequired[Optional[List["InboxDrafts.AttachmentParams"]]]
attachments: NotRequired[Optional[List["InboxDrafts.DraftAttachmentParams"]]]
"""
Files to store on the draft. Each one needs ``content``, ``filename`` and
``content_type``. A ``path`` or ``content_id`` returns a 422. The files on
a draft can total at most 25 MB. An empty file or a blocked file type
returns a 422. Sending the field replaces every attachment on the draft.
Send ``[]`` or None to remove them all.
Files to store on the draft. Each one needs a ``filename`` and either
``content`` with a ``content_type``, or a ``path`` URL that is downloaded
when the request is made. The files on a draft, downloaded ones included,
can total at most 25 MB. An empty file, a blocked file type, or a ``path``
that cannot be downloaded returns a 422. Sending the field replaces every
attachment on the draft. Send ``[]`` or None to remove them all.
"""

class UpdateResponse(BaseResponse):
Expand Down Expand Up @@ -394,8 +422,8 @@ def create(
``to`` is addressed to the sender of the email it replies to, or to its
Reply-To when set. For an email the inbox sent, it defaults to that email's
``to`` recipients. Send ``to: []`` to leave it empty. Attachments are
stored on the draft, so each one needs ``content``, ``filename`` and
``content_type``. Only one unsent reply draft can target an email, so a
stored on the draft, so a ``path`` file is downloaded when the request
is made. Only one unsent reply draft can target an email, so a
reply draft request for an email that already has one returns the ID of
the existing draft. That request ignores every other field, except
``attachments``, which replaces the attachments of the existing draft. A
Expand Down Expand Up @@ -550,8 +578,8 @@ async def create_async(
``to`` is addressed to the sender of the email it replies to, or to its
Reply-To when set. For an email the inbox sent, it defaults to that email's
``to`` recipients. Send ``to: []`` to leave it empty. Attachments are
stored on the draft, so each one needs ``content``, ``filename`` and
``content_type``. Only one unsent reply draft can target an email, so a
stored on the draft, so a ``path`` file is downloaded when the request
is made. Only one unsent reply draft can target an email, so a
reply draft request for an email that already has one returns the ID of
the existing draft. That request ignores every other field, except
``attachments``, which replaces the attachments of the existing draft. A
Expand Down
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.50.1"
__version__ = "2.51.0"


def get_version() -> str:
Expand Down
47 changes: 42 additions & 5 deletions tests/inboxes_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,20 @@ def test_should_send_inbox_drafts_raise_exception_when_no_content(
_ = resend.Inboxes.Drafts.send(INBOX_ID, DRAFT_ID)


def _invalid_draft_attachments() -> List[resend.Inboxes.Drafts.DraftAttachmentParams]:
"""Checked by mypy: each entry must stay a type error."""
return [
{"filename": "note.txt"}, # type: ignore[list-item]
{"content": "aGVsbG8=", "filename": "note.txt"}, # type: ignore[list-item]
{ # type: ignore[list-item]
"content": "aGVsbG8=",
"path": "https://example.com/invoice.pdf",
"filename": "note.txt",
"content_type": "text/plain",
},
]


class TestInboxesRequest(TestCase):
def setUp(self) -> None:
resend.api_key = "re_123"
Expand Down Expand Up @@ -1044,11 +1058,11 @@ def test_drafts_paths_and_bodies(self) -> None:
{"to": None},
)

attachment: resend.Inboxes.Drafts.AttachmentParams = {
"content": "aGVsbG8=",
"filename": "note.txt",
"content_type": "text/plain",
}
attachment = resend.InboxDrafts.AttachmentParams(
content="aGVsbG8=",
filename="note.txt",
content_type="text/plain",
)
resend.Inboxes.Drafts.create(
{"inbox_id": INBOX_ID, "to": "ada@example.org", "attachments": [attachment]}
)
Expand All @@ -1057,6 +1071,29 @@ def test_drafts_paths_and_bodies(self) -> None:
"attachments": [attachment],
}

path_attachment: resend.Inboxes.Drafts.DraftAttachmentParams = {
"path": "https://example.com/invoice.pdf",
"filename": "invoice.pdf",
}
resend.Inboxes.Drafts.create(
{"inbox_id": INBOX_ID, "attachments": [path_attachment]}
)
assert self.last_request()["json"] == {"attachments": [path_attachment]}

typed_path_attachment: resend.Inboxes.Drafts.DraftAttachmentParams = {
"path": "https://example.com/invoice.pdf",
"filename": "invoice.pdf",
"content_type": "application/pdf",
}
resend.Inboxes.Drafts.update(
{
"inbox_id": INBOX_ID,
"id": DRAFT_ID,
"attachments": [typed_path_attachment],
}
)
assert self.last_request()["json"] == {"attachments": [typed_path_attachment]}

resend.Inboxes.Drafts.update(
{"inbox_id": INBOX_ID, "id": DRAFT_ID, "attachments": []}
)
Expand Down
Loading