Guide

Approval queue

The approval queue is the safety layer between your agent and the outside world. When your agent calls mail.send, the message doesn't go out immediately — it enters a queue where a human (or another agent) reviews and approves it.

This is the default behavior, and it's intentional. Agents make mistakes. They hallucinate. They misinterpret context. The approval queue ensures a human is always in the loop for outbound communication, until you've built enough confidence to relax the rules.

Approval modes

Each mailbox has an approval_mode that controls how outbound messages are handled:

all — Review everything

Every outbound message is held. Nothing sends without explicit human approval. This is the default and the recommended starting point.

smart — Auto-approve trusted patterns

Messages matching your auto-approve rules send immediately. Everything else is held. This is the mode you graduate to after building confidence.

none — No review

Messages send immediately. No queue. Use this only for fully automated workflows where you've validated the agent's behavior extensively and accept the risk.

Auto-approve rules

When approval_mode is smart, you define rules that determine which messages skip the queue. Rules are evaluated in order — the first match wins.

mailbox.update({
  id: "mbx_ops",
  approval_mode: "smart",
  auto_approve_rules: [
    // Auto-approve replies to existing threads
    { type: "reply" },

    // Auto-approve messages to specific domains
    { type: "domain", match: "acme.com" },
    { type: "domain", match: "*.internal.co" },

    // Auto-approve messages to contacts in a named list
    { type: "contact_list", list: "approved_vendors" },

    // Auto-approve based on subject pattern
    { type: "subject", match: "RE:*" },

    // Auto-approve all messages (makes smart behave like none)
    { type: "all" }
  ]
})

Rule types

  • reply — Matches messages that are replies to existing inbound threads. The agent is responding to someone who emailed first.
  • domain — Matches messages where all recipients are at a specified domain. Supports glob patterns (*.acme.com).
  • contact_list — Matches messages where all recipients are in a named contact list. Manage lists with contact.create.
  • subject — Matches messages whose subject matches a pattern. Glob patterns supported.
  • all — Matches everything. Effectively disables the queue for this mailbox. Place last.
New recipients are always held. Even with auto-approve rules, messages to recipients your agent has never emailed before are held for review the first time. This prevents the agent from cold-emailing strangers without human oversight.

Reviewing the queue

There are three ways to review pending messages:

1. Via MCP tools (agent-assisted review)

A human asks their agent "show me pending emails" and the agent calls:

const pending = await queue.list({
  status: "pending"
})

// Returns array of queued messages with full preview
// { id, to, subject, body, attachments, queued_at, mailbox }

The human reviews and the agent approves:

await queue.approve({ message_id: "msg_..." })

// Or approve with edits
await queue.approve({
  message_id: "msg_...",
  edits: {
    body: "Revised message text..."
  }
})

// Or reject
await queue.reject({
  message_id: "msg_...",
  reason: "Too aggressive tone"
})

2. Via the dashboard

The MCPMail dashboard shows all pending messages with one-click approve/reject. Useful for reviewing a batch quickly.

3. Via webhook + external tool

Register a webhook for queue.pending events. When a message enters the queue, MCPMail posts a notification to your URL. You can build approval flows in Slack, Teams, or any internal tool.

webhook.register({
  url: "https://your-app.com/webhooks/mcpmail",
  events: ["queue.pending"]
})

Queue behavior with mail.wait

When your agent calls mail.send followed by mail.wait, the wait begins after the message is approved and sent, not when it enters the queue. If the message is rejected, mail.wait returns a rejection result instead of waiting for a reply that will never come.

// mail.send puts the message in the queue
const sent = await mail.send({ ... })
// sent.status is "queued" — not sent yet

// mail.wait handles the queue transparently
const reply = await mail.wait({
  after_message: sent.id,
  timeout: "48h"
})

// If the queued message was rejected:
// reply = { rejected: true, reason: "..." }

// If approved, sent, and a reply arrived:
// reply = { body: "...", from: "...", ... }

Graduation path

The recommended path from full review to autonomous sending:

  1. Start with all — Review every message. Build confidence in what the agent produces.
  2. Move to smart with conservative rules — Auto-approve replies to threads. Hold everything else.
  3. Expand rules gradually — Add trusted domains, contact lists, subject patterns as you verify the agent handles them correctly.
  4. Consider none only for low-risk workflows — Internal notifications, status updates, automated reports where a bad message is annoying but not damaging.
Rejection reasons are valuable. When you reject a message with a reason, that feedback can be used to improve your agent's prompts. Over time, rejection rates drop and auto-approve coverage expands.