Skip to content

feat: inboxes endpoints - #1088

Open
isabellaaquino wants to merge 1 commit into
canaryfrom
preview-inboxes
Open

feat: inboxes endpoints#1088
isabellaaquino wants to merge 1 commit into
canaryfrom
preview-inboxes

Conversation

@isabellaaquino

@isabellaaquino isabellaaquino commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Adds inbox management to the Resend Node.js client, enabling programmatic control over inboxes, threads, labels, and drafts. The client now exposes resend.inboxes with nested resources, and the package version is bumped to 6.27.0-preview-inboxes.0 as a preview release.

New Features

  • Adds resend.inboxes with create, list, get, update, and remove methods.
  • Adds resend.inboxes.threads for listing, fetching, updating, and removing threads.
  • Adds resend.inboxes.threads.emails for getting, replying to, and forwarding thread emails.
  • Adds resend.inboxes.labels and resend.inboxes.drafts with full CRUD, plus sending drafts.
  • Exports all new inbox types and interfaces from the package entry point.
  • Updates the readme with inbox examples and the changelog for 6.27.0.

Written for commit ad5189b. Summary will update on new commits.

Review in cubic

@isabellaaquino
isabellaaquino requested a review from a team as a code owner September 7, 2026 23:00
@cubic-dev-ai

cubic-dev-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Running ultrareview automatically — This PR adds a large new public API surface (inboxes, threads, drafts, labels) with many new endpoint methods and custom query/pagination logic, so a deeper review reduces the risk of subtle bugs affecting all users of these new endpoints.. I'll post findings when complete.

@pkg-pr-new

pkg-pr-new Bot commented Sep 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/resend@1088

commit: ad5189b

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

Ultrareview completed in 10m 19s

8 issues found across 47 files

Confidence score: 2/5

  • src/inboxes/inboxes.spec.ts contains a hardcoded Resend API key, creating a credential-exposure risk if the key is valid or test recordings are shared — revoke/rotate it and replace it with a redacted fixture.
  • src/resend.ts adds authenticated inbox operations whose required API-key permissions are not confirmed; production calls could fail despite the SDK surface compiling — verify and document the necessary permissions.
  • src/inboxes/threads/interfaces/update-inbox-thread.interface.ts types PATCH results as an inbox, while src/inboxes/labels/interfaces/update-inbox-label.interface.ts and src/inboxes/drafts/interfaces/create-inbox-draft.interface.ts allow invalid empty updates or drafts; correct the response type and require at least one valid field.
  • src/inboxes/threads/threads.spec.ts and src/inboxes/labels/labels.spec.ts do not assert request URLs or HTTP methods, so endpoint wiring regressions could pass tests — add method and URL assertions.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/inboxes/labels/labels.spec.ts">

<violation number="1" location="src/inboxes/labels/labels.spec.ts:41">
P3: The update and remove tests only assert the response body (`data.data`); they never verify the HTTP method (PATCH/DELETE) or the request URL. The create and list tests in this same file assert method/URL, and the sibling drafts.spec.ts update test asserts `method: 'PATCH'` and the endpoint path. As written, a regression that changes `update` to POST/PUT or `remove` to GET would still pass. Add `expect(fetchMock).toHaveBeenCalledWith('https://api.resend.com/inboxes/${inboxId}/labels/${labelId}', expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Later' }) }))` to the update test and the DELETE equivalent to the remove test, matching the drafts spec.</violation>
</file>

<file name="src/inboxes/threads/threads.spec.ts">

<violation number="1" location="src/inboxes/threads/threads.spec.ts:150">
P2: The `remove` test only asserts the parsed response and never checks the outgoing request, so it would still pass if `InboxThreads.remove` hit the wrong URL or used the wrong HTTP method. Add an assertion that `fetchMock` was called with `https://api.resend.com/inboxes/${inboxId}/threads/${threadId}` and method `DELETE`, matching the `get`/`update` tests in this file.</violation>
</file>

<file name="src/inboxes/drafts/interfaces/create-inbox-draft.interface.ts">

<violation number="1" location="src/inboxes/drafts/interfaces/create-inbox-draft.interface.ts:14">
P2: `CreateInboxDraftOptions` allows `{}` and replies without content, but the endpoint rejects drafts without a non-empty field. Make `CreateInboxDraftContent` a `RequireAtLeastOne` type so invalid payloads are caught at compile time.</violation>
</file>

<file name="CHANGELOG.md">

<violation number="1" location="CHANGELOG.md:1">
P3: This entry labels the feature as stable `6.27.0`, but the package currently publishes `6.27.0-preview-inboxes.0`. Use the preview version in the heading, or add the stable heading only when the package is released as `6.27.0`.</violation>
</file>

<file name="src/inboxes/threads/interfaces/update-inbox-thread.interface.ts">

<violation number="1" location="src/inboxes/threads/interfaces/update-inbox-thread.interface.ts:16">
P2: The update response is typed as an inbox rather than an inbox thread. This disagrees with the other thread response types and prevents consumers from narrowing the PATCH result as an `inbox_thread`; use the thread object literal or reuse `InboxThreadSummary`.</violation>
</file>

<file name="src/inboxes/labels/interfaces/update-inbox-label.interface.ts">

<violation number="1" location="src/inboxes/labels/interfaces/update-inbox-label.interface.ts:4">
P2: `UpdateInboxLabelOptions` permits `{}`, allowing typed callers to send a no-op label PATCH. Define this payload with `RequireAtLeastOne` so every update changes `name` or `color`, matching the other inbox PATCH contracts.</violation>
</file>

<file name="src/resend.ts">

<violation number="1" location="src/resend.ts:68">
P2: Custom agent: **API Key Permission Check SDK Methods**

The new `inboxes` surface adds authenticated inbox, thread, draft, label, reply, and forward operations. Confirm production API keys have the required inbox permissions before release to prevent authorization failures.</violation>
</file>

<file name="src/inboxes/inboxes.spec.ts">

<violation number="1" location="src/inboxes/inboxes.spec.ts:14">
P1: Custom agent: **API Key Permission Check SDK Methods**

A hardcoded Resend API key (`re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop`) is committed in the test file. Secrets/API tokens must be redacted from test recordings and specs even when the keys are ephemeral; the fixture should use a non-sensitive placeholder instead (e.g., `re_<YOUR_API_KEY>` or an env-driven value).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

const fetchMocker = createFetchMock(vi);
fetchMocker.enableMocks();

const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');

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.

P1: Custom agent: API Key Permission Check SDK Methods

A hardcoded Resend API key (re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop) is committed in the test file. Secrets/API tokens must be redacted from test recordings and specs even when the keys are ephemeral; the fixture should use a non-sensitive placeholder instead (e.g., re_<YOUR_API_KEY> or an env-driven value).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/inboxes/inboxes.spec.ts, line 14:

<comment>A hardcoded Resend API key (`re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop`) is committed in the test file. Secrets/API tokens must be redacted from test recordings and specs even when the keys are ephemeral; the fixture should use a non-sensitive placeholder instead (e.g., `re_<YOUR_API_KEY>` or an env-driven value).</comment>

<file context>
@@ -0,0 +1,329 @@
+const fetchMocker = createFetchMock(vi);
+fetchMocker.enableMocks();
+
+const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
+
+describe('Inboxes', () => {
</file context>
Suggested change
const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
const resend = new Resend('re_<YOUR_API_KEY>');


const data = await resend.inboxes.threads.get(inboxId, threadId);

expect(data.data).toEqual(response);

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.

P2: The remove test only asserts the parsed response and never checks the outgoing request, so it would still pass if InboxThreads.remove hit the wrong URL or used the wrong HTTP method. Add an assertion that fetchMock was called with https://api.resend.com/inboxes/${inboxId}/threads/${threadId} and method DELETE, matching the get/update tests in this file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/inboxes/threads/threads.spec.ts, line 150:

<comment>The `remove` test only asserts the parsed response and never checks the outgoing request, so it would still pass if `InboxThreads.remove` hit the wrong URL or used the wrong HTTP method. Add an assertion that `fetchMock` was called with `https://api.resend.com/inboxes/${inboxId}/threads/${threadId}` and method `DELETE`, matching the `get`/`update` tests in this file.</comment>

<file context>
@@ -0,0 +1,214 @@
+
+      const data = await resend.inboxes.threads.get(inboxId, threadId);
+
+      expect(data.data).toEqual(response);
+      expect(fetchMock).toHaveBeenCalledWith(
+        `https://api.resend.com/inboxes/${inboxId}/threads/${threadId}`,
</file context>

html?: string | null;
}

export type CreateInboxDraftOptions = CreateInboxDraftContent &

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.

P2: CreateInboxDraftOptions allows {} and replies without content, but the endpoint rejects drafts without a non-empty field. Make CreateInboxDraftContent a RequireAtLeastOne type so invalid payloads are caught at compile time.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/inboxes/drafts/interfaces/create-inbox-draft.interface.ts, line 14:

<comment>`CreateInboxDraftOptions` allows `{}` and replies without content, but the endpoint rejects drafts without a non-empty field. Make `CreateInboxDraftContent` a `RequireAtLeastOne` type so invalid payloads are caught at compile time.</comment>

<file context>
@@ -0,0 +1,31 @@
+  html?: string | null;
+}
+
+export type CreateInboxDraftOptions = CreateInboxDraftContent &
+  (
+    | {
</file context>

}>;

export interface UpdateInboxThreadResponseSuccess {
object: 'inbox';

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.

P2: The update response is typed as an inbox rather than an inbox thread. This disagrees with the other thread response types and prevents consumers from narrowing the PATCH result as an inbox_thread; use the thread object literal or reuse InboxThreadSummary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/inboxes/threads/interfaces/update-inbox-thread.interface.ts, line 16:

<comment>The update response is typed as an inbox rather than an inbox thread. This disagrees with the other thread response types and prevents consumers from narrowing the PATCH result as an `inbox_thread`; use the thread object literal or reuse `InboxThreadSummary`.</comment>

<file context>
@@ -0,0 +1,25 @@
+}>;
+
+export interface UpdateInboxThreadResponseSuccess {
+  object: 'inbox';
+  id: string;
+  subject: string | null;
</file context>
Suggested change
object: 'inbox';
object: 'inbox_thread';

import type { Response } from '../../../interfaces';
import type { InboxLabelColor } from '../../interfaces/inbox';

export interface UpdateInboxLabelOptions {

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.

P2: UpdateInboxLabelOptions permits {}, allowing typed callers to send a no-op label PATCH. Define this payload with RequireAtLeastOne so every update changes name or color, matching the other inbox PATCH contracts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/inboxes/labels/interfaces/update-inbox-label.interface.ts, line 4:

<comment>`UpdateInboxLabelOptions` permits `{}`, allowing typed callers to send a no-op label PATCH. Define this payload with `RequireAtLeastOne` so every update changes `name` or `color`, matching the other inbox PATCH contracts.</comment>

<file context>
@@ -0,0 +1,15 @@
+import type { Response } from '../../../interfaces';
+import type { InboxLabelColor } from '../../interfaces/inbox';
+
+export interface UpdateInboxLabelOptions {
+  name?: string;
+  color?: InboxLabelColor;
</file context>

Comment thread src/resend.ts
readonly domains = new Domains(this);
readonly emails = new Emails(this);
readonly events = new Events(this);
readonly inboxes = new Inboxes(this);

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.

P2: Custom agent: API Key Permission Check SDK Methods

The new inboxes surface adds authenticated inbox, thread, draft, label, reply, and forward operations. Confirm production API keys have the required inbox permissions before release to prevent authorization failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/resend.ts, line 68:

<comment>The new `inboxes` surface adds authenticated inbox, thread, draft, label, reply, and forward operations. Confirm production API keys have the required inbox permissions before release to prevent authorization failures.</comment>

<file context>
@@ -64,6 +65,7 @@ export class Resend {
   readonly domains = new Domains(this);
   readonly emails = new Emails(this);
   readonly events = new Events(this);
+  readonly inboxes = new Inboxes(this);
   readonly logs = new Logs(this);
   readonly oauthGrants = new OAuthGrants(this);
</file context>


const data = await resend.inboxes.labels.list(inboxId);

expect(data.data).toEqual(response);

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: The update and remove tests only assert the response body (data.data); they never verify the HTTP method (PATCH/DELETE) or the request URL. The create and list tests in this same file assert method/URL, and the sibling drafts.spec.ts update test asserts method: 'PATCH' and the endpoint path. As written, a regression that changes update to POST/PUT or remove to GET would still pass. Add expect(fetchMock).toHaveBeenCalledWith('https://api.resend.com/inboxes/${inboxId}/labels/${labelId}', expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Later' }) })) to the update test and the DELETE equivalent to the remove test, matching the drafts spec.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/inboxes/labels/labels.spec.ts, line 41:

<comment>The update and remove tests only assert the response body (`data.data`); they never verify the HTTP method (PATCH/DELETE) or the request URL. The create and list tests in this same file assert method/URL, and the sibling drafts.spec.ts update test asserts `method: 'PATCH'` and the endpoint path. As written, a regression that changes `update` to POST/PUT or `remove` to GET would still pass. Add `expect(fetchMock).toHaveBeenCalledWith('https://api.resend.com/inboxes/${inboxId}/labels/${labelId}', expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Later' }) }))` to the update test and the DELETE equivalent to the remove test, matching the drafts spec.</comment>

<file context>
@@ -0,0 +1,146 @@
+
+      const data = await resend.inboxes.labels.list(inboxId);
+
+      expect(data.data).toEqual(response);
+      expect(fetchMock).toHaveBeenCalledWith(
+        `https://api.resend.com/inboxes/${inboxId}/labels`,
</file context>

Comment thread CHANGELOG.md
@@ -1,3 +1,7 @@
## resend@6.27.0

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 entry labels the feature as stable 6.27.0, but the package currently publishes 6.27.0-preview-inboxes.0. Use the preview version in the heading, or add the stable heading only when the package is released as 6.27.0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At CHANGELOG.md, line 1:

<comment>This entry labels the feature as stable `6.27.0`, but the package currently publishes `6.27.0-preview-inboxes.0`. Use the preview version in the heading, or add the stable heading only when the package is released as `6.27.0`.</comment>

<file context>
@@ -1,3 +1,7 @@
+## resend@6.27.0
+
+### Add `resend.inboxes` with nested `threads`, `threads.emails`, `labels`, and `drafts`
</file context>
Suggested change
## resend@6.27.0
## resend@6.27.0-preview-inboxes.0

@github-actions github-actions Bot added the linear-synced PR has been synced to Linear label Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge linear-synced PR has been synced to Linear

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant