Documentation

Policy reference

A MailPolicy is a declarative document attached to a mailbox. It is evaluated server-side on every inbound message, before your agent code runs. It enforces sender allowlists, DKIM/SPF verification, content guards, rate limits, and per-sender capability scoping.

A policy is set with client.setPolicy(mailboxId, policy) (or PUT /v1/mailboxes/{id}/policy) and evaluated automatically by the platform. The exact evaluation order is documented in Evaluation order.

Schema overview

interface MailPolicy {
  defaultAction: "bounce" | "drop";
  senders: SenderRule[];
  contentGuards?: ContentGuard[];
  auditLog: AuditConfig;
}

interface SenderRule {
  match: SenderMatch;
  capabilities: string[];
  rateLimit?: RateLimit;
  tokenBudget?: TokenBudget;
}

The wire format uses camelCase. The Python SDK maps these to snake_case field names internally.

MailPolicy

Field Type Required Default Constraints Description
defaultAction "bounce" | "drop" yes one of two literals Action taken for senders that match no rule. See DefaultAction.
senders SenderRule[] yes [] top-to-bottom evaluation; first match wins Ordered list of sender rules.
contentGuards ContentGuard[] no [] applied after a sender rule matches Regex-based body filters.
auditLog AuditConfig yes retentionDays ≥ 1 Audit-log configuration.

SenderRule

A SenderRule matches a sender (or a class of senders) and grants them a list of capabilities, optionally bounded by rate and token limits.

Field Type Required Default Constraints Description
match SenderMatch yes at least one of address/domain Who this rule applies to.
capabilities string[] yes [] arbitrary strings; see Capability What tools/actions the agent may invoke for this sender.
rateLimit RateLimit no unbounded independent per-hour/per-day Cap on inbound messages from this sender.
tokenBudget TokenBudget no unbounded per-thread and per-day are independent Cap on LLM tokens spent on this sender.

Rules are evaluated in order; the first rule whose match matches the inbound sender wins. Subsequent rules are not evaluated.

SenderMatch

Field Type Required Default Constraints Description
address string no full email address; case-insensitive Exact-match an address.
domain string no bare domain; case-insensitive Match any address at this domain.
requireDkim boolean no false Reject if DKIM did not pass.
requireSpf boolean no false Reject if SPF did not pass.

If both address and domain are set, the sender must match the address (the domain is then redundant). If neither is set, the rule matches every sender — useful as a catch-all at the bottom of the list.

Matching is case-insensitive in both directions.

Capability

capabilities is an array of arbitrary strings. The platform does not enforce a fixed enum; the strings are passed verbatim to your agent runtime, which decides what they mean. The reference integration uses these four:

Value Meaning in the reference integration
read_calendar Agent may inspect the user's calendar.
propose_meeting Agent may suggest a meeting time.
confirm_meeting Agent may book a meeting.
ingest_conflict_notice Agent may process a conflict-detection bot's notification.

Other capability strings work fine; the platform only warns if it has not seen them before.

RateLimit

Field Type Required Default Constraints Description
perHour integer no unbounded ≥ 1 if set Inbound messages from this sender per UTC hour.
perDay integer no unbounded ≥ 1 if set Inbound messages from this sender per UTC day.

Both limits are enforced independently if both are set. Windows are tumbling (UTC hour boundary, UTC day boundary).

When a limit is exceeded, the message is rejected with audit outcome rate_limited.

TokenBudget

Field Type Required Default Constraints Description
perThread integer no unbounded ≥ 1 if set Total LLM tokens this sender may consume per email thread.
perDay integer no unbounded ≥ 1 if set Total LLM tokens this sender may consume per UTC day.

Token budgets are enforced after the agent runs; the agent reports its own token usage back to the platform. When a budget is exhausted, the message is rejected with audit outcome budget_exhausted.

ContentGuard

Field Type Required Default Constraints Description
reject string yes regex source; ECMAScript syntax with inline flags (e.g. (?i)foo) Pattern that, if it matches the body, rejects the message.
reason string yes non-empty Reason recorded on the audit entry and embedded in any bounce notification.

Content guards are applied after sender matching. They run against the message's text body. Regexes use ECMAScript syntax; for case-insensitive matching, prefix with (?i).

When a guard matches, the message is rejected with audit outcome rejected_at_content_guard.

AuditConfig

Field Type Required Default Constraints Description
retentionDays integer yes ≥ 1 How long audit entries are kept before deletion.
includeBodyHash boolean no false If true, every audit entry includes a SHA-256 of the inbound body bytes.

See Audit log for what is recorded and how it is queried.

DefaultAction

The defaultAction field on MailPolicy controls what happens to senders that do not match any rule.

Value Behaviour
bounce Send a notification email to the sender explaining the rejection. The original message is not delivered to the agent.
drop Silently discard the message. No notification sent; the audit log records the rejection.

bounce is the friendlier default for legitimate human senders; drop is appropriate for bot/spam-prone mailboxes.

Evaluation order

For every inbound message, the platform evaluates the policy in this exact order. The first failing step rejects the message; no later step runs.

  1. Sender rule matching. Walk senders top-to-bottom. The first SenderMatch that matches the inbound sender wins. If none match, reject with outcome rejected_at_policy and apply defaultAction.
  2. Verification. If the matched rule sets requireDkim or requireSpf, check the inbound message's DKIM/SPF verdicts. On failure, reject with outcome rejected_at_verification.
  3. Content guards. Walk contentGuards. If any reject regex matches the body, reject with outcome rejected_at_content_guard and the rule's reason.
  4. Rate limits. If the matched rule has a rateLimit, increment the per-hour and per-day counters for the sender. If either exceeds the limit, reject with outcome rate_limited.
  5. Token budget gate. If the matched rule has a tokenBudget, check the already-consumed counters for this thread and this sender's UTC day. If the latest already-consumed total exceeds the budget, reject with outcome budget_exhausted. (Token budgets are enforced retrospectively: the gate prevents the next message after the budget is exhausted, not the one that exhausted it.)
  6. Capability scoping. The matched rule's capabilities are attached to the inbound event and passed to the agent runtime.

Bounces are sent only when defaultAction == "bounce" AND the rejection happens. Verification and content-guard rejections are accompanied by a bounce notification with a specific reason.

Examples

Single-user scheduling agent

The reference integration's policy. One named boss, the boss's domain as a backup, no one else.

const policy: MailPolicy = {
  defaultAction: "bounce",
  senders: [
    {
      match: { address: "boss@acme.com" },
      capabilities: ["read_calendar", "propose_meeting", "confirm_meeting"],
      rateLimit: { perHour: 30 },
      tokenBudget: { perThread: 8000, perDay: 100_000 },
    },
    {
      match: { domain: "acme.com", requireDkim: true },
      capabilities: ["read_calendar"],
      rateLimit: { perHour: 10 },
    },
  ],
  contentGuards: [
    { reject: "(?i)wire transfer", reason: "phishing-likely keyword" },
  ],
  auditLog: { retentionDays: 30, includeBodyHash: true },
};

Customer-support triage with three sender tiers

A support agent with VIP, paying-customer, and free-tier sender bands.

const policy: MailPolicy = {
  defaultAction: "drop",
  senders: [
    {
      match: { domain: "vip-customer.com", requireDkim: true },
      capabilities: ["read_account", "create_ticket", "escalate_immediate"],
      rateLimit: { perHour: 100 },
    },
    {
      match: { domain: "paying-customer.com", requireDkim: true },
      capabilities: ["read_account", "create_ticket"],
      rateLimit: { perHour: 30 },
      tokenBudget: { perDay: 200_000 },
    },
    {
      match: {},
      capabilities: ["create_ticket"],
      rateLimit: { perHour: 5 },
      tokenBudget: { perThread: 2000 },
    },
  ],
  auditLog: { retentionDays: 90, includeBodyHash: true },
};

Internal-tool agent restricted to one corporate domain

A dev-ops bot that only ever talks to one company's mailservers.

const policy: MailPolicy = {
  defaultAction: "drop",
  senders: [
    {
      match: { domain: "acme-corp.com", requireDkim: true, requireSpf: true },
      capabilities: ["deploy", "rollback", "read_status"],
      rateLimit: { perHour: 60 },
    },
  ],
  contentGuards: [
    { reject: "(?i)\\b(prod|production)\\b.+rollback", reason: "production rollback requires human approval" },
  ],
  auditLog: { retentionDays: 365, includeBodyHash: true },
};

Errors

PUT /v1/mailboxes/{id}/policy returns 400 Bad Request with {"errors": [...]} on validation failure. Each entry is a human-readable description.

Error string (example) Cause
auditLog.retentionDays must be >= 1 retentionDays was zero or negative.
senders[2].rateLimit.perHour must be >= 1 Numeric limit was zero or negative.
contentGuards[0].reject is not a valid regex Pattern source failed the regex parser.
senders[0].capabilities[1] is empty Capability string was empty.

The Python and TypeScript SDKs surface these as ValidationError, exposing the array as .field_errors / .fieldErrors.

Other status codes:

Status Cause
401 Missing or invalid API key.
403 API key does not own this mailbox.
404 Mailbox does not exist.
429 Rate-limited at the API layer.
5xx Backend or upstream failure.