Skip to content

feat(inboxes): match the inbox commands to the stable Inboxes API - #420

Merged
felipefreitag merged 14 commits into
mainfrom
feat/inboxes-stable-api
Oct 16, 2026
Merged

felipefreitag merged 14 commits into
mainfrom
feat/inboxes-stable-api

Conversation

@felipefreitag

@felipefreitag felipefreitag commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What broke in 2.25.0

The Inboxes API is now stable. Three commands of the released 2.25.0 fail against the live API.

  • inboxes agent get reads enabled_actions. The API now returns actions, so the interactive output crashes on .join.
  • inboxes agent update --enabled-actions sends enabled_actions. The API ignores it and returns 422.
  • inboxes threads update --add-label-ids and --remove-label-ids send fields that the API strips. The request returns 422.

This PR moves the CLI to resend@6.34.1 and matches every inbox command to the merged OpenAPI spec (resend-openapi#137).

Changes per command group

agent

  • agent get reads and prints actions.
  • agent update takes --actions and removes duplicate values. The values are archive and move_to_trash, not archive_thread and delete_thread. An old value fails with invalid_actions and names the new value.
  • --tone accepts only the four presets from INBOX_AGENT_TONES, or "" to clear it. Other text fails with invalid_tone (MES-2747).
  • The error code invalid_enabled_actions is now invalid_actions. The sibling codes (invalid_tone, invalid_limit, invalid_header) take the name of the flag.
  • main had no SDK bridge for actions. The bridge was only in the closed feat(inboxes): rename the agent enabled-actions flag and field to actions #417, so MES-2740 needs no code removal here.

threads labels (new)

  • threads labels add <inbox> <thread> --labels <ids...> calls POST /inboxes/{inbox_id}/threads/{thread_id}/labels.
  • threads labels set <inbox> <thread> --labels <ids...> calls PUT on the same path. --labels "" removes every label.
  • threads labels remove <inbox> <thread> <label> calls DELETE .../labels/{label_id}.
  • Each command calls one SDK method (resend.inboxes.threads.labels.add|set|remove) and outputs the label list of the thread.

threads

  • threads update takes only --read, --unread and --folder.
  • threads search drops highlights from help and from the table. The table shows matched_email_id, which is now the email ID that threads emails list returns.

Inbox ID or email address

  • Every command that names an inbox takes its ID or its email address. A value with @ goes to the SDK as emailAddress. Any other value goes as inboxId, like the MCP servers.
  • The help of every argument and --inbox-id flag says "Inbox UUID or email address".
  • inboxes delete prints the API result, so an address argument still prints the inbox UUID.

inboxes, drafts, attachments, keys

  • inboxes get and list help show created_at and updated_at. get prints both in interactive mode.
  • inboxes create help says the address cannot contain a +.
  • drafts update help shows the new output {"object":"inbox_draft","id":"<uuid>"}.
  • drafts create and drafts send help say a reply draft without a subject is sent with the thread subject and a Re: prefix.
  • threads emails reply and forward help state only the 25 MB attachment rule. The path rule now matches the API.
  • threads emails reply, threads emails forward, drafts create and drafts send accept a sending-access key. The other inbox commands still need full access. The interactive pickers list inboxes, threads or drafts, so a sending-access key must pass every ID.

Docs

  • README and skills/resend-cli/references/inboxes.md describe all of the above. The SKILL.md command table lists threads labels.

Compatibility

  • --enabled-actions and --enabled_actions stay as hidden aliases of --actions.
  • --add-label-ids, --remove-label-ids and their snake_case spellings stay as hidden flags. They fail with invalid_options and a message that names threads labels add, remove and set. The CLI does not drop them silently.
  • invalid_enabled_actions is now invalid_actions.
  • drafts update JSON output is now only object and id, because the API changed.

Checks

  • pnpm install --frozen-lockfile passes. resend is in minimumReleaseAgeExclude in pnpm-workspace.yaml, so 6.34.1 installs.
  • pnpm lint, pnpm typecheck, CI=true pnpm vitest run (1479 passed, 1 skipped) and pnpm build pass.
  • New tests assert the whole SDK payload for each new and changed command. tests/commands/inboxes/email-address.test.ts runs every inbox command with an address. tests/commands/inboxes/sending-access.test.ts checks the key access.
  • pnpm test:e2e was not run.

E2E

Built CLI, .env key, fixture inbox 43cd94f5-63f2-4abe-93d4-58b9c26f353e.

  • --help for all 36 inbox commands exits 0.
  • agent get by ID and by address returns actions. Interactive output prints Actions: (none). agent update was not called on the API, so the fixture agent did not change. Its local invalid_tone and invalid_actions errors were checked.
  • threads labels add, set, remove and set --labels "" on thread 772da839 returned the expected lists. An unknown label returned 404 Label not found.. The thread labels are [] again, as before. The labels spec-137 a and spec-137 b did not change.
  • threads update --add-label-ids returned invalid_options before any API call.
  • inboxes get, labels list, threads list, threads search, threads get, threads emails list, threads emails get, drafts list and drafts get passed by ID, by address and by an uppercase address. matched_email_id equals the id from threads emails list.
  • One draft was created, updated (output {object, id}), read and deleted.
  • A throwaway inbox was created with --forwarding and deleted by its address. The delete output held the inbox UUID.

Release

Release 2.26.0. package.json is 2.26.0 and the resend-cli skill is 2.23.0.

agent get reads actions, which replaced enabled_actions. agent update
sends actions through the new --actions flag. --enabled-actions and
--enabled_actions stay as hidden aliases. --tone accepts only the four
presets and fails with invalid_tone for other text.
The API no longer returns highlights. The search table shows the
matched email ID in place of the match snippet. matched_email_id is
now the email ID that threads emails list returns.
Each command calls one thread label endpoint: POST, PUT and DELETE
/inboxes/{inbox_id}/threads/{thread_id}/labels. The API now strips
add_label_ids and remove_label_ids from update thread, so threads update
drops --add-label-ids and --remove-label-ids. The old flags stay hidden
and fail with invalid_options and the names of the new commands.
Every route under /inboxes/{inbox_id} now takes the inbox ID or its
email address. The commands pass a value with @ to the SDK as
emailAddress and any other value as inboxId, like the MCP servers.
- drafts update outputs only the object and the draft ID.
- inboxes get prints created_at and updated_at; the help shows both
  fields on get and list.
- A reply draft without a subject is sent with the thread subject.
- Replies and forwards state only the 25 MB attachment rule.
- create help says the address cannot contain a +.
- reply, forward, drafts create and drafts send accept a
  sending-access key.
Document the threads labels commands, the --actions flag, the tone
presets, the inbox email address, the sending-access commands, and the
new draft and search output.

@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.

2 issues found across 61 files

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="tests/commands/inboxes/threads/search.test.ts">

<violation number="1" location="tests/commands/inboxes/threads/search.test.ts:251">
P3: This assertion can't fail as written: the mock no longer returns `highlights`, and JSON output passes the SDK result through unchanged. It doesn't guard against highlights reappearing. Either drop the assertion, or keep `highlights` in the mock and assert the CLI strips it, which needs the command to drop that field explicitly.</violation>
</file>

<file name="src/commands/inboxes/drafts/send.ts">

<violation number="1" location="src/commands/inboxes/drafts/send.ts:68">
P2: Interactive `drafts send` with a sending-access credential still fails when either ID is omitted: the pickers fetch before this permission is applied and require `full_access`. Use a sending-access-compatible picker path or require explicit IDs for these credentials.</violation>
</file>

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

View guided diff | Turn on auto-fix | Re-trigger cubic

await runCreate(
{
loading: 'Sending draft...',
permission: 'sending_access',

@cubic-dev-ai cubic-dev-ai Bot Oct 16, 2026 •

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: Interactive drafts send with a sending-access credential still fails when either ID is omitted: the pickers fetch before this permission is applied and require full_access. Use a sending-access-compatible picker path or require explicit IDs for these credentials.

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 src/commands/inboxes/drafts/send.ts, line 68:

<comment>Interactive `drafts send` with a sending-access credential still fails when either ID is omitted: the pickers fetch before this permission is applied and require `full_access`. Use a sending-access-compatible picker path or require explicit IDs for these credentials.</comment>

<file context>
@@ -63,9 +65,10 @@ The output holds the ID of the sent email. Use it with "resend emails get".`,
     await runCreate(
       {
         loading: 'Sending draft...',
+        permission: 'sending_access',
         sdkCall: (resend) =>
           resend.inboxes.drafts.send(
</file context>
Fix with cubic

Comment thread src/commands/inboxes/delete.ts Resolved
const parsed = JSON.parse(spies.logSpy.mock.calls[0][0] as string);
expect(parsed.data[0].matched_email_id).toBe(EMAIL_ID);
expect(parsed.data[0].highlights.subject).toEqual(['**Billing** question']);
expect(parsed.data[0]).not.toHaveProperty('highlights');

@cubic-dev-ai cubic-dev-ai Bot Oct 16, 2026 •

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 assertion can't fail as written: the mock no longer returns highlights, and JSON output passes the SDK result through unchanged. It doesn't guard against highlights reappearing. Either drop the assertion, or keep highlights in the mock and assert the CLI strips it, which needs the command to drop that field explicitly.

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 tests/commands/inboxes/threads/search.test.ts, line 251:

<comment>This assertion can't fail as written: the mock no longer returns `highlights`, and JSON output passes the SDK result through unchanged. It doesn't guard against highlights reappearing. Either drop the assertion, or keep `highlights` in the mock and assert the CLI strips it, which needs the command to drop that field explicitly.</comment>

<file context>
@@ -249,7 +248,7 @@ describe('inboxes threads search command', () => {
     const parsed = JSON.parse(spies.logSpy.mock.calls[0][0] as string);
     expect(parsed.data[0].matched_email_id).toBe(EMAIL_ID);
-    expect(parsed.data[0].highlights.subject).toEqual(['**Billing** question']);
+    expect(parsed.data[0]).not.toHaveProperty('highlights');
   });
 
</file context>
Fix with cubic

Comment thread src/commands/inboxes/threads/update.ts Resolved
Comment thread src/commands/inboxes/agent/update.ts Resolved
Comment thread src/commands/inboxes/agent/update.ts Resolved
Comment thread skills/resend-cli/references/inboxes.md Resolved
- inboxes delete prints the API result, so an address prints the UUID.
- threads labels add rejects an empty --labels with missing_labels.
- agent update removes duplicate actions and checks renames with
  Object.hasOwn.
- The threads update error names full commands.
- The labels help lists missing_id and invalid_options.

@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.

0 issues found across 13 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

Remove the hidden --enabled-actions and --enabled_actions aliases of agent
update --actions. Remove the custom error for the removed threads update
label flags, so commander rejects them as unknown options. Remove the
version-history paragraphs from the README and the skill reference.
The insufficient permissions message now lists the four inbox commands that
a sending-access key can run. The inbox help, README and skill reference say
that a sending-access key must pass every ID, because the pickers need full
access.

@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.

0 issues found across 10 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

An old action value such as archive_thread now gets the same
invalid_actions error as any other invalid value.

@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.

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

@dielduarte dielduarte 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.

I found no bugs, just two nits, already approving anyway!

  1. --labels doesn't split commas (threads/labels/utils.ts, requireLabelIds). --actions in the same PR does. --labels a,b gets a 422 from the API.
  2. The labels set missing-flag error doesn't mention that --labels "" is valid.

… set

"threads labels add" and "threads labels set" now split --labels values on
commas, like "agent update --actions". Before, "--labels a,b" sent one ID and
the API returned a 422. The missing-flag error of "set" now names
--labels "" to remove every label.

@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.

0 issues found across 6 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

@felipefreitag
felipefreitag merged commit d4367e1 into main Oct 16, 2026
16 checks passed
@felipefreitag
felipefreitag deleted the feat/inboxes-stable-api branch October 16, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants