Give Browser Use Agents a Real Email API

Stop scraping webmail UIs. MultiMail gives your Browser Use agents structured email endpoints with audit logs, oversight modes, and formal security guarantees.


Browser Use lets AI agents drive web browsers — clicking buttons, filling forms, reading page content — to accomplish tasks inside real SaaS products. At ~40k GitHub stars, it's the dominant framework for agents that need to interact with tools that don't expose clean APIs.

Email is a common target for browser agents: read the inbox, reply to a message, forward to a colleague. But navigating Gmail or Outlook through the DOM is brittle. Session tokens expire, UI layouts change, and there are no guardrails — a misclick sends an email before your oversight logic runs.

MultiMail provides a purpose-built email API for agents. Instead of your Browser Use agent clicking through a webmail interface, it calls `POST /send_email` or uses the MCP `send_email` tool directly. Every action is logged, every send goes through your configured oversight mode, and the same code works regardless of which email provider is underneath.

Built for Browser Use developers

No more brittle DOM selectors

Webmail UIs change. A Gmail layout update or an Outlook modal dialog can break your agent mid-task with no useful error. MultiMail's REST API returns structured JSON — no XPath, no CSS selectors, no visual parsing required.

Oversight modes that actually enforce

Browser agents clicking through Gmail have no approval layer — the Send button is the Send button. MultiMail's `gated_send` mode holds outbound messages until a human approves via webhook, giving you a real checkpoint before delivery.

Audit trail for every action

Every `send_email`, `read_email`, and `decide_email` call is logged with timestamp, agent identity, and outcome. Browser automation leaves no structured trace — MultiMail gives you a queryable record for compliance and debugging under GDPR and CAN-SPAM.

Stable API surface across providers

Whether routing through a custom domain or a @multimail.dev mailbox, the API calls are identical. Your Browser Use agent doesn't need to know which provider is underneath, and swapping providers doesn't touch agent code.

Formally verified security model

MultiMail's oversight, identity, and authorization logic is proven correct in Lean 4 — the same class of formal verification used in safety-critical systems. Browser automation has no equivalent guarantee.


Get started in minutes

Browser Use agent with MultiMail email tools
python
import os
import asyncio
import httpx
from browser_use import Agent
from browser_use.tools import Tool
from langchain_openai import ChatOpenAI

MULTIMAIL_API_KEY = os.environ["MULTIMAIL_API_KEY"]
BASE = "https://api.multimail.dev"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}"}


def send_email(to: str, subject: str, body: str) -> dict:
    """Send an email from the agent mailbox."""
    resp = httpx.post(
        f"{BASE}/send_email",
        headers=HEADERS,
        json={
            "to": to,
            "subject": subject,
            "body": body,
            "from_mailbox": "[email protected]",
        },
    )
    resp.raise_for_status()
    return resp.json()


def check_inbox(limit: int = 10) -> dict:
    """Return recent inbox messages."""
    resp = httpx.get(
        f"{BASE}/check_inbox",
        headers=HEADERS,
        params={"limit": limit},
    )
    resp.raise_for_status()
    return resp.json()


agent = Agent(
    task="Check the inbox and reply to any messages asking about pricing.",
    llm=ChatOpenAI(model="gpt-4o"),
    tools=[
        Tool.from_function(send_email),
        Tool.from_function(check_inbox),
    ],
)

asyncio.run(agent.run())

Define send and read tools using MultiMail's REST API and register them with a Browser Use agent.

Gated send — human approval before delivery
python
import os
import asyncio
import httpx
from browser_use import Agent
from browser_use.tools import Tool
from langchain_openai import ChatOpenAI

"cm"># Mailbox must be configured with oversight_mode=gated_send
"cm"># Set once: POST /create_mailbox with {"oversight_mode": "gated_send"}

MULTIMAIL_API_KEY = os.environ["MULTIMAIL_API_KEY"]
BASE = "https://api.multimail.dev"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}"}


def send_email_for_approval(to: str, subject: str, body: str) -> dict:
    """
    Submit email for human approval.
    Returns immediately with status=pending.
    Delivery waits for approval webhook.
    """
    resp = httpx.post(
        f"{BASE}/send_email",
        headers=HEADERS,
        json={
            "to": to,
            "subject": subject,
            "body": body,
            "from_mailbox": "[email protected]",
        },
    )
    resp.raise_for_status()
    return resp.json()  "cm"># {"status": "pending", "message_id": "msg_..."}


def list_pending_approvals() -> dict:
    """Check which outbound messages are waiting for human approval."""
    resp = httpx.get(f"{BASE}/list_pending", headers=HEADERS)
    resp.raise_for_status()
    return resp.json()


agent = Agent(
    task=(
        "Draft follow-up emails to leads from the CRM, "
        "then report how many are pending approval."
    ),
    llm=ChatOpenAI(model="gpt-4o"),
    tools=[
        Tool.from_function(send_email_for_approval),
        Tool.from_function(list_pending_approvals),
    ],
)

asyncio.run(agent.run())

Configure MultiMail's `gated_send` oversight mode so your Browser Use agent drafts emails but a human approves before delivery.

Inbound webhook triggering a Browser Use agent
python
import os
import asyncio
import httpx
from fastapi import FastAPI, Request
from browser_use import Agent
from browser_use.tools import Tool
from langchain_openai import ChatOpenAI

app = FastAPI()
MULTIMAIL_API_KEY = os.environ["MULTIMAIL_API_KEY"]
BASE = "https://api.multimail.dev"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}"}


@app.post("/webhook/inbound")
async def handle_inbound(request: Request):
    payload = await request.json()
    message_id = payload["message_id"]
    sender = payload["from"]
    subject = payload["subject"]
    body = payload["body_text"]

    def reply(text: str) -> dict:
        """Reply to the inbound message."""
        return httpx.post(
            f"{BASE}/reply_email",
            headers=HEADERS,
            json={"message_id": message_id, "body": text},
        ).json()

    def tag(tags: list[str]) -> dict:
        """Tag the inbound message."""
        return httpx.post(
            f"{BASE}/tag_email",
            headers=HEADERS,
            json={"message_id": message_id, "tags": tags},
        ).json()

    agent = Agent(
        task=(
            f"Email from {sender}, subject &"cm">#039;{subject}'.\n"
            f"Body: {body}\n\n"
            "1. Tag it &"cm">#039;inbound-webhook'.\n"
            "2. If it&"cm">#039;s a support request, tag it 'support' and reply with a confirmation.\n"
            "3. Otherwise reply that the message was received."
        ),
        llm=ChatOpenAI(model="gpt-4o"),
        tools=[Tool.from_function(reply), Tool.from_function(tag)],
    )
    asyncio.create_task(agent.run())
    return {"status": "processing"}

Handle inbound emails via MultiMail webhook and launch a Browser Use agent to act on the content.

Thread-aware reply with contact context
python
import os
import asyncio
import httpx
from browser_use import Agent
from browser_use.tools import Tool
from langchain_openai import ChatOpenAI

MULTIMAIL_API_KEY = os.environ["MULTIMAIL_API_KEY"]
BASE = "https://api.multimail.dev"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}"}


def get_thread(thread_id: str) -> dict:
    """Fetch all messages in a thread."""
    return httpx.get(
        f"{BASE}/get_thread",
        headers=HEADERS,
        params={"thread_id": thread_id},
    ).json()


def search_contacts(query: str) -> dict:
    """Search contacts by name or email."""
    return httpx.get(
        f"{BASE}/search_contacts",
        headers=HEADERS,
        params={"q": query},
    ).json()


def reply_email(message_id: str, body: str) -> dict:
    """Reply to a specific message."""
    return httpx.post(
        f"{BASE}/reply_email",
        headers=HEADERS,
        json={"message_id": message_id, "body": body},
    ).json()


agent = Agent(
    task=(
        "Fetch thread thd_01JQXA7KBRM3P and read the full conversation. "
        "Search contacts to find history with the sender. "
        "Reply to the latest message summarizing agreed next steps."
    ),
    llm=ChatOpenAI(model="gpt-4o"),
    tools=[
        Tool.from_function(get_thread),
        Tool.from_function(search_contacts),
        Tool.from_function(reply_email),
    ],
)

asyncio.run(agent.run())

Fetch an email thread and search contacts via MultiMail, then have the agent compose a context-aware reply.


Step by step

1

Install Browser Use and get a MultiMail API key

Install Browser Use via pip. Sign up at multimail.dev to get an API key. Use `mm_test_...` keys during development — they go through the same code paths but don't deliver to real recipients.

bash
pip install browser-use
export MULTIMAIL_API_KEY=mm_test_your_key_here
2

Create a mailbox for your agent

Provision a mailbox at a @multimail.dev address or a custom domain. Set `oversight_mode` to `gated_send` to require human approval on outbound messages.

bash
curl -X POST https://api.multimail.dev/create_mailbox \
  -H "Authorization: Bearer $MULTIMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d &"cm">#039;{
    "address": "[email protected]",
    "oversight_mode": "gated_send"
  }&"cm">#039;
3

Wrap MultiMail endpoints as Python tool functions

Define functions for the operations your agent needs. Browser Use's `Tool.from_function` uses the docstring as the tool description shown to the LLM.

bash
import httpx
from browser_use.tools import Tool

BASE = "https://api.multimail.dev"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}"}

def send_email(to: str, subject: str, body: str) -> dict:
    """Send an email from the agent mailbox."""
    return httpx.post(
        f"{BASE}/send_email",
        headers=HEADERS,
        json={"to": to, "subject": subject, "body": body,
              "from_mailbox": "[email protected]"},
    ).json()

def check_inbox(limit: int = 5) -> dict:
    """Return recent inbox messages."""
    return httpx.get(
        f"{BASE}/check_inbox", headers=HEADERS, params={"limit": limit}
    ).json()

tools = [Tool.from_function(send_email), Tool.from_function(check_inbox)]
4

Attach tools to your Browser Use agent

Pass the MultiMail tools alongside any browser tools. The agent will call them when the task involves email — no changes to the agent loop required.

bash
from browser_use import Agent
from langchain_openai import ChatOpenAI
import asyncio

agent = Agent(
    task="Check the inbox and send a summary of unread messages to [email protected].",
    llm=ChatOpenAI(model="gpt-4o"),
    tools=tools,
)
asyncio.run(agent.run())
5

Set up an inbound webhook for reactive agents

Add a `webhook_url` to your mailbox so MultiMail POSTs inbound email events to your endpoint. Your server can then launch a Browser Use agent in response to incoming mail.

bash
curl -X POST https://api.multimail.dev/create_mailbox \
  -H "Authorization: Bearer $MULTIMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d &"cm">#039;{
    "address": "[email protected]",
    "webhook_url": "https://yourapp.com/webhook/inbound",
    "oversight_mode": "gated_send"
  }&"cm">#039;

Common questions

Why not just let Browser Use drive Gmail or Outlook directly?
Browser-driven webmail works until it doesn't: UI changes break selectors, CAPTCHA challenges interrupt sessions, and two-factor prompts stall the agent. More critically, there's no approval layer — if the agent clicks Send, the email is gone. MultiMail gives you a stable API, structured responses, and oversight modes that enforce review before delivery.
Does MultiMail work with custom domains or only @multimail.dev addresses?
Both. You can provision mailboxes on any domain you control by adding MultiMail's MX records in your DNS settings. @multimail.dev addresses work immediately with no DNS changes.
What happens to messages in `gated_send` mode if the human never approves?
Messages stay in the pending queue until approved or cancelled. You can cancel a specific message via `DELETE /cancel_message` with the `message_id` returned from the original send call. All pending messages are visible via `GET /list_pending`.
Can I use the MultiMail MCP server instead of the REST API?
Yes. MultiMail's MCP server exposes the same operations as MCP tools — `send_email`, `check_inbox`, `read_email`, `get_thread`, `decide_email`, and more. If your setup uses an MCP-compatible LLM host like Claude Desktop or Cursor, configure the MultiMail MCP server and skip the REST wrappers entirely.
How are API calls authenticated?
All requests use Bearer token auth. Pass your key in the `Authorization` header: `Authorization: Bearer $MULTIMAIL_API_KEY`. Use `mm_test_...` keys in development — they run through the full code path but don't deliver to real recipients.
Does MultiMail log what my Browser Use agent sends?
Yes. Every send, read, reply, and tag operation is logged with timestamp, originating API key, target mailbox, and outcome. This audit trail is queryable via the dashboard and API, and is useful for demonstrating compliance under GDPR and CAN-SPAM.

Explore more

The only agent email with a verifiable sender

Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.