# Human-in-the-loop email agents

> An approval queue checks the agent’s replies. A hand-off gives the conversation to a person. Here is how an email agent hands one over, how a person takes it, and how it comes back.

## A queue for approval is not a hand-off

Most "human in the loop" setups for email agents are an approval queue: the agent writes a reply, and a person checks it before it goes. That is useful, and it is not the hard case. The hard case is the conversation the agent should stop answering altogether: the customer who wants a refund above what it may approve, the supplier who is angry, the reply that is really a legal question.

For that, the conversation has to change hands. A person has it now, the agent has stepped back, and everything watching knows.

## The owner is a field on the thread

In Pidgeon every conversation has an owner: `agent`, `human` or `automation`. Handing one over is one call, because it is one intention:

```sh
curl https://pidgeon.ai/api/v1/threads/$THREAD_ID \
  -X PATCH \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "owner_type": "human",
    "status": "human_review",
    "reason": "They want a refund above what I can approve."
  }'
```

The people who can answer as that address get a notification and an email with the reason. In the inbox the conversation sits under **Waiting for a person** with a **Take it** button. They reply as the same address, from the web inbox or from Apple Mail with an app password, and a reply takes the conversation over even if nobody pressed anything.

Your code hears all of it: `thread.assigned` and `thread.updated` fire on every change, and `thread.updated` carries the previous owner beside the new one. Hand it back with `owner_type: "agent"` and the agent finds it among its own conversations. Over MCP, the `update_thread` tool does the same.

The other side keeps writing to the same address, in the same thread. Whether to tell them a person has joined is your call.

## An inbox per role, and a key for each

Builders who run several agents tend to land on the same shape:

- **One inbox per agent role.** `support@`, `billing@`, `scout@`, each created in one call. Mail arriving at an agent address starts a conversation the agent owns, so each agent's queue is the list of its own threads.
- **A key per agent.** Scope each agent's key to its own addresses, and it cannot read another agent's conversations or the inbox a person works from.
- **An inbound policy per inbox.** Give an address its own allow list, so the billing agent only hears from customers on file. A sender on the list counts only when its DMARC passes, so a forged From is a stranger.

## Start

The [hand-off guide](https://pidgeon.ai/docs/handover.md) walks through all of it against your own address in about five minutes: mail arrives, the agent gets stuck, a person takes it and hands it back. To have a person check each reply instead, see [approving drafts](https://pidgeon.ai/docs/approve-drafts.md).
