---
title: "Replace a mailbox's mail policy"
method: PUT
path: "/v1/mailboxes/{mailbox_id}/policy"
tags: ["policy"]
---

# Replace a mailbox's mail policy

`PUT /v1/mailboxes/{mailbox_id}/policy`

## Request

- Base URL: `https://mbag.ai/api`
- URL: `https://mbag.ai/api/v1/mailboxes/{mailbox_id}/policy`
- Auth: HTTP bearer

Validated before write: hard errors (regex won't compile, a
`contentGuards[].reject` over 1024 chars, `auditLog.retentionDays` <
1, a `senders[].match` with neither `address` nor `domain`, an
`address`/`domain` of the wrong shape) reject the whole PUT with 400.
Unknown `capabilities` values are warnings only — they pass through
and are echoed back in `warnings`, not rejected. On success the
stored `version` is incremented by 1.

## Path parameters

- `mailbox_id` integer, required

## Request body

- MailPolicy: camelCase on the wire. On PUT, `senders`/`contentGuards`/`blocklist` default to empty and `mode` defaults to `live` when omitted; the GET/PUT response always includes all of them (`blocklist` omitted when empty).
  - `auditLog` AuditConfig, required
    - `includeBodyHash` boolean, required
    - `retentionDays` integer, required
  - `blocklist` string[]: Recipient addresses/bare domains this mailbox may never send to. Omitted when empty.
  - `contentGuards` ContentGuard[], required
    - `reason` string, required
    - `reject` string, required: Regex source (Rust `regex` crate flavour); compiled at PUT time.
  - `defaultAction` 'bounce' | 'drop', required
  - `mode` 'live' | 'supervised' | 'shadow', required
  - `senders` SenderRule[], required
    - `allowedHosts` string[]
    - `capabilities` string[], required: Opaque capability strings; unknown values warn but don't reject at PUT time.
    - `match` SenderMatch, required
      - `address` string
      - `domain` string
      - `requireDkim` boolean, required
      - `requireSpf` boolean, required
    - `passive` boolean, required: When true, matching mail is accepted and audited but never invokes the agent.
    - `rateLimit` RateLimit
      - `perDay` integer
      - `perHour` integer
    - `tokenBudget` TokenBudget
      - `perDay` integer
      - `perThread` integer

## Response `200`

The new stored policy, with the bumped `version` and any non-fatal `warnings`.

- PolicyResponse: Field names are snake_case here (this wrapper predates the camelCase `MailPolicy` convention); only `policy`'s own fields are camelCase.
  - `mailbox_id` integer, required
  - `policy` MailPolicy, required: camelCase on the wire. On PUT, `senders`/`contentGuards`/`blocklist` default to empty and `mode` defaults to `live` when omitted; the GET/PUT response always includes all of them (`blocklist` omitted when empty).
    - `auditLog` AuditConfig, required
      - `includeBodyHash` boolean, required
      - `retentionDays` integer, required
    - `blocklist` string[]: Recipient addresses/bare domains this mailbox may never send to. Omitted when empty.
    - `contentGuards` ContentGuard[], required
      - `reason` string, required
      - `reject` string, required: Regex source (Rust `regex` crate flavour); compiled at PUT time.
    - `defaultAction` 'bounce' | 'drop', required
    - `mode` 'live' | 'supervised' | 'shadow', required
    - `senders` SenderRule[], required
      - `allowedHosts` string[]
      - `capabilities` string[], required: Opaque capability strings; unknown values warn but don't reject at PUT time.
      - `match` SenderMatch, required
        - `address` string
        - `domain` string
        - `requireDkim` boolean, required
        - `requireSpf` boolean, required
      - `passive` boolean, required: When true, matching mail is accepted and audited but never invokes the agent.
      - `rateLimit` RateLimit
        - `perDay` integer
        - `perHour` integer
      - `tokenBudget` TokenBudget
        - `perDay` integer
        - `perThread` integer
  - `version` integer, required
  - `warnings` string[]: Non-fatal validation warnings from PUT (e.g. unknown capability names). Omitted when empty; always empty on GET.

## Other responses

- `400`: Policy failed validation.
- `401`: Missing or invalid credential, raised by the `ApiKeyAuth` extractor (`backend/src/api_keys.rs`) before the handler runs: no `Authorization` header, a non-`Bearer` scheme, a key that doesn't hash-match any row, or a key whose status isn't `active`. A header that isn't valid UTF-8, has no scheme token, or has `Bearer` with no key value returns 400 with this same JSON shape instead of 401.
- `403`: Mailbox exists but is owned by another user. Plain-text body.
- `404`: Mailbox not found, or (rarely) the mailbox's policy row is missing at write time even though ownership checked out. Plain-text body.
- `500`: Database error, or the validated policy failed to serialise back to JSON. Plain-text body.

---

[API](https://skmtc.dev/mailbuttons/apis/mailbuttons-agent-mail-api.md) · [All operations](https://skmtc.dev/mailbuttons/apis/mailbuttons-agent-mail-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mailbuttons/apis/mailbuttons-agent-mail-api/revisions/fe2761823978?raw)
