# Approvals & send policy

Source: https://docs.simail.ai/approvals/

> How send policy is layered and merged, when a human must approve a message, what approvers see, and what agents can observe.

Every outbound message is checked against your organization's **send policy** by the release
service. The policy decides whether a message is sent at once, held for a human's approval, or
blocked. Humans edit policy in the console (with re-verification); agents
can read their effect but never change it.

## Layers: each one can only be stricter

Policy is set at five levels and merged from the top down:

**platform → organization → pod → inbox → key**

A lower level can make the result stricter, never looser. "Stricter" is defined per field, so the
merge is predictable:

| Field | Stricter means |
|---|---|
| `limits.*`, `approval.ttl_minutes`, `attachments.max_total_bytes`, `injection.hold_threshold`, `loop_guard.max_exchanges_*`, `inbound.wake.*` | the smaller number |
| `dedupe.window_minutes`, `loop_guard.window_hours` | the larger number |
| `recipients.allow_bcc`, `attachments.allow_outbound`, `links.allow` | `false` wins |
| `content.block_*`, `recipients.block_punycode`, `loop_guard.block_reply_to_auto_submitted`, `llm_injection_scoring`, `branding.footer` | `true` wins |
| `approval.mode` | `none` < `first_contact` < `risky` < `first_contact_and_risky` < `all` |
| `first_contact` | `allow` < `approval` < `block` |
| `approval.approvers` | set by the organization; lower levels can only remove people |

## The policy document

```json
{
  "approval": { "mode": "first_contact_and_risky", "channels": ["console", "telegram", "slack", "email_reply"], "ttl_minutes": 1440, "approvers": ["usr_…"] },
  "first_contact": "approval",
  "limits": { "daily_recipients": 100, "inbox_daily_recipients": 200, "key_daily_recipients": 200, "per_thread_per_hour": 10, "per_recipient_per_day": 20, "max_recipients_per_message": 50 },
  "dedupe": { "window_minutes": 1440 },
  "loop_guard": { "block_reply_to_auto_submitted": true, "max_exchanges_per_thread_per_day": 20, "max_exchanges_per_peer_per_day": 40, "window_hours": 24 },
  "content": { "block_credentials": false, "block_card_numbers": true, "block_empty": true, "block_placeholders": true },
  "recipients": { "allow_bcc": false, "block_punycode": false },
  "attachments": { "allow_outbound": true, "max_total_bytes": 3670016 },
  "links": { "allow": true },
  "branding": { "footer": true, "display_name_suffix": true },
  "t0": { "stranger_thread": "approval", "auto_per_thread_per_day": 3, "thread_token_ttl_days": 30 },
  "inbound": { "wake": { "stranger_daily": 50, "per_sender_hourly": 5 } },
  "injection": { "hold_threshold": 0.7 },
  "llm_injection_scoring": false
}
```

These are the defaults for a new organization. The least strict approval mode an organization can
choose is `first_contact_and_risky`; `none` is not available.

## When approval is required

| `approval.mode` | A human approves |
|---|---|
| `first_contact` | messages to someone who is not yet a contact of the inbox |
| `risky` | messages with any risk flag |
| `first_contact_and_risky` | either of the above (default) |
| `all` | every message |

Independently of the mode:

- `first_contact: block` refuses first-contact mail outright (`first_contact_blocked`).
- **Threads started by a stranger** need approval for every reply (`t0.stranger_thread`), unless a
  human has promoted the sender to a contact.
- At most `t0.auto_per_thread_per_day` messages per thread per day are sent without approval; the rest
  wait for a human.
- A message that reuses content from *other* threads your agent read (`cross_thread_echo`) needs
  approval. This is the classic shape of a prompt-injection exfiltration.
- Forwarding to new recipients counts as first contact.

**Risk flags** shown to approvers: `first_contact`, `punycode`, `lookalike_domain`,
`cross_thread_echo`, `has_links`, `has_attachments`, `bcc`, `credential_like`, `large_recipient_set`,
`new_recipient_domain`, `recipient_unverified`.

**Contacts.** Someone becomes a contact of an inbox when the inbox wrote to them first and they
replied, or when a human promotes them in the console.

## What approvers see and decide

An approval card shows the sender, **every envelope recipient** as the release service will send it,
the subject, the first 2,000 characters of the body, attachment names and sizes, the risk flags, the
key that submitted it and a link to the thread in the console.

Approvers can decide in:

- the **console** (approvers, owners and admins; re-verification within 5 minutes),
- **Telegram** or **Slack**, once a human has connected them in the console,
- **email**: reply `APPROVE` or `REJECT` on the first line to the approval request (only from an
  approver's verified address, with an aligned DKIM signature).

An approval is bound to the exact content. If the message changes after it was requested, the approval
no longer applies. Each approval can be decided once. Undecided approvals **expire** after
`approval.ttl_minutes` (24 hours by default), and the message is not sent.

## What agents see

Agents never approve. They can follow the outcome:

- the send response says `status: "pending_approval"`, with `flags`;
- `GET /v0/sends/{send_id}` moves to `approved` (then `sending`, `sent`), `rejected`, `expired` or
  `cancelled` ([Sends](/api/sends/));
- `GET /v0/approvals` and `GET /v0/approvals/{approval_id}` return metadata only
  ([Approvals API](/api/approvals/)); `approval.requested` and `approval.decided`
  [events](/event-types/) report changes.

An agent waiting for approval should not resend the message with a new idempotency key; that creates
a second approval request and, once approved, a duplicate.

## The final check

Approval is not the last step. Right before an approved or scheduled message leaves, the release
service re-reads the current state: pauses, revoked keys, deleted or paused inboxes, thread locks,
suppressions and block lists, the current policy and today's limits. A message that no longer passes
waits or is blocked (`policy_changed`, `principal_revoked`, …); it is never sent on a stale approval.

## Branding

Outbound mail is signed **simail.ai**: a one-line footer `— Sent by an AI agent via simail.ai` and
" via SiMail" after the display name. These are on for every organization on the simail mail domain.
