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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
pip install browser-use
export MULTIMAIL_API_KEY=mm_test_your_key_hereProvision a mailbox at a @multimail.dev address or a custom domain. Set `oversight_mode` to `gated_send` to require human approval on outbound messages.
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;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.
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)]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.
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())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.
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;Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.