Repository navigation
feat(inboxes): add the inboxes API - #172
Conversation
There was a problem hiding this comment.
3 issues found across 17 files
Confidence score: 4/5
inboxes.goadds routes that need more than the send-only permissions on restricted Resend keys, and inboxes are limited to beta-enabled accounts. Confirm the required permissions and account availability.UpdateInboxRequest.FromNamedoes not clear the value as documented: a non-nil pointer to an empty string serializes as"from_name": "", notnull. Align the serialization with the clear behavior promised in the PR description.inbox_agent.goletsClearInstructions()orClearTone()take precedence over a later assigned value on the same request, silently dropping that value. Make the final setter call determine what gets sent.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. 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. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="inbox_agent.go">
<violation number="1" location="inbox_agent.go:155">
P3: In setNullableString the `cleared` flag always wins over a non-nil value, so a caller who calls ClearInstructions()/ClearTone() and then assigns a new value on the same request struct gets `instructions`/`tone` silently serialized as null; the assigned value is dropped. Prefer the value when it is non-nil so the most recent action wins. This also governs the identical Clear* pattern in UpdateInboxDraftRequest, so the fix applies there too.</violation>
</file>
<file name="inboxes.go">
<violation number="1" location="inboxes.go:88">
P2: `UpdateInboxRequest.FromName` claims a pointer to an empty string clears the value, but with `omitempty` a non-nil `&""` still serializes as `"from_name": ""`, not `null`. The PR description says clearable fields use `Clear*` methods to send `null`, and the sibling types added in this same PR (inbox_agent.go `ClearInstructions`/`ClearTone`, inbox_drafts.go `ClearSubject`/`ClearHtml`/`ClearText`) implement exactly that via `MarshalJSON` + `setNullableString`. If the API only clears on `null`, this will send `""` instead and silently fail to clear; at minimum it diverges from the Clear* convention established in this PR.</violation>
<violation number="2" location="inboxes.go:145">
P2: Custom agent: **API Key Permission Check SDK Methods**
These new inbox routes require more than the send-only permissions available to restricted Resend keys, and inboxes are limited to beta-enabled accounts. Confirm that the existing production keys and accounts have the required `full_access` and inbox beta access before release.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| return CreateInboxResponse{}, errors.New("[ERROR]: EmailAddress is required") | ||
| } | ||
|
|
||
| req, err := s.client.NewRequest(ctx, http.MethodPost, "inboxes", params) |
There was a problem hiding this comment.
P2: Custom agent: API Key Permission Check SDK Methods
These new inbox routes require more than the send-only permissions available to restricted Resend keys, and inboxes are limited to beta-enabled accounts. Confirm that the existing production keys and accounts have the required full_access and inbox beta access before release.
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 inboxes.go, line 145:
<comment>These new inbox routes require more than the send-only permissions available to restricted Resend keys, and inboxes are limited to beta-enabled accounts. Confirm that the existing production keys and accounts have the required `full_access` and inbox beta access before release.</comment>
<file context>
@@ -0,0 +1,263 @@
+ return CreateInboxResponse{}, errors.New("[ERROR]: EmailAddress is required")
+ }
+
+ req, err := s.client.NewRequest(ctx, http.MethodPost, "inboxes", params)
+ if err != nil {
+ return CreateInboxResponse{}, ErrFailedToCreateInboxesCreateRequest
</file context>
| Name string `json:"name,omitempty"` | ||
| // FromName is the name recipients see when mail is sent from this inbox. Max 78 characters. A | ||
| // pointer to an empty string removes it. | ||
| FromName *string `json:"from_name,omitempty"` |
There was a problem hiding this comment.
P2: UpdateInboxRequest.FromName claims a pointer to an empty string clears the value, but with omitempty a non-nil &"" still serializes as "from_name": "", not null. The PR description says clearable fields use Clear* methods to send null, and the sibling types added in this same PR (inbox_agent.go ClearInstructions/ClearTone, inbox_drafts.go ClearSubject/ClearHtml/ClearText) implement exactly that via MarshalJSON + setNullableString. If the API only clears on null, this will send "" instead and silently fail to clear; at minimum it diverges from the Clear* convention established in this PR.
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 inboxes.go, line 88:
<comment>`UpdateInboxRequest.FromName` claims a pointer to an empty string clears the value, but with `omitempty` a non-nil `&""` still serializes as `"from_name": ""`, not `null`. The PR description says clearable fields use `Clear*` methods to send `null`, and the sibling types added in this same PR (inbox_agent.go `ClearInstructions`/`ClearTone`, inbox_drafts.go `ClearSubject`/`ClearHtml`/`ClearText`) implement exactly that via `MarshalJSON` + `setNullableString`. If the API only clears on `null`, this will send `""` instead and silently fail to clear; at minimum it diverges from the Clear* convention established in this PR.</comment>
<file context>
@@ -0,0 +1,263 @@
+ Name string `json:"name,omitempty"`
+ // FromName is the name recipients see when mail is sent from this inbox. Max 78 characters. A
+ // pointer to an empty string removes it.
+ FromName *string `json:"from_name,omitempty"`
+}
+
</file context>
| if cleared { | ||
| aux[key] = nil | ||
| } else if value != nil { | ||
| aux[key] = *value | ||
| } |
There was a problem hiding this comment.
P3: In setNullableString the cleared flag always wins over a non-nil value, so a caller who calls ClearInstructions()/ClearTone() and then assigns a new value on the same request struct gets instructions/tone silently serialized as null; the assigned value is dropped. Prefer the value when it is non-nil so the most recent action wins. This also governs the identical Clear* pattern in UpdateInboxDraftRequest, so the fix applies there too.
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 inbox_agent.go, line 155:
<comment>In setNullableString the `cleared` flag always wins over a non-nil value, so a caller who calls ClearInstructions()/ClearTone() and then assigns a new value on the same request struct gets `instructions`/`tone` silently serialized as null; the assigned value is dropped. Prefer the value when it is non-nil so the most recent action wins. This also governs the identical Clear* pattern in UpdateInboxDraftRequest, so the fix applies there too.</comment>
<file context>
@@ -0,0 +1,160 @@
+}
+
+func setNullableString(aux map[string]any, key string, value *string, cleared bool) {
+ if cleared {
+ aux[key] = nil
+ } else if value != nil {
</file context>
| if cleared { | |
| aux[key] = nil | |
| } else if value != nil { | |
| aux[key] = *value | |
| } | |
| func setNullableString(aux map[string]any, key string, value *string, cleared bool) { | |
| if value != nil { | |
| aux[key] = *value | |
| } else if cleared { | |
| aux[key] = nil | |
| } | |
| } |
There was a problem hiding this comment.
0 issues found across 8 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Confidence score: 4/5
inboxes.goleaves the documented folder restriction unenforced, so callers can still setFoldertoInboxFolderSent. Use a defined string type to enforce the restriction at compile time.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. 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. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="inboxes.go">
<violation number="1" location="inboxes.go:24">
P2: This alias does not prevent callers from setting `Folder: InboxFolderSent`, so the documented restriction is not enforced even at compile time. Make this a defined string type so the sent-folder constant cannot be assigned accidentally.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
|
|
||
| // InboxMoveThreadFolder is a folder a thread update can move a thread to. It has no `sent`, | ||
| // because `sent` is assigned automatically. | ||
| type InboxMoveThreadFolder = string |
There was a problem hiding this comment.
P2: This alias does not prevent callers from setting Folder: InboxFolderSent, so the documented restriction is not enforced even at compile time. Make this a defined string type so the sent-folder constant cannot be assigned accidentally.
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 inboxes.go, line 24:
<comment>This alias does not prevent callers from setting `Folder: InboxFolderSent`, so the documented restriction is not enforced even at compile time. Make this a defined string type so the sent-folder constant cannot be assigned accidentally.</comment>
<file context>
@@ -14,11 +14,22 @@ const (
+// InboxMoveThreadFolder is a folder a thread update can move a thread to. It has no `sent`,
+// because `sent` is assigned automatically.
+type InboxMoveThreadFolder = string
+
+const (
</file context>
| type InboxMoveThreadFolder = string | |
| type InboxMoveThreadFolder string |
There was a problem hiding this comment.
0 issues found across 5 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 5 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
… reply and forward
There was a problem hiding this comment.
0 issues found across 5 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 5 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
…otes with the spec review
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Auto-approval blocked by 4 unresolved issues from previous reviews.
View guided diff | Re-trigger cubic
dielduarte
left a comment
There was a problem hiding this comment.
some findings so far in code, but everything is working I tested locally:
- Clearing to on a draft: UpdateInboxDraftRequest.To can only be left out or sent as []. The spec documents only null as the way to clear to; it allows [] just for cc and bcc. I tested it, and the API accepts to: [] and clears it, so it works, but the spec doesn't promise it. Fix with a ClearTo() like the existing ClearSubject(), or get [] documented in the spec.
- Reply attachments: reply and forward reuse the emails Attachment type, which sends the file content as an array of numbers rather than a string. I sent a reply with an attachment to check, and it arrived correctly (text/plain, 20 bytes).
- Nits:
- draft create trims whitespace before its non-empty check, which is stricter than the spec
- the helper setNullableString is in inbox_agent.go; the codebase keeps shared helpers in utils.go
- withQuery is a very generic name
- one test only re-checks string constants
Follow resend-openapi #137 and resend-node 7943103: - Inboxes.Update and Inboxes.Remove take the Inbox ID or the inbox email address, like every other inbox route. - Agent: EnabledActions is now Actions, the archive_thread and delete_thread actions are now archive and move_to_trash, and Tone is one of the four InboxAgentTone presets. - Add Inboxes.Threads.Labels with Add, Set and Remove. Thread Update no longer takes AddLabelIds or RemoveLabelIds. - Drafts.Update returns UpdateInboxDraftResponse with only Object and Id. - Search results lose Highlights. - Inbox adds UpdatedAt, and InboxListItem adds CreatedAt and UpdatedAt. - Reply and forward no longer cap attachments at 10 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
1 existing issue remains and 1 new issue found across 14 files (changes from recent commits).
Confidence score: 4/5
Inboxes.Threads.Labelsrequires afull_accessAPI key; asending_accesskey cannot use these operations. Confirm production keys have the required permission before enabling them.- The email-address route test does not cover
Inboxes.UpdateorInboxes.Remove, so regressions in either changed route could go unnoticed. Add both calls to the test.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. 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. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="inbox_thread_labels_test.go">
<violation number="1" location="inbox_thread_labels_test.go:110">
P2: This email-address route test omits `Inboxes.Update` and `Inboxes.Remove`, despite those routes being changed to accept email addresses. A regression in either top-level route would pass the test; add both calls and their expected paths.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
View guided diff | Re-trigger cubic
| assert.EqualError(t, err, "[ERROR]: Label ID is required") | ||
| } | ||
|
|
||
| func TestInboxRoutesAcceptAnEmailAddress(t *testing.T) { |
There was a problem hiding this comment.
P2: This email-address route test omits Inboxes.Update and Inboxes.Remove, despite those routes being changed to accept email addresses. A regression in either top-level route would pass the test; add both calls and their expected paths.
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 inbox_thread_labels_test.go, line 110:
<comment>This email-address route test omits `Inboxes.Update` and `Inboxes.Remove`, despite those routes being changed to accept email addresses. A regression in either top-level route would pass the test; add both calls and their expected paths.</comment>
<file context>
@@ -0,0 +1,144 @@
+ assert.EqualError(t, err, "[ERROR]: Label ID is required")
+}
+
+func TestInboxRoutesAcceptAnEmailAddress(t *testing.T) {
+ setup()
+ defer teardown()
</file context>
Summary
This PR adds the Inboxes API to the Go SDK. It covers the 26 routes under
/inboxesand the 14inbox.*webhook events.client.Inboxes:Create,List,Get,Update,Remove.Getaccepts an inbox ID or the inbox email address.client.Inboxes.Agent:Get,Update.client.Inboxes.Labels:List,Create,Update,Remove.client.Inboxes.Threads:List,Search,Get,Update,Remove.client.Inboxes.Threads.Emails:List,Get,Reply,Forward.client.Inboxes.Drafts:List,Create,Get,Update,Remove,Send.EventInbox*webhook event constants.examples/inboxes.go.4.9.0, in its own commit.Each method has a
WithContextvariant, the same asSuppressions.Drafts.Create,Drafts.Send,Threads.Emails.ReplyandThreads.Emails.Forwardalso have aWithOptionsvariant that takes anIdempotencyKey, the same asEmails.SendWithOptions. The names follow the Node SDK preview. The shapes follow the spec PR resend/resend-openapi#125:objectandidonly.idonly.#RRGGBBstrings.ReplyandForwardacceptAttachments. They use theAttachmenttype ofEmails.Send.Reply,ForwardandDrafts.SendacceptHeaders, amap[string]stringlikeSendEmailRequest.Headers.Drafts.Sendtakes an optional*SendInboxDraftRequestfor it, and nil params send no body. Resend setsIn-Reply-To,ReferencesandReply-To, so the API returns a 422 for them.Threads.Updatesendsread,folder,add_label_idsandremove_label_ids.FolderusesInboxMoveThreadFolder, which has nosent.Drafts.CreateandDrafts.UpdateacceptAttachmentsof typeInboxDraftAttachmentInput. Each one needsContent,FilenameandContentType, and it has noPathorContentId. On update, the field replaces every attachment, and an empty slice removes them all.InboxDraftandInboxDraftListItemreturnattachments.Some update fields can be cleared with
null. For these, the request types haveClear*methods (UpdateInboxAgentRequest.ClearTone,UpdateInboxDraftRequest.ClearHtml). This followsSetUnsubscribedandSetOpenTracking. A nil slice is omitted, and an empty slice is sent as[]. SoEnabledActions: []resend.InboxAgentAction{}disables all agent actions.Depends on
Idempotency-Keyon inbox sends) and #12509 (custom headers). All are merged and live.Verification
All pass on Go 1.26.1. CI passes on Go 1.21 to 1.26.
I also ran an end-to-end script through the built SDK against
https://api.resend.com, on the shared test inbox. 47 of 47 calls passed:Inboxes.Create: 403 "You have reached the inbox limit of your plan" (expected, the team is at its limit).Inboxes.List,Get(by ID and by email),Updateand restore: OK.Agent.Get,Update, restore withClearToneandClearInstructions: OK.Labels.Create(lowercase hex, returned uppercase),Update,Updatewith an empty body,List,Remove: OK.Threads.Search(returnsmatched_email_idandhighlights),Listwithfoldersandread,Get: OK.Threads.Updatewithreadandfolderarchive, then back toinbox: OK.Threads.Updatewithadd_label_ids(the thread gets the label), thenremove_label_ids(the label stays on the inbox): OK. The same ID in both arrays returns the expected validation error.Threads.Emails.List,Get,Reply,Replywith a text attachment,Forwardwith a text attachment: OK. Each send returned the sent emailid.Drafts.Createwith an attachment, thenGetandListreturn it.Updatewith a new attachment replaces it, andUpdatewith[]removes it: OK.Drafts.UpdatewithClearText,Sendof a draft with an attachment,Remove, and a reply draft create and remove: OK.Threads.Removeon the seeded thread and on the thread of the sent draft: OK.Follow-up runs on the same inbox:
Drafts.Sendof a reply draft with no subject returns the expected "subject is required" error.Drafts.CreateWithOptionstwice with the same key returns the same draft ID, and the inbox has one draft. The same key with a different body returns the idempotency error.Drafts.SendWithOptionstwice with the same key returns the same sent email ID.ReplywithX-Ticket-IdinHeadersis sent to the Reply-To address.Drafts.Sendwith a custom header and with nil params both send.Replywithreply-toinHeadersreturns the expected 422.Not in this PR