# Upgrading to samva 0.35 and the CLI 0.30
URL: https://samva.dev/changelog/upgrade-to-samva-0-35
> samva 0.31 to 0.35 reject undeclared fields, cap resource names, read threads and drafts across mailboxes, and route unknown addresses. Upgrade the CLI to 0.30.
- Published: 2026-11-05
- Tags: sdk, cli, api

Upgrade `samva` to 0.35.0 and `@samva/cli` to 0.30.0. Start with the request bodies: the API now
rejects fields an operation does not declare, so a request that spreads a whole response object or
sends `events` to a webhook update fails until you correct it. Then handle the name limits and the
unknown-address setting, widen channel switches, add the `ServiceUnavailableError` branch if you
create templates, and read mailboxes across the organization where you used to walk them one at a
time. Each package's full notes are on its
[GitHub release](https://github.com/SamvaHQ/samva-typescript/releases).

**What you need to do**

1. **Undeclared fields are rejected (0.35, CLI 0.30).** Remove properties that an operation does not
declare from request bodies, including nested objects. For a webhook update, replace `events` with
`eventTypes`, and pass the mutation's input fields instead of spreading a whole response object into
a request. The Effect SDK rejects undeclared body fields before it sends; a Promise client call
sends the request and receives the API's error. The API answers
`MalformedRequestError` (400) when only top-level fields are undeclared and `ValidationError` (422)
for nested fields or other faults; `fields` names each rejected path and the declared alternatives.
`samva` commands that take `--data` apply the same rule to JSON input, fail before sending, and name
each rejected field. Template data, metadata, and webhook test data stay free-form. See
[`MalformedRequestError`](/docs/developers/error-reference#error-types).

2. **Optional filters and headers are absent, not null (0.35).** List filters such as `cursor`,
`channel`, and `direction`, and headers such as `idempotency-key`, no longer accept `null` in their
types. Omit the field instead. Replace imports of the `Cursor`, `VersionCursor`, and
`PublicationDomainCursor` aliases with `string`.

3. **Types keep every channel (0.35).** Request and response types retain every channel's values and
content, so template lists, mailboxes, messages, subscriptions, and other shared resources decode
what the API returns. Unions widen: an exhaustive `switch` over a channel must handle every member.

4. **Names have limits (0.32).** Create and update requests reject an organization, mailbox, endpoint
display, sender, contact, contact group, campaign, newsletter, topic, or OAuth client name, or a
mailbox label display name, that exceeds its limit. A rejected request returns `ValidationError`
(422) with `fields` naming the input. Existing longer names stay readable, so shorten one the next
time you change it. The CLI applies the same limits. The
[name limits](/docs/developers/rest-api#name-limits) table lists each maximum; see also
[`ValidationError`](/docs/developers/error-reference#validationerror-422).

5. **Unknown addresses reach a mailbox (0.33).** Catch-all receiving no longer needs a webhook
endpoint or a forwarding rule to have somewhere to deliver. A domain carries
`unknownRecipientMailboxId`: name an active mailbox on the same domain and mail that matches no
mailbox, forwarding rule, or catch-all webhook reaches that mailbox instead of being dropped. Set it
to `null` to drop unknown addresses again. They never bounce and send no delivery-status
notification. A catch-all webhook takes precedence, so the mailbox receives unknown addresses only
while the domain has no catch-all webhook. See
[receive unknown addresses](/docs/mailboxes/manage-mailbox#receive-unknown-addresses).

```ts title="route-unknown-addresses.ts"
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

await samva.email.updateCatchAllConfig({
  domainId: "dom_abc123",
  unknownRecipientMailboxId: "mbx_7q2xk9mvt4znw8rh",
});

// Drop unknown addresses again.
await samva.email.updateCatchAllConfig({ domainId: "dom_abc123", unknownRecipientMailboxId: null });
```

The CLI follows. `samva email receiving enable --catch-all` enables receiving with no `--endpoint` and
no forwarding rule, and `samva email receiving status` reports where mail lands. Neither command sets
this mailbox, so send that setting through `samva api`:

```bash
samva api email/catch-all/config -X PATCH \
  -F domainId=dom_abc123 \
  -F unknownRecipientMailboxId=mbx_7q2xk9mvt4znw8rh
```

6. **Work across mailboxes (0.32).** `mailboxes.listOrganizationThreads` lists threads in the
mailboxes your credential can read, and it takes `conversation` to find the threads one conversation
has across this organization. `mailboxes.listOrganizationDrafts` lists your own drafts in the
mailboxes where you may write drafts, and `mailboxes.getNeedsYou` collects held sends you may approve
and quarantined messages you may release, with counts over every page. Each row carries its mailbox's
`id`, `address`, and `kind`, so one list replaces a walk over every mailbox you support. Effect
callers import the same operations from `samva/effect/mailboxes`. The CLI adds
`samva mailboxes all-threads`, `samva mailboxes needs-you`, and `samva mailboxes thread-update`. See
[work across mailboxes](/docs/mailboxes/read-and-organize#work-across-mailboxes) and
[find your drafts across mailboxes](/docs/mailboxes/compose-and-send#find-your-drafts-across-mailboxes).

```ts title="triage-across-mailboxes.ts"
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const id = "mbx_7q2xk9mvt4znw8rh";
const threadId = "thread_7q2xk9mvt4znw8rh";

const page = await samva.mailboxes.listOrganizationThreads({ kind: "shared", state: "active" });
for (const thread of page.items) console.log(thread.mailbox.address, thread.subject);

await samva.mailboxes.updateThread({ id, threadId, done: true });
```

`mailboxes.getNeedsYou` lists only decisions the caller may take. An API key cannot approve a send or
release a quarantined message, so a call made with an API key returns an empty list. Read it as a
signed-in user, for example with the CLI:

```bash
samva mailboxes needs-you --limit 20 --json
```

A thread now takes `done` for your own view and `closed` for everyone in a shared mailbox. A new
live message clears Done for every reader and reopens a closed thread, and personal mailboxes refuse
Close and archive instead. Neither state emits a webhook or event-stream event, so read the thread or
refresh the list to observe it. See
[Done and Close](/docs/mailboxes/read-and-organize#done-and-close).

7. **Templates can answer 503 (0.32).** Template operations report a temporary service failure as
`ServiceUnavailableError` (503), with `retryAfterSeconds` matching the `Retry-After` header. Add the
tag wherever you branch on `_tag`, wait the advertised time, and reuse the same idempotency key on a
write. See
[`ServiceUnavailableError`](/docs/developers/error-reference#serviceunavailableerror-503).

```ts title="create-template-with-retry.ts"
import { createClient, ServiceUnavailableError } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

try {
  await samva.templates.create({ name: "Receipt" });
} catch (error) {
  if (error instanceof ServiceUnavailableError) {
    console.error(`Templates are unavailable for ${error.retryAfterSeconds} seconds`);
  } else {
    throw error;
  }
}
```

8. **Conversations filter by mailbox (0.31).** `conversations.list` takes `mailboxId` and returns only
the conversations that reached that mailbox, beside the contact, channel, status, and date filters
you already use. See [list conversations](/docs/api-reference/conversations/list).

```ts title="conversations-in-a-mailbox.ts"
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

const page = await samva.conversations.list({ mailboxId: "mbx_7q2xk9mvt4znw8rh", limit: 20 });
for (const conversation of page.items) console.log(conversation.id, conversation.status);
```

**Also new**

- **Skipped webhook replays (0.34).** `webhooks.getLogs` includes a terminal queue replay as an
  attempt with `kind: "replay"`, `classification: "skipped"`, and a `queueMessageId`. A replay
  record shows processing without another HTTP request and does not raise the attempt count, so
  filter on `kind` if you count attempts. See [webhooks](/docs/platform/webhooks).
- **Mailbox Retry (0.33).** Retry help and the SDK docs now state the boundary: an eligible queued
mailbox action can be retried while cleanup from an ended send attempt waits. Retry keeps the
existing delays and the original deadline. A send attempt that is still running, or whose status
cannot be confirmed, stays ineligible. See
[Cancel or retry](/docs/mailboxes/approvals#cancel-or-retry).
- **Clear a catch-all webhook (0.35, CLI 0.30).** Pass `endpointId: null` to
  `email.updateCatchAllConfig` to remove the webhook while catch-all stays enabled. Unknown addresses
  then follow the saved `unknownRecipientMailboxId`, or are dropped without a bounce when none is set.
  Set `unknownRecipientMailboxId` in the same update to choose a mailbox, or pass `null` to drop
  unknown addresses. Omitted settings stay unchanged.
  `samva email receiving update <domainId>` sets or clears both in one request. See
  [receive unknown addresses](/docs/mailboxes/manage-mailbox#receive-unknown-addresses).
- **Latest webhook attempt (0.35).** The stats for a webhook endpoint include `latestAttempt`, the
  newest non-skipped delivery attempt with its `errorCode` and `completedAt`. A `cloudflare_challenge`
  code means your Cloudflare zone challenged the request before it reached the endpoint. See
  [Cloudflare challenges](/docs/platform/webhooks#cloudflare-challenges).
- **Templates on every channel (CLI 0.30).** `samva templates list` and `samva templates get` read
  templates on every channel, so an organization with templates on more than one channel no longer
  fails while decoding.
- **CLI 0.29.2.** `samva mailboxes read` accepts a message whose campaign and sender fields are null,
including with `--json`.

## Links

- [TypeScript SDK](/docs/developers/typescript-sdk)
- [Name limits](/docs/developers/rest-api#name-limits)
- [Read and organize conversations](/docs/mailboxes/read-and-organize)
- [Receive unknown addresses](/docs/mailboxes/manage-mailbox#receive-unknown-addresses)
- [Error reference](/docs/developers/error-reference)
- [Webhooks](/docs/platform/webhooks)
- [samva 0.35.0 release notes](https://github.com/SamvaHQ/samva-typescript/releases/tag/samva%400.35.0)
- [@samva/cli 0.30.0 downloads](https://github.com/SamvaHQ/samva-cli/releases/tag/v0.30.0)

## Related

- [Upgrading to samva 0.30 and the CLI 0.28](/changelog/upgrade-to-samva-0-30)
- [All changelog entries](/changelog)
