SendRaven

Docs

Approvals

A key that drafts instead of sends, and the queue a person releases from.

An API key can be marked as requiring approval. A send from it is persisted and visible, but nothing goes out until a person releases it. This is the safety valve for giving an autonomous agent a sending credential — without it the choices are trusting the agent completely or not giving it email at all.

Marking a key

Set it when creating the key in the dashboard under API keys, or with requires_approval: true on POST /v1/api-keys. Creating keys over the API needs domains:write, the closest thing to an administrative scope: a key that can only send must not be able to mint one that can do more.

{
  "name": "support-agent",
  "scopes": ["emails:send", "emails:read", "threads:read"],
  "requires_approval": true,
  "daily_send_limit": 50
}

What the agent sees

POST /v1/emails from such a key still returns 202, with status: "pending_approval" and an approval_id. The message exists in the log with status queued. Suppression, the recipient allowlist and the daily cap are all checked first, so a send that would have been refused is refused rather than queued for someone to reject.

That status is not an error and there is nothing to retry — sending again queues a second draft. Replaying the same Idempotency-Key returns the same held response.

{
  "id": "…",
  "status": "pending_approval",
  "approval_id": "…",
  "thread_id": "…",
  "scheduled_at": null
}

The queue

GET /v1/approvals (scope emails:read) lists what is waiting, oldest first, each with id, message_id, api_key_id, created_at, expires_at and the full messageto, subject, text, html — so a reviewer can read it in place. An approval expires 72 hours after it was requested and drops out of the list, so a forgotten draft never goes out days later, out of context.

Deciding

POST /v1/approvals/{id} (scope emails:send) takes a decision of approve or reject, a decided_by naming who authorised it, and an optional reason. Approving sends the message immediately and returns status: "approved" with the message_id. If the held send carried a scheduled_at still in the future, approving places it on the schedule instead and the response carries that scheduled_at; a hold cannot be walked around by scheduling. Rejecting marks the message canceled.

curl -X POST https://api.example.com/v1/approvals/$APPROVAL_ID \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"decision": "approve", "decided_by": "ana@example.com"}'

Once, not twice

The decision is a conditional update: it applies only while the approval is still pending and unexpired, and the second caller to arrive gets 409 already_decided. Two reviewers clicking at the same moment, or one double-clicking, cannot release the same message twice — the second attempt fails before anything is dispatched. If the release itself fails at delivery time the call answers 502, and the approval stays decided rather than reopening.

What an agent should do with a held response