MCP for Cold Email Infrastructure: Domains and Mailboxes
Give an AI agent your cold email infrastructure through MCP: check domains, connect your own, list mailboxes. Which actions to allow, which to keep manual.

On this page
An AI agent that works on cold email infrastructure needs to know which domains you have, which names are free, which mailboxes exist and where each domain is in its setup. Most of that is reading. A few actions change things, and one of them, buying domains, costs money the moment it runs.
This post sorts the domain and mailbox operations of the Outreach2day API into what an agent may do on its own, what it may do after you approve a plan, and what stays with a person. Then it builds an MCP server for Claude or Cursor that follows those rules. The default is the hosted Outreach2day MCP server (Outreach2day MCP): add https://public.outreach2day.com/mcp to your AI client and sign in with your Outreach2day account. The server built here is optional: it runs locally over the public REST API with a key from $OUTREACH2DAY_API_KEY.
Which Actions an Agent Should Get
The four layers of cold email infrastructure are domains, mailboxes, warmup and the sending tool. For the first two:
Action | Endpoint | Agent tool |
|---|---|---|
Check if names are free |
| Yes (read-only) |
List domains |
| Yes, without the registrant contact |
List mailboxes |
| Yes, without passwords |
Connect domains you own |
| After you approve a plan (free) |
Nameserver setup progress |
| Yes (read-only) |
Buy domains |
| No: paid right away |
Replace DNS records |
| No: replaces the record set |
Order mailboxes | the app | No |
Delete mailboxes |
| No: removes paid mailboxes |
The two "No" rows that matter most:
Buying. Buying domains through the API is paid right away — give an agent a billing-scoped key only if you want it to buy.
POST /domains/purchasehas no idempotency key, so checkGET /domainsbefore retrying a request that timed out (details in how to buy domains via API). Our hosted MCP server buys through a quote, your confirmation and a Stripe checkout instead.DNS writes.
POST /domains/{domain}/dnsreplaces every record of the domain with the list you send. A request with one record deletes the MX, SPF, DKIM and DMARC records. Records on domains bought or connected through Outreach2day are set automatically, so there is nothing for an agent to edit.
Domain Checker and Mailbox MCP Tools
Tool | What it does |
|---|---|
| Status of up to 50 names: |
| Domains with status, mailbox count and redirect target |
| Mailboxes with status, optionally for one domain |
| Prepares a list of your own domains to connect; changes nothing |
| Connects the domains of an approved plan |
| Setup stage and nameservers per connected domain |
The Server
Save this as o2d_infra_mcp.py. Setup, testing and client config are the same as in the cold email MCP server post.
import os
import uuid
import requests
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import ToolAnnotations
BASE = "https://public.outreach2day.com"
KEY = os.environ["OUTREACH2DAY_API_KEY"]
WS = int(os.environ["O2D_WORKSPACE_ID"])
READ = ToolAnnotations(read_only_hint=True)
PLANS = {}
mcp = MCPServer("outreach2day-infra")
def call(method, path, **kwargs):
try:
r = requests.request(
method, BASE + path, timeout=30,
headers={"Authorization": f"Bearer {KEY}"},
**kwargs,
)
except requests.RequestException as e:
raise ToolError(
f"Network error ({type(e).__name__}); the "
"request may have arrived. Check state "
"before retrying.") from e
if r.status_code >= 400:
raise ToolError(
f"API {r.status_code}: {r.text[:300]}")
return r.json()
def pick(rows, keys):
return [{k: row.get(k) for k in keys} for row in rows]
@mcp.tool(annotations=READ)
def check_domains(domains: list[str]) -> list:
"""Check up to 50 names: free, taken or
not_allowed. Buys nothing."""
if len(domains) > 50:
raise ToolError("Send at most 50 names")
res = call("POST", "/check_domains",
json={"domains": domains})
return res["domains"]
@mcp.tool(annotations=READ)
def list_domains() -> list:
"""Domains in the workspace with status and
mailbox count. Registrant contact is dropped."""
rows = call("GET", "/domains",
params={"workspace_id": WS})
return pick(rows, ("id", "domain", "status",
"mailboxes_count",
"redirect_domain"))
@mcp.tool(annotations=READ)
def list_mailboxes(domain_id: int | None = None) -> list:
"""Mailboxes: address, status, domain_id.
Optional domain_id filter. No passwords."""
rows = call("GET", "/mailboxes",
params={"workspace_id": WS})
rows = [m for m in rows if domain_id is None
or m.get("domain_id") == domain_id]
return pick(rows, ("id", "address", "status",
"domain_id"))
@mcp.tool(annotations=READ)
def plan_connect_domains(domains: list[str],
redirect_domain: str) -> dict:
"""Prepare connecting domains the user already
owns (free, needs a nameserver change at their
registrar). Changes nothing. Show the plan and
call apply_connect_plan only after approval."""
names = sorted({d.strip().lower() for d in domains
if d.strip()})
if not 0 < len(names) <= 50:
raise ToolError("Send 1-50 domains")
plan_id = uuid.uuid4().hex
PLANS[plan_id] = {"domains": names,
"redirect": redirect_domain,
"result": None}
return {"plan_id": plan_id, "domains": names,
"redirect_domain": redirect_domain,
"cost": "free"}
@mcp.tool(annotations=ToolAnnotations(
read_only_hint=False, destructive_hint=False,
idempotent_hint=True))
def apply_connect_plan(plan_id: str) -> dict:
"""Connect the domains of an approved plan.
Calling it again returns the first result.
After a network error, call connect_status
before trying again."""
plan = PLANS.get(plan_id)
if plan is None:
raise ToolError("Unknown plan_id; call "
"plan_connect_domains first")
if plan["result"] is None:
plan["result"] = call(
"POST", "/domains/transfer/checkout",
json={"workspaceId": WS,
"domains": plan["domains"],
"redirectDomain": plan["redirect"]})
return {"domainsCount":
plan["result"].get("domainsCount"),
"next": "call connect_status for the "
"nameservers to set"}
@mcp.tool(annotations=READ)
def connect_status() -> list:
"""Setup stage and nameservers of connected
domains: creating_zone, waiting_nameservers,
configuring_dns, active or error."""
res = call("GET", "/domains/transfer/status",
params={"workspaceId": WS})
return pick(res["domains"], (
"domain", "setupStage", "nameservers",
"dnsReady", "errorMessage"))
if __name__ == "__main__":
mcp.run()
How Connecting Your Own Domains Works
Domains registered elsewhere can be connected for free. Outreach2day creates a DNS zone for each one; you change the nameservers at your registrar, and the mail records are added once the zone is active. The API takes up to 250 domains per request; the server caps a plan at 50 so it stays readable.
With the agent, the flow is:
You: "Connect acme-outreach.com and acme-mail.com, redirect them to acme.com." The agent calls
plan_connect_domainsand shows the plan: two domains, redirect target, cost free.You approve. The agent calls
apply_connect_plan, which sendsPOST /domains/transfer/checkoutonce.The agent calls
connect_statusand gives you thenameserversfor each domain. You set them at your registrar. The agent cannot do this step: it happens in your registrar account.Later, the agent calls
connect_statusagain.setupStagemoves throughcreating_zone,waiting_nameservers,configuring_dnsandactive, orerrorwith anerrorMessage. Nameserver changes can take hours to spread, so there is no point in checking every minute.
A second call to apply_connect_plan with the same plan returns the stored result and sends nothing. If the same domains are sent again, the API rejects them with DOMAINS_ALREADY_EXIST, so connecting has no double-run risk. After a timeout the request may still have gone through: the tool says so, and the agent should call connect_status before retrying. A DOMAINS_ALREADY_EXIST answer on a retry then means the first call worked.
Safety Rules for Agents on Cold Email Infrastructure
The server above follows these rules; they apply to any agent you give infrastructure access:
No tool that spends money. Purchases need a price the person has seen and a confirmation outside the model's control. If an API charges on call, it does not become a tool.
Plan, approve, apply. A tool that changes something takes a plan ID as its argument. The person approves the plan in the conversation.
Retries do not run twice. Either the API has an idempotency key (warmup does, see the warmup MCP post) or the server stores the first result and returns it.
Pin the workspace and use a separate key. An Outreach2day key acts as your user across every workspace you belong to. The server sends one workspace ID from its environment, and a key made only for the agent can be revoked on its own (see keep the key safe).
Cap batch sizes. 50 domains, 100 mailboxes. Larger lists are split into plans a person can read.
Strip secrets from results. Mailbox passwords and registrant contacts never enter the model's context.
Treat outside text as data. Replies and web pages the agent reads can contain instructions. A tool that reads replies should not share a server with tools that change infrastructure unless every change needs approval.
The MCP specification points the same way: clients should ask for confirmation on sensitive operations and show tool inputs before calling the server (MCP tools, security considerations).
Free guide19 min read
Send 100,000 cold emails a month
Domains and mailboxes you need, DNS, warmup, lists, copy and follow-ups, with a launch checklist, sent to your inbox.
How Infrastructure Vendors Expose MCP
Outreach2day runs a live hosted MCP server at
https://public.outreach2day.com/mcpthat follows the rules above end to end: it checks names, quotes domains and mailboxes, and returns a Stripe checkout link that charges nothing, so you pay on the Stripe page. It connects domains you own, starts warmup, launches campaigns only after 14 days of warmup and a passed preflight, and exports warm mailboxes to Instantly or Smartlead after you confirm (Outreach2day MCP).Maildoso runs a hosted MCP server at
mcp.maildoso.com/mcpwith a personal access token. Its post describes one prompt that registers domains, creates mailboxes with SPF, DKIM and DMARC, exports them to a sequencer and starts warmup (Maildoso API and MCP).Mailforge and Infraforge are part of the Salesforge Forge MCP server at
https://mcp.salesforge.ai/mcp, with a separate header key per product. Its README lists 23 Mailforge and 24 Infraforge tools, including domain availability and DNS (forge-mcp).
If you connect a hosted server that can buy, check how it confirms a purchase and what a retry does.
FAQ
Can the agent create mailboxes?
Not through this server. Mailboxes are ordered in the app; the agent can list them once they exist and start their warmup with the warmup server.
Can the agent pick domain names?
Yes. Ask it for name ideas and have it call check_domains. It returns free, taken or not_allowed per name; prices are on the pricing page.
How is this different from a WHOIS domain checker MCP server?
Open-source domain checker servers query WHOIS or DNS. check_domains asks the registrar Outreach2day registers domains through, so free means you can order the name here, and not_allowed marks names that cannot be registered this way.
Why is the redirect target required?
redirectDomain is the site people land on when they type a sending domain into a browser. It is a required field of the connect request.
Does connecting cost anything?
No. Connecting your own domains is free; mailboxes on them are billed per mailbox.
How does this fit with the REST API?
The tools are thin wrappers. Everything they do is also available with curl; see the cold email API overview.
Connect the Hosted Outreach2day Server
Add https://public.outreach2day.com/mcp to your AI client and sign in with your Outreach2day account when it asks: in claude.ai and Claude Desktop as a custom connector, in ChatGPT as a developer-mode app, in Cursor and VS Code with the one-click links on the MCP page. To set up every MCP client on your computer at once, run the installer:
curl -fsSL https://outreach2day.com/install | sh
On Windows, in PowerShell: irm https://outreach2day.com/install.ps1 | iex. The installer finds Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Gemini CLI and Codex on your computer, backs up each config file and adds an outreach2day server by URL next to the servers you already have (for Claude Desktop, which takes remote servers as connectors, it prints the steps). It asks for no API key: each client opens an Outreach2day sign-in page the first time it connects. For CI or a machine without a browser, run the installer with --use-key: it asks for the key once and the client config reads it from $OUTREACH2DAY_API_KEY. --client cursor limits it to one client, --dry-run shows the changes without writing them, --uninstall takes them out again.
Then ask your agent: "Check my Outreach2day account status". Steps for each client are on the MCP page.
The hosted server's domain and mailbox tools, an example order from name check to live mailboxes, and what the agent cannot do are on domains and mailboxes over MCP. The full chain through warmup and the campaign is on outbound end to end over MCP.
Get Started
Create an account and make your first order in the app, then add the MCP server to your AI client and sign in. For the local server above, create a key on the API keys page.
Keep reading
Send 100,000 cold emails a month
How to send 100,000 cold emails a month while keeping quality, engagement and conversion rates high: domains, mailboxes, warmup, leads, copy and scaling
Step-by-step playbook · 19 min readEmail tools9 min read
Cold Email API for Domains, Warmup and Replies
Email tools8 min read
MCP Server for Email Warmup With Claude
Email tools7 min read
How to Buy Domains via API for Cold Email
Answer2 min read
How Many Mailboxes Do I Need to Send N Cold Emails a Day?
Agent skill
Cold email sending domain planner