For agents

Agent email approvals

To require human approval before an AI agent sends email, let the send be held: it becomes a row in the approval queue that a person approves or rejects with one request, and the agent is told to wait rather than that it failed.

To require human approval before an AI agent sends email, send through the approval queue: a held send becomes a row that GET /agent-actions lists, and a person releases it with POST /agent-actions/{id}/approve or refuses it with POST /agent-actions/{id}/reject. The agent is answered approval_required, which says the send is waiting rather than that it failed.

What gets held today

A send is held when the loop guard flags it: a repeat of a message this key already sent, or a collapse in who it is sending to. That is the rule that puts sends in this queue right now, and /agent-email-loop-detection states its thresholds.

An agent can also ask for approval itself. Over MCP, the request_approval tool puts a send in the same queue before attempting it, which is the way to gate a send you already know needs a person.

The queue

curl -sS -X GET https://api.agentisend.com/agent-actions \
  -H "Authorization: Bearer $AGENTISEND_API_KEY"
200
{
  "data": [],
  "has_more": true,
  "next_cursor": "string"
}
Response

Filter by state to read one slice of it. Each row carries:

  • statepending, approved, rejected or killed.
  • preview.subject, preview.to_count and preview.first_recipients — at most three addresses, so a queue full of thousand-recipient holds is still readable.
  • preview.api_key_id — which agent asked.
  • preview.held_reason — which check held it.
  • decided_by, decision_reason and decided_at — null while pending.

Approving

curl -sS -X POST https://api.agentisend.com/agent-actions/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42/approve \
  -H "Authorization: Bearer $AGENTISEND_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
201
{
  "action": "string",
  "message_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "status": "string"
}
Response

The approved send runs through the same path any other send takes: budget, suppression, trust, events. An approved send is indistinguishable from one that was never held, and the reply carries its message_id.

Rejecting is POST /agent-actions/{id}/reject with a reason of up to 500 characters. The reason stays on the row, and both decisions are written to the audit log with who made them.

What the agent should do while it waits

Poll the action. Do not resubmit: a second submission is a second held send, and an agent that treats approval_required as a failure and retries turns a queue into a backlog.

{
  "error": {
    "code": "approval_required",
    "message": "This action requires human approval before it executes.",
    "fix": "Approve the pending action via POST /agent-actions/:id/approve, then retry.",
    "docs_url": "https://agentisend.com/docs/errors",
    "retryable": false
  }
}

The console shows the same queue with the message body rendered, because approving a send nobody has read is not approval.