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

Keep humans in control of your agent's email communications.

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
1client.inboxes.messages.send(
2 inbox_id="agent@yourdomain.com",
3 to=["customer@example.com"],
4 cc=["manager@yourcompany.com"],
5 subject="Re: Your request",
6 text="I've processed your refund...",
7 html="<p>I've processed your refund...</p>"
8)

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
1# Agent composes a draft
2draft = client.inboxes.drafts.create(
3 inbox_id="agent@yourdomain.com",
4 to=["important-client@example.com"],
5 subject="Contract proposal",
6 text="Here is our proposal...",
7 html="<p>Here is our proposal...</p>"
8)
9
10print(f"Draft created: {draft.draft_id}")
11
12# Human reviews the draft, then approves it
13sent_message = client.inboxes.drafts.send(
14 inbox_id="agent@yourdomain.com",
15 draft_id=draft.draft_id
16)

You can also list all pending drafts across your entire organization, which is useful for building a central approval dashboard:

Python
1# List all drafts across every inbox in the organization
2all_drafts = client.drafts.list()
3print(f"{all_drafts.count} drafts pending review")

For more details, see the Drafts core concept 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
1# Agent detects a situation that needs human review
2client.inboxes.messages.update(
3 inbox_id="agent@yourdomain.com",
4 message_id=msg.message_id,
5 add_labels=["needs-human-review", "escalation"]
6)

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

Python
1# Periodically check for messages that need human review
2flagged = client.inboxes.messages.list(
3 inbox_id="agent@yourdomain.com",
4 labels=["needs-human-review"]
5)
6
7for msg in flagged.messages:
8 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 will reject the request and the email will not go out.

Python
1# Only allow sending to known customer domains
2client.lists.create("send", "allow", entry="trusted-customer.com")
3client.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.

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, use allowlists for autonomous sending to known recipients, drafts for unknown recipients, and CC a human on everything during the first week