Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2647298
feat(inboxes): add the Inboxes resource
felipefreitag Oct 12, 2026
fe2ede9
test(inboxes): cover every Inboxes method
felipefreitag Oct 12, 2026
7637497
docs(examples): add Inboxes examples
felipefreitag Oct 12, 2026
fc5d96b
feat(webhooks): add the inbox webhook event types
felipefreitag Oct 12, 2026
f7f70f6
chore: bump version to 2.50.0
felipefreitag Oct 12, 2026
5a38b38
test(inboxes): assert the path of every async method
felipefreitag Oct 12, 2026
765ee17
fix(inboxes): leave empty list filters out of the query
felipefreitag Oct 12, 2026
90d0486
docs(examples): reply to the first email of the thread
felipefreitag Oct 12, 2026
a162fb7
fix(inboxes): remove label_id from the thread update params
felipefreitag Oct 13, 2026
8207c6d
feat(inboxes): add attachments to drafts
felipefreitag Oct 13, 2026
fe14e17
docs(examples): attach a file to the example draft
felipefreitag Oct 13, 2026
7e2d410
docs(inboxes): align the doc comments with the API audit
felipefreitag Oct 13, 2026
cc96a60
feat(inboxes): accept an idempotency key on the inbox sending methods
felipefreitag Oct 13, 2026
81201d9
docs(examples): send the example draft with an idempotency key
felipefreitag Oct 13, 2026
4af433a
feat(inboxes): accept custom headers on reply, forward and send draft
felipefreitag Oct 13, 2026
fc2f1f7
docs(examples): send the example draft with a custom header
felipefreitag Oct 13, 2026
4000679
docs(inboxes): align the doc comments with the spec review
felipefreitag Oct 13, 2026
e20cf43
fix(inboxes): rename message_count to email_count
felipefreitag Oct 14, 2026
877bdbb
docs(inboxes): describe the agent tone presets
felipefreitag Oct 14, 2026
4803c17
feat(inboxes): match the stabilized Inboxes API
dielduarte Oct 16, 2026
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
68 changes: 68 additions & 0 deletions examples/async/inboxes_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import asyncio
import os

import resend

if not os.environ["RESEND_API_KEY"]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This lookup raises KeyError before the intended error whenever RESEND_API_KEY is unset. Use os.environ.get() so the guard emits the missing-key message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At examples/async/inboxes_async.py, line 6:

<comment>This lookup raises `KeyError` before the intended error whenever `RESEND_API_KEY` is unset. Use `os.environ.get()` so the guard emits the missing-key message.</comment>

<file context>
@@ -0,0 +1,68 @@
+
+import resend
+
+if not os.environ["RESEND_API_KEY"]:
+    raise EnvironmentError("RESEND_API_KEY is missing")
+
</file context>
Suggested change
if not os.environ["RESEND_API_KEY"]:
if not os.environ.get("RESEND_API_KEY"):

raise EnvironmentError("RESEND_API_KEY is missing")


async def main() -> None:
created: resend.Inboxes.CreateResponse = await resend.Inboxes.create_async(
{"email_address": "support@acme.com", "name": "Customer Support"}
)
inbox_id = created["id"]
print(f"Created inbox: {inbox_id}")

inbox: resend.Inbox = await resend.Inboxes.get_async(inbox_id)
print(f"Retrieved inbox: {inbox['email_address']}")

label: resend.Inboxes.Labels.CreateResponse = (
await resend.Inboxes.Labels.create_async(
{"inbox_id": inbox_id, "name": "Urgent", "color": "#E93D82"}
)
)

threads: resend.Inboxes.Threads.SearchResponse = (
await resend.Inboxes.Threads.search_async(
inbox_id, {"query": "refund", "folders": ["inbox"]}
)
)
print(f"Found {len(threads['data'])} matching threads")

if threads["data"]:
thread_id = threads["data"][0]["id"]
await resend.Inboxes.Threads.Labels.add_async(
{"inbox_id": inbox_id, "thread_id": thread_id, "labels": [label["id"]]}
)
emails: resend.Inboxes.Threads.Emails.ListResponse = (
await resend.Inboxes.Threads.Emails.list_async(inbox_id, thread_id)
)
reply: resend.Inboxes.Threads.Emails.ReplyResponse = (
await resend.Inboxes.Threads.Emails.reply_async(
{
"inbox_id": inbox_id,
"thread_id": thread_id,
"email_id": emails["data"][0]["id"],
"text": "Thanks, we are on it.",
}
)
)
print(f"Sent reply: {reply['id']}")

draft: resend.Inboxes.Drafts.CreateResponse = (
await resend.Inboxes.Drafts.create_async(
{"inbox_id": inbox_id, "to": "ada@example.org", "text": "Hello"}
)
)
sent: resend.Inboxes.Drafts.SendResponse = await resend.Inboxes.Drafts.send_async(
inbox_id, draft["id"]
)
print(f"Sent draft as email: {sent['id']}")

await resend.Inboxes.Labels.remove_async(inbox_id, label["id"])
await resend.Inboxes.remove_async(inbox_id)


if __name__ == "__main__":
asyncio.run(main())
169 changes: 169 additions & 0 deletions examples/inboxes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import os

import resend

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

create_params: resend.Inboxes.CreateParams = {
"email_address": "support@acme.com",
"name": "Customer Support",
"from_name": "Ada from Support",
}
created: resend.Inboxes.CreateResponse = resend.Inboxes.create(create_params)
inbox_id = created["id"]
print(f"Created inbox: {inbox_id}")

inbox: resend.Inbox = resend.Inboxes.get("support@acme.com")
print(f"Retrieved inbox {inbox['email_address']} with {inbox['unread']} unread threads")

inboxes: resend.Inboxes.ListResponse = resend.Inboxes.list({"limit": 10})
print(f"Found {len(inboxes['data'])} inboxes")

update_params: resend.Inboxes.UpdateParams = {"id": inbox_id, "name": "Support"}
resend.Inboxes.update(update_params)

agent: resend.InboxAgentSettings = resend.Inboxes.Agent.get(inbox_id)
print(f"Agent actions: {agent['actions']}")
agent_params: resend.Inboxes.Agent.UpdateParams = {
"inbox_id": inbox_id,
"tone": "concise_casual",
"actions": ["draft_reply", "add_labels"],
}
resend.Inboxes.Agent.update(agent_params)

label_params: resend.Inboxes.Labels.CreateParams = {
"inbox_id": inbox_id,
"name": "Urgent",
"color": "#E93D82",
}
label: resend.Inboxes.Labels.CreateResponse = resend.Inboxes.Labels.create(
label_params
)
label_update: resend.Inboxes.Labels.UpdateParams = {
"inbox_id": inbox_id,
"id": label["id"],
"color": "#F76B15",
}
resend.Inboxes.Labels.update(label_update)
labels: resend.Inboxes.Labels.ListResponse = resend.Inboxes.Labels.list(inbox_id)
print(f"Found {len(labels['data'])} labels")

threads: resend.Inboxes.Threads.ListResponse = resend.Inboxes.Threads.list(
inbox_id, {"folders": ["inbox", "sent"], "read": False, "limit": 10}
)
print(f"Found {len(threads['data'])} unread threads")

search_params: resend.Inboxes.Threads.SearchParams = {
"query": "refund",
"has_attachment": False,
}
results: resend.Inboxes.Threads.SearchResponse = resend.Inboxes.Threads.search(
inbox_id, search_params
)
for result in results["data"]:
print(f"{result['subject']}: matched email {result['matched_email_id']}")

if threads["data"]:
thread_id = threads["data"][0]["id"]
thread: resend.InboxThreadSummary = resend.Inboxes.Threads.get(inbox_id, thread_id)
print(f"Thread {thread['id']} is in {thread['folder']}")

thread_update: resend.Inboxes.Threads.UpdateParams = {
"inbox_id": inbox_id,
"id": thread_id,
"read": True,
}
resend.Inboxes.Threads.update(thread_update)

add_labels: resend.Inboxes.Threads.Labels.AddParams = {
"inbox_id": inbox_id,
"thread_id": thread_id,
"labels": [label["id"]],
}
thread_labels: resend.Inboxes.Threads.Labels.ListResponse = (
resend.Inboxes.Threads.Labels.add(add_labels)
)
print(f"Thread has {len(thread_labels['data'])} labels")
resend.Inboxes.Threads.Labels.remove(inbox_id, thread_id, label["id"])

emails: resend.Inboxes.Threads.Emails.ListResponse = (
resend.Inboxes.Threads.Emails.list(inbox_id, thread_id)
)
email_id = emails["data"][0]["id"]
email: resend.InboxThreadEmail = resend.Inboxes.Threads.Emails.get(
inbox_id, thread_id, email_id
)
print(f"First email from {email['from']}: {email['subject']}")

reply_params: resend.Inboxes.Threads.Emails.ReplyParams = {
"inbox_id": inbox_id,
"thread_id": thread_id,
"email_id": email_id,
"text": "Thanks, we are on it.",
"attachments": [{"filename": "note.txt", "content": "VGhhbmtzIQ=="}],
}
reply: resend.Inboxes.Threads.Emails.ReplyResponse = (
resend.Inboxes.Threads.Emails.reply(reply_params)
)
print(f"Sent reply: {reply['id']}")

forward_params: resend.Inboxes.Threads.Emails.ForwardParams = {
"inbox_id": inbox_id,
"thread_id": thread_id,
"email_id": email_id,
"to": ["colleague@example.org"],
"text": "Flagging this for you.",
}
forward: resend.Inboxes.Threads.Emails.ForwardResponse = (
resend.Inboxes.Threads.Emails.forward(forward_params)
)
print(f"Sent forward: {forward['id']}")

resend.Inboxes.Threads.remove(inbox_id, thread_id)

draft_params: resend.Inboxes.Drafts.CreateParams = {
"inbox_id": inbox_id,
"to": ["ada@example.org"],
"subject": "Refund for order 1041",
"text": "Refund issued for order 1041.",
"attachments": [
{
"content": "UmVmdW5kIHJlY2VpcHQ=",
"filename": "receipt.txt",
"content_type": "text/plain",
}
],
}
draft: resend.Inboxes.Drafts.CreateResponse = resend.Inboxes.Drafts.create(
draft_params
)
draft_update: resend.Inboxes.Drafts.UpdateParams = {
"inbox_id": inbox_id,
"id": draft["id"],
"html": "<p>Refund issued for order 1041.</p>",
}
updated: resend.Inboxes.Drafts.UpdateResponse = resend.Inboxes.Drafts.update(
draft_update
)
updated_draft: resend.InboxDraft = resend.Inboxes.Drafts.get(inbox_id, updated["id"])
print(f"Draft {updated_draft['id']} is a {updated_draft['type']} draft")
print(f"Draft attachments: {[a['filename'] for a in updated_draft['attachments']]}")

drafts: resend.Inboxes.Drafts.ListResponse = resend.Inboxes.Drafts.list(inbox_id)
print(f"Found {len(drafts['data'])} drafts")

send_options: resend.Inboxes.Drafts.SendOptions = {
"idempotency_key": f"send-draft-{draft['id']}",
}
send_params: resend.Inboxes.Drafts.SendParams = {
"headers": {"X-Ticket-Id": "4821"},
}
sent: resend.Inboxes.Drafts.SendResponse = resend.Inboxes.Drafts.send(
inbox_id, draft["id"], send_params, send_options
)
print(f"Sent draft as email: {sent['id']}")

resend.Inboxes.Labels.remove(inbox_id, label["id"])
removed: resend.Inboxes.RemoveResponse = resend.Inboxes.remove(inbox_id)
print(f"Removed inbox: {removed['deleted']}")
95 changes: 95 additions & 0 deletions resend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@
from .http_client_async import \
AsyncHTTPClient # Okay to import AsyncHTTPClient since it is just an interface.
from .http_client_requests import RequestsClient
from .inboxes._inbox import (Inbox, InboxListItem, InboxMessageFolder,
MoveThreadFolder)
from .inboxes._inboxes import Inboxes
from .inboxes.agent._inbox_agent import InboxAgent
from .inboxes.agent._inbox_agent_settings import (InboxAgentAction,
InboxAgentSettings,
InboxAgentTone)
from .inboxes.drafts._inbox_draft import (InboxDraft, InboxDraftAttachment,
InboxDraftListItem, InboxDraftType)
from .inboxes.drafts._inbox_drafts import InboxDrafts
from .inboxes.labels._inbox_label import InboxLabel
from .inboxes.labels._inbox_labels import InboxLabels
from .inboxes.threads._inbox_thread import (InboxThread, InboxThreadLabel,
InboxThreadSearchResult,
InboxThreadSummary)
from .inboxes.threads._inbox_threads import InboxThreads
from .inboxes.threads.emails._inbox_thread_email import (
InboxThreadEmail, InboxThreadEmailAttachment)
from .inboxes.threads.emails._inbox_thread_emails import InboxThreadEmails
from .inboxes.threads.labels._inbox_thread_labels import InboxThreadLabels
from .logs._log import Log
from .logs._logs import Logs
from .oauth_grants._oauth_grant import OAuthGrant, OAuthGrantClient
Expand Down Expand Up @@ -91,6 +111,29 @@
EmailFailedEvent, EmailOpenedEvent,
EmailReceivedEvent, EmailScheduledEvent,
EmailSentEvent, EmailSuppressedEvent,
InboxCreatedEvent, InboxDeletedEvent,
InboxDeletedEventData,
InboxDraftCreatedEvent,
InboxDraftDeletedEvent,
InboxDraftEventData, InboxDraftSentEvent,
InboxDraftSentEventData,
InboxDraftUpdatedEvent,
InboxEmailReceivedEvent,
InboxEmailSentEvent, InboxEventData,
InboxEventDraft, InboxEventEmail,
InboxEventEmailAttachment,
InboxEventSource,
InboxThreadAssignedEvent,
InboxThreadAssignedEventData,
InboxThreadCreatedEvent,
InboxThreadEmailEventData,
InboxThreadFolderUpdatedEvent,
InboxThreadFolderUpdatedEventData,
InboxThreadLabelsUpdatedEvent,
InboxThreadLabelsUpdatedEventData,
InboxThreadUnassignedEvent,
InboxThreadUnassignedEventData,
InboxUpdatedEvent,
ReceivedEmailEventData,
SuppressionAddedEvent,
SuppressionEventData,
Expand Down Expand Up @@ -141,6 +184,13 @@
"Suppressions",
"SuppressionsBatch",
"Usage",
"Inboxes",
"InboxAgent",
"InboxLabels",
"InboxThreads",
"InboxThreadEmails",
"InboxThreadLabels",
"InboxDrafts",
# Types
"Audience",
"Automation",
Expand Down Expand Up @@ -229,6 +279,51 @@
"TopicCreatedEvent",
"TopicUpdatedEvent",
"TopicDeletedEvent",
"InboxEventSource",
"InboxEventEmail",
"InboxEventEmailAttachment",
"InboxEventDraft",
"InboxEventData",
"InboxDeletedEventData",
"InboxThreadEmailEventData",
"InboxThreadFolderUpdatedEventData",
"InboxThreadAssignedEventData",
"InboxThreadUnassignedEventData",
"InboxThreadLabelsUpdatedEventData",
"InboxDraftEventData",
"InboxDraftSentEventData",
"InboxCreatedEvent",
"InboxUpdatedEvent",
"InboxDeletedEvent",
"InboxThreadCreatedEvent",
"InboxEmailReceivedEvent",
"InboxEmailSentEvent",
"InboxThreadFolderUpdatedEvent",
"InboxThreadAssignedEvent",
"InboxThreadUnassignedEvent",
"InboxThreadLabelsUpdatedEvent",
"InboxDraftCreatedEvent",
"InboxDraftUpdatedEvent",
"InboxDraftSentEvent",
"InboxDraftDeletedEvent",
"Inbox",
"InboxListItem",
"InboxMessageFolder",
"MoveThreadFolder",
"InboxAgentAction",
"InboxAgentSettings",
"InboxAgentTone",
"InboxLabel",
"InboxThread",
"InboxThreadLabel",
"InboxThreadSearchResult",
"InboxThreadSummary",
"InboxThreadEmail",
"InboxThreadEmailAttachment",
"InboxDraft",
"InboxDraftAttachment",
"InboxDraftListItem",
"InboxDraftType",
"Topic",
"OAuthGrant",
"OAuthGrantClient",
Expand Down
Loading
Loading