feat: inboxes endpoints - #1088
Conversation
|
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. |
commit: |
There was a problem hiding this comment.
Ultrareview completed in 10m 19s
8 issues found across 47 files
Confidence score: 2/5
src/inboxes/inboxes.spec.tscontains 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.tsadds 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.tstypes PATCH results as an inbox, whilesrc/inboxes/labels/interfaces/update-inbox-label.interface.tsandsrc/inboxes/drafts/interfaces/create-inbox-draft.interface.tsallow invalid empty updates or drafts; correct the response type and require at least one valid field.src/inboxes/threads/threads.spec.tsandsrc/inboxes/labels/labels.spec.tsdo 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'); |
There was a problem hiding this comment.
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>
| 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); |
There was a problem hiding this comment.
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 & |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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>
| object: 'inbox'; | |
| object: 'inbox_thread'; |
| import type { Response } from '../../../interfaces'; | ||
| import type { InboxLabelColor } from '../../interfaces/inbox'; | ||
|
|
||
| export interface UpdateInboxLabelOptions { |
There was a problem hiding this comment.
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>
| readonly domains = new Domains(this); | ||
| readonly emails = new Emails(this); | ||
| readonly events = new Events(this); | ||
| readonly inboxes = new Inboxes(this); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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>
| @@ -1,3 +1,7 @@ | |||
| ## resend@6.27.0 | |||
There was a problem hiding this comment.
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>
| ## resend@6.27.0 | |
| ## resend@6.27.0-preview-inboxes.0 |
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.inboxeswith nested resources, and the package version is bumped to6.27.0-preview-inboxes.0as a preview release.New Features
resend.inboxeswith create, list, get, update, and remove methods.resend.inboxes.threadsfor listing, fetching, updating, and removing threads.resend.inboxes.threads.emailsfor getting, replying to, and forwarding thread emails.resend.inboxes.labelsandresend.inboxes.draftswith full CRUD, plus sending drafts.6.27.0.Written for commit ad5189b. Summary will update on new commits.