Skip to content

Commit a0b3e57

Browse files
authored
feat: Add emails list method with pagination (#162)
1 parent 071d156 commit a0b3e57

3 files changed

Lines changed: 187 additions & 0 deletions

File tree

examples/simple_email.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,23 @@
4242
print("Email reply_to: ", email_resp["reply_to"])
4343
print("Email bcc: ", email_resp["bcc"])
4444
print("Email cc: ", email_resp["cc"])
45+
46+
print("\n--- Listing Emails ---")
47+
48+
# List all emails
49+
all_emails = resend.Emails.list()
50+
print(f"Total emails in this batch: {len(all_emails['data'])}")
51+
print(f"Has more emails: {all_emails['has_more']}")
52+
53+
# List with pagination
54+
list_params: resend.Emails.ListParams = {
55+
"limit": 5,
56+
}
57+
paginated_emails = resend.Emails.list(params=list_params)
58+
print(f"Retrieved {len(paginated_emails['data'])} emails (limited to 5)")
59+
60+
# Example with cursor-based pagination
61+
if paginated_emails["data"]:
62+
last_email_id = paginated_emails["data"][-1]["id"]
63+
next_page = resend.Emails.list(params={"limit": 5, "after": last_email_id})
64+
print(f"Next page has {len(next_page['data'])} emails")

resend/emails/_emails.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from resend.emails._attachment import Attachment, RemoteAttachment
77
from resend.emails._email import Email
88
from resend.emails._tag import Tag
9+
from resend.pagination_helper import PaginationHelper
910

1011

1112
class _UpdateParams(TypedDict):
@@ -178,6 +179,52 @@ class SendResponse(TypedDict):
178179
The sent Email ID.
179180
"""
180181

182+
class ListParams(TypedDict):
183+
"""
184+
ListParams is the class that wraps the parameters for the list method.
185+
186+
Attributes:
187+
limit (NotRequired[int]): The maximum number of emails to return. Defaults to 10, maximum 100.
188+
after (NotRequired[str]): Return emails after this cursor for pagination.
189+
before (NotRequired[str]): Return emails before this cursor for pagination.
190+
"""
191+
192+
limit: NotRequired[int]
193+
"""
194+
The maximum number of emails to return. Defaults to 10, maximum 100.
195+
"""
196+
after: NotRequired[str]
197+
"""
198+
Return emails after this cursor for pagination.
199+
"""
200+
before: NotRequired[str]
201+
"""
202+
Return emails before this cursor for pagination.
203+
"""
204+
205+
class ListResponse(TypedDict):
206+
"""
207+
ListResponse is the type that wraps the response for listing emails.
208+
209+
Attributes:
210+
object (str): The object type: "list"
211+
data (List[Email]): The list of email objects.
212+
has_more (bool): Whether there are more emails available for pagination.
213+
"""
214+
215+
object: str
216+
"""
217+
The object type: "list"
218+
"""
219+
data: List[Email]
220+
"""
221+
The list of email objects.
222+
"""
223+
has_more: bool
224+
"""
225+
Whether there are more emails available for pagination.
226+
"""
227+
181228
@classmethod
182229
def send(
183230
cls, params: SendParams, options: Optional[SendOptions] = None
@@ -261,3 +308,25 @@ def update(cls, params: UpdateParams) -> UpdateEmailResponse:
261308
verb="patch",
262309
).perform_with_content()
263310
return resp
311+
312+
@classmethod
313+
def list(cls, params: Optional[ListParams] = None) -> ListResponse:
314+
"""
315+
Retrieve a list of emails.
316+
see more: https://resend.com/docs/api-reference/emails/list-emails
317+
318+
Args:
319+
params (Optional[ListParams]): The list parameters for pagination
320+
321+
Returns:
322+
ListResponse: A paginated list of email objects
323+
"""
324+
base_path = "/emails"
325+
query_params = cast(Dict[Any, Any], params) if params else None
326+
path = PaginationHelper.build_paginated_path(base_path, query_params)
327+
resp = request.Request[Emails.ListResponse](
328+
path=path,
329+
params={},
330+
verb="get",
331+
).perform_with_content()
332+
return resp

tests/emails_test.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,101 @@ def test_email_send_with_inline_attachment(self) -> None:
156156
}
157157
email: resend.Emails.SendResponse = resend.Emails.send(params)
158158
assert email["id"] == "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
159+
160+
def test_email_list(self) -> None:
161+
self.set_mock_json(
162+
{
163+
"object": "list",
164+
"data": [
165+
{
166+
"id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
167+
"to": ["james@bond.com"],
168+
"from": "onboarding@resend.dev",
169+
"created_at": "2023-04-03T22:13:42.674981+00:00",
170+
"subject": "Hello World",
171+
"html": "Congrats on sending your <strong>first email</strong>!",
172+
"text": None,
173+
"bcc": [None],
174+
"cc": [None],
175+
"reply_to": [None],
176+
"last_event": "delivered",
177+
},
178+
{
179+
"id": "5ef9a417-02e9-4d39-ad75-9611e0fcc33d",
180+
"to": ["test@example.com"],
181+
"from": "hello@resend.dev",
182+
"created_at": "2023-04-04T10:15:42.674981+00:00",
183+
"subject": "Test Email",
184+
"html": "This is a test email",
185+
"text": "This is a test email",
186+
"bcc": [None],
187+
"cc": [None],
188+
"reply_to": [None],
189+
"last_event": "sent",
190+
},
191+
],
192+
"has_more": True,
193+
}
194+
)
195+
196+
emails: resend.Emails.ListResponse = resend.Emails.list()
197+
assert emails["object"] == "list"
198+
assert len(emails["data"]) == 2
199+
assert emails["has_more"] == True
200+
assert emails["data"][0]["id"] == "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
201+
assert emails["data"][1]["id"] == "5ef9a417-02e9-4d39-ad75-9611e0fcc33d"
202+
203+
def test_email_list_with_params(self) -> None:
204+
self.set_mock_json(
205+
{
206+
"object": "list",
207+
"data": [
208+
{
209+
"id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
210+
"to": ["james@bond.com"],
211+
"from": "onboarding@resend.dev",
212+
"created_at": "2023-04-03T22:13:42.674981+00:00",
213+
"subject": "Hello World",
214+
"html": "Congrats on sending your <strong>first email</strong>!",
215+
"text": None,
216+
"bcc": [None],
217+
"cc": [None],
218+
"reply_to": [None],
219+
"last_event": "delivered",
220+
},
221+
],
222+
"has_more": False,
223+
}
224+
)
225+
226+
list_params: resend.Emails.ListParams = {
227+
"limit": 10,
228+
"after": "cursor123",
229+
}
230+
emails: resend.Emails.ListResponse = resend.Emails.list(params=list_params)
231+
assert emails["object"] == "list"
232+
assert len(emails["data"]) == 1
233+
assert emails["has_more"] == False
234+
235+
def test_email_list_with_before_param(self) -> None:
236+
self.set_mock_json(
237+
{
238+
"object": "list",
239+
"data": [],
240+
"has_more": False,
241+
}
242+
)
243+
244+
list_params: resend.Emails.ListParams = {
245+
"limit": 5,
246+
"before": "cursor456",
247+
}
248+
emails: resend.Emails.ListResponse = resend.Emails.list(params=list_params)
249+
assert emails["object"] == "list"
250+
assert len(emails["data"]) == 0
251+
assert emails["has_more"] == False
252+
253+
def test_should_list_email_raise_exception_when_no_content(self) -> None:
254+
self.set_mock_json(None)
255+
with self.assertRaises(NoContentError):
256+
_ = resend.Emails.list()

0 commit comments

Comments
 (0)