Skip to content

Commit ee59ece

Browse files
authored
feat: Inbound (#170)
1 parent 6dd113f commit ee59ece

10 files changed

Lines changed: 990 additions & 0 deletions

File tree

‎examples/receiving_email.py‎

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import os
2+
3+
import resend
4+
5+
# Type imports
6+
from resend import AttachmentsReceiving, EmailsReceiving
7+
8+
if not os.environ["RESEND_API_KEY"]:
9+
raise EnvironmentError("RESEND_API_KEY is missing")
10+
11+
# Retrieve a single received email by ID
12+
email_id = "006e2796-ff6a-4436-91ad-0429e600bf8a"
13+
14+
received_email: resend.ReceivedEmail = resend.Emails.Receiving.get(email_id=email_id)
15+
16+
print(f"\nRetrieved received email: {received_email['id']}")
17+
print("\n--- Email Details ---")
18+
print(f"From: {received_email['from']}")
19+
print(f"To: {received_email['to']}")
20+
print(f"Subject: {received_email['subject']}")
21+
print(f"Created at: {received_email['created_at']}")
22+
print(f"Object type: {received_email['object']}")
23+
24+
print("\n--- Email Content ---")
25+
html_content = received_email.get("html")
26+
if html_content:
27+
print(f"HTML: {html_content[:100]}...")
28+
else:
29+
print("HTML: None")
30+
31+
text_content = received_email.get("text")
32+
if text_content:
33+
print(f"Text: {text_content[:100]}...")
34+
else:
35+
print("Text: None")
36+
37+
print("\n--- Recipients ---")
38+
print(f"CC: {received_email.get('cc', [])}")
39+
print(f"BCC: {received_email.get('bcc', [])}")
40+
print(f"Reply-To: {received_email.get('reply_to', [])}")
41+
42+
print("\n--- Headers ---")
43+
if received_email.get("headers"):
44+
for header_name, header_value in received_email["headers"].items():
45+
print(f"{header_name}: {header_value}")
46+
else:
47+
print("No custom headers")
48+
49+
print("\n--- Attachments ---")
50+
if received_email["attachments"]:
51+
print(f"Total attachments: {len(received_email['attachments'])}")
52+
for idx, attachment in enumerate(received_email["attachments"], 1):
53+
print(f"\nAttachment {idx}:")
54+
print(f" ID: {attachment['id']}")
55+
print(f" Filename: {attachment['filename']}")
56+
print(f" Content Type: {attachment['content_type']}")
57+
print(f" Content Disposition: {attachment['content_disposition']}")
58+
if attachment.get("content_id"):
59+
print(f" Content ID: {attachment['content_id']}")
60+
if attachment.get("size"):
61+
print(f" Size: {attachment['size']} bytes")
62+
else:
63+
print("No attachments")
64+
65+
print("\n--- Listing All Received Emails ---")
66+
all_emails: EmailsReceiving.ListResponse = resend.Emails.Receiving.list()
67+
68+
print(f"Total emails in this batch: {len(all_emails['data'])}")
69+
print(f"Has more emails: {all_emails['has_more']}")
70+
71+
if all_emails["data"]:
72+
for idx, email in enumerate(all_emails["data"], 1):
73+
print(f"\nEmail {idx}:")
74+
print(f" ID: {email['id']}")
75+
print(f" From: {email['from']}")
76+
print(f" To: {email['to']}")
77+
print(f" Subject: {email['subject']}")
78+
print(f" Created: {email['created_at']}")
79+
print(f" Attachments: {len(email['attachments'])}")
80+
81+
print("\n--- Listing Received Emails with Pagination ---")
82+
list_params: EmailsReceiving.ListParams = {
83+
"limit": 5,
84+
}
85+
paginated_emails: EmailsReceiving.ListResponse = resend.Emails.Receiving.list(
86+
params=list_params
87+
)
88+
89+
print(f"Retrieved {len(paginated_emails['data'])} emails (limited to 5)")
90+
print(f"Has more: {paginated_emails['has_more']}")
91+
92+
if paginated_emails["data"] and paginated_emails["has_more"]:
93+
last_email_id = paginated_emails["data"][-1]["id"]
94+
print(f"\n--- Getting Next Page (after {last_email_id}) ---")
95+
next_page_params: EmailsReceiving.ListParams = {
96+
"limit": 5,
97+
"after": last_email_id,
98+
}
99+
next_page: EmailsReceiving.ListResponse = resend.Emails.Receiving.list(
100+
params=next_page_params
101+
)
102+
print(f"Next page has {len(next_page['data'])} emails")
103+
print(f"Next page has more: {next_page['has_more']}")
104+
105+
print("\n--- Listing All Attachments ---")
106+
all_attachments: AttachmentsReceiving.ListResponse = resend.Attachments.Receiving.list(
107+
email_id=email_id
108+
)
109+
110+
print(f"Total attachments: {len(all_attachments['data'])}")
111+
print(f"Has more: {all_attachments['has_more']}")
112+
113+
if all_attachments["data"]:
114+
for idx, att in enumerate(all_attachments["data"], 1):
115+
print(f"\nAttachment {idx}:")
116+
print(f" ID: {att['id']}")
117+
print(f" Filename: {att['filename']}")
118+
print(f" Content Type: {att['content_type']}")
119+
print(f" Size: {att.get('size', 'N/A')} bytes")
120+
121+
if received_email["attachments"] and len(received_email["attachments"]) > 0:
122+
first_attachment = received_email["attachments"][0]
123+
attachment_id = first_attachment["id"]
124+
125+
print(f"\n--- Retrieving Attachment Details: {first_attachment['filename']} ---")
126+
127+
attachment_details: resend.ReceivedEmailAttachmentDetails = (
128+
resend.Attachments.Receiving.get(
129+
email_id=email_id,
130+
attachment_id=attachment_id,
131+
)
132+
)
133+
134+
print(f"Attachment ID: {attachment_details['id']}")
135+
print(f"Filename: {attachment_details['filename']}")
136+
print(f"Content Type: {attachment_details['content_type']}")
137+
print(f"Content Disposition: {attachment_details['content_disposition']}")
138+
if attachment_details.get("content_id"):
139+
print(f" Content ID: {attachment_details['content_id']}")
140+
print(f"Download URL: {attachment_details['download_url']}")
141+
print(f"Expires At: {attachment_details['expires_at']}")
142+
else:
143+
print("\nNo attachments available to retrieve in this example.")

‎resend/__init__.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from .api_keys._api_key import ApiKey
44
from .api_keys._api_keys import ApiKeys
5+
from .attachments._attachments import Attachments
6+
from .attachments._receiving import Receiving as AttachmentsReceiving
57
from .audiences._audience import Audience
68
from .audiences._audiences import Audiences
79
from .broadcasts._broadcast import Broadcast
@@ -14,6 +16,10 @@
1416
from .emails._batch import Batch, BatchValidationError
1517
from .emails._email import Email
1618
from .emails._emails import Emails
19+
from .emails._received_email import (ListReceivedEmail, ReceivedEmail,
20+
ReceivedEmailAttachment,
21+
ReceivedEmailAttachmentDetails)
22+
from .emails._receiving import Receiving as EmailsReceiving
1723
from .emails._tag import Tag
1824
from .http_client import HTTPClient
1925
from .http_client_requests import RequestsClient
@@ -43,6 +49,7 @@
4349
"Audiences",
4450
"Contacts",
4551
"Broadcasts",
52+
"Attachments",
4653
"Topics",
4754
# Types
4855
"Audience",
@@ -56,6 +63,13 @@
5663
"Broadcast",
5764
"Topic",
5865
"BatchValidationError",
66+
"ReceivedEmail",
67+
"ReceivedEmailAttachment",
68+
"ReceivedEmailAttachmentDetails",
69+
"ListReceivedEmail",
70+
# Receiving types (for type hints)
71+
"EmailsReceiving",
72+
"AttachmentsReceiving",
5973
# Default HTTP Client
6074
"RequestsClient",
6175
]

‎resend/attachments/__init__.py‎

Whitespace-only changes.

‎resend/attachments/_attachments.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from resend.attachments._receiving import Receiving
2+
3+
4+
class Attachments:
5+
"""
6+
Attachments class that provides methods for managing email attachments.
7+
"""
8+
9+
Receiving = Receiving

‎resend/attachments/_receiving.py‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from typing import Any, Dict, List, Optional, cast
2+
3+
from typing_extensions import NotRequired, TypedDict
4+
5+
from resend import request
6+
from resend.emails._received_email import (ReceivedEmailAttachment,
7+
ReceivedEmailAttachmentDetails)
8+
from resend.pagination_helper import PaginationHelper
9+
10+
11+
# Internal wrapper type for get attachment API response
12+
class _GetAttachmentResponse(TypedDict):
13+
object: str
14+
data: ReceivedEmailAttachmentDetails
15+
16+
17+
class _ListParams(TypedDict):
18+
limit: NotRequired[int]
19+
"""
20+
The maximum number of attachments to return. Maximum 100, minimum 1.
21+
"""
22+
after: NotRequired[str]
23+
"""
24+
Return attachments after this cursor for pagination.
25+
"""
26+
before: NotRequired[str]
27+
"""
28+
Return attachments before this cursor for pagination.
29+
"""
30+
31+
32+
class _ListResponse(TypedDict):
33+
object: str
34+
"""
35+
The object type: "list"
36+
"""
37+
data: List[ReceivedEmailAttachment]
38+
"""
39+
The list of attachment objects.
40+
"""
41+
has_more: bool
42+
"""
43+
Whether there are more attachments available for pagination.
44+
"""
45+
46+
47+
class Receiving:
48+
"""
49+
Receiving class that provides methods for retrieving attachments from received emails.
50+
"""
51+
52+
class ListParams(_ListParams):
53+
"""
54+
ListParams is the class that wraps the parameters for the list method.
55+
56+
Attributes:
57+
limit (NotRequired[int]): The maximum number of attachments to return. Maximum 100, minimum 1.
58+
after (NotRequired[str]): Return attachments after this cursor for pagination.
59+
before (NotRequired[str]): Return attachments before this cursor for pagination.
60+
"""
61+
62+
class ListResponse(_ListResponse):
63+
"""
64+
ListResponse is the type that wraps the response for listing attachments.
65+
66+
Attributes:
67+
object (str): The object type: "list"
68+
data (List[ReceivedEmailAttachment]): The list of attachment objects.
69+
has_more (bool): Whether there are more attachments available for pagination.
70+
"""
71+
72+
@classmethod
73+
def get(cls, email_id: str, attachment_id: str) -> ReceivedEmailAttachmentDetails:
74+
"""
75+
Retrieve a single attachment from a received email.
76+
see more: https://resend.com/docs/api-reference/attachments/retrieve-attachment
77+
78+
Args:
79+
email_id (str): The ID of the received email
80+
attachment_id (str): The ID of the attachment to retrieve
81+
82+
Returns:
83+
ReceivedEmailAttachmentDetails: The attachment details including download URL
84+
"""
85+
path = f"/emails/receiving/{email_id}/attachments/{attachment_id}"
86+
resp = request.Request[_GetAttachmentResponse](
87+
path=path,
88+
params={},
89+
verb="get",
90+
).perform_with_content()
91+
# Extract the data field from the wrapped response
92+
return resp["data"]
93+
94+
@classmethod
95+
def list(cls, email_id: str, params: Optional[ListParams] = None) -> ListResponse:
96+
"""
97+
Retrieve a list of attachments from a received email.
98+
see more: https://resend.com/docs/api-reference/attachments/list-attachments
99+
100+
Args:
101+
email_id (str): The ID of the received email
102+
params (Optional[ListParams]): The list parameters for pagination
103+
104+
Returns:
105+
ListResponse: A paginated list of attachment objects
106+
"""
107+
base_path = f"/emails/receiving/{email_id}/attachments"
108+
query_params = cast(Dict[Any, Any], params) if params else None
109+
path = PaginationHelper.build_paginated_path(base_path, query_params)
110+
resp = request.Request[Receiving.ListResponse](
111+
path=path,
112+
params={},
113+
verb="get",
114+
).perform_with_content()
115+
return resp

‎resend/emails/_emails.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from resend import request
66
from resend.emails._attachment import Attachment, RemoteAttachment
77
from resend.emails._email import Email
8+
from resend.emails._receiving import Receiving
89
from resend.emails._tag import Tag
910
from resend.pagination_helper import PaginationHelper
1011

@@ -103,6 +104,7 @@ class _SendParamsDefault(_SendParamsFrom):
103104

104105

105106
class Emails:
107+
Receiving = Receiving
106108

107109
class CancelScheduledEmailResponse(_CancelScheduledEmailResponse):
108110
"""

0 commit comments

Comments
 (0)