> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://www.agentmail.to/docs/llms.txt.

# How do I build a human-in-the-loop workflow?

AgentMail provides several mechanisms for keeping humans involved when agents send emails. You can combine these approaches to match the level of oversight your workflow requires.

## 1. CC or BCC a human on every email

The simplest approach: copy a human on every email your agent sends so they have full visibility.

**`Python`**

```python title="Python"
client.inboxes.messages.send(
    inbox_id="agent@yourdomain.com",
    to=["customer@example.com"],
    cc=["manager@yourcompany.com"],
    subject="Re: Your request",
    text="I've processed your refund...",
    html="<p>I've processed your refund...</p>"
)
```

The manager sees every outgoing email and can step in if something looks wrong. Use `bcc` instead of `cc` if you want the oversight to be invisible to the recipient.

## 2. Drafts for review before sending

Use Drafts to let your agent compose emails that a human reviews and approves before they go out. This is ideal for high-stakes emails like contracts, legal communications, or financial matters.

**`Python`**

```python title="Python"
# agent composes a draft and labels it for review
draft = client.inboxes.drafts.create(
    inbox_id="agent@yourdomain.com",
    to=["important-client@example.com"],
    subject="Contract proposal",
    text="Here is our proposal...",
    html="<p>Here is our proposal...</p>",
    labels=["needs-approval"]
)

print(f"Draft created: {draft.draft_id}")

# human reviews the draft, then approves it
# (labels on a draft carry over to the sent message, so drop the review label)
sent_message = client.inboxes.drafts.send(
    inbox_id="agent@yourdomain.com",
    draft_id=draft.draft_id,
    remove_labels=["needs-approval"]
)
```

> **Warning**
>
> A draft is only an approval step if the agent cannot send on its own. If the agent's API key can send (`messages.send`, replies, forwards, or `drafts.send`), it can skip the review. Give the agent a key with [scoped permissions](https://docs.agentmail.to/permissions) that grants `draft_create` (plus `draft_read` and `draft_update` if it revises drafts) but not `message_send`, `message_reply`, or `draft_send`. Keep `draft_send` for the key your approval tool uses. Scheduling a draft with `send_at` also requires `draft_send`.

You can also list drafts across every inbox your API key can access, which is useful for building a central approval dashboard. Two things to keep in mind:

* `drafts.list()` returns every draft unless you filter it, so filter by the label your agent puts on drafts awaiting review.
* Results come one page at a time. `count` is the number of drafts in the current page, not the total, so follow `next_page_token` until it is absent.

**`Python`**

```python title="Python"
import os

from agentmail import AgentMail

client = AgentMail(api_key=os.getenv("AGENTMAIL_API_KEY"))

# collect every draft awaiting review, one page at a time
pending = []
page_token = None
while True:
    page = client.drafts.list(labels=["needs-approval"], page_token=page_token)
    pending.extend(page.drafts)
    page_token = page.next_page_token
    if not page_token:
        break

print(f"{len(pending)} drafts awaiting review")
```

**`TypeScript`**

```typescript title="TypeScript"
import { AgentMail, AgentMailClient } from "agentmail";

const client = new AgentMailClient({ apiKey: process.env.AGENTMAIL_API_KEY });

// collect every draft awaiting review, one page at a time
const pending: AgentMail.DraftItem[] = [];
let pageToken: string | undefined;
do {
  const page = await client.drafts.list({ labels: ["needs-approval"], pageToken });
  pending.push(...page.drafts);
  pageToken = page.nextPageToken;
} while (pageToken);

console.log(`${pending.length} drafts awaiting review`);
```

For more details, see the [Drafts core concept](https://docs.agentmail.to/drafts) documentation.

## 3. Labels for escalation

Use labels to flag messages that need human attention. Your agent can detect situations it cannot handle and tag them for review.

**`Python`**

```python title="Python"
# agent detects a situation that needs human review
client.inboxes.messages.update(
    inbox_id="agent@yourdomain.com",
    message_id=msg.message_id,
    add_labels=["needs-human-review", "escalation"]
)
```

Then build a dashboard or scheduled job that queries for flagged messages:

**`Python`**

```python title="Python"
# periodically check for messages that need human review
flagged = client.inboxes.messages.list(
    inbox_id="agent@yourdomain.com",
    labels=["needs-human-review"]
)

for msg in flagged.messages:
    notify_human(msg)
```

## 4. Allowlists as guardrails

Use a send allowlist to restrict which addresses your agent can email. If the agent tries to send to an address or domain not on the list, AgentMail rejects the request with a `403` and error code `message_rejected`, and the email does not go out.

**`Python`**

```python title="Python"
# only allow sending to known customer domains
client.lists.create("send", "allow", entry="trusted-customer.com")
client.lists.create("send", "allow", entry="partner-corp.com")
```

This acts as a hard safety boundary. Your agent can only email recipients you have explicitly approved, regardless of what the agent logic tries to do, as long as the agent's API key cannot edit lists (`list_entry_create` and `list_entry_delete`).

## Best practices

* **Start with CC + drafts** for new agents until you trust their behavior
* **Graduate to autonomous sending** for routine emails, and keep drafts for high-value communications
* **Always have an escalation path:** agents should know when to stop and ask for help
* **Use labels consistently** across your agents so dashboards and alerting work reliably
* **Combine approaches:** for example, let the agent send autonomously from an inbox with an inbox-level send allowlist of known recipients, route everything else through drafts in a separate inbox, and CC a human on everything during the first week. Give the agent an inbox-scoped key for each inbox, and leave the send permissions off the drafts inbox's key; a single key that can send would let the agent skip the drafts. Keep the two inboxes separate because a send allowlist also applies when a draft is sent, so an approved draft to a recipient outside the list would still be rejected.