Email tools

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.

Outreach2dayPublished 8 min read
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. A hosted Outreach2day MCP server is in development (Get early access on the home page); this server runs locally over the public REST API.

Which Actions an Agent Should Get

The four layers of cold email infrastructure are domains, mailboxes, warm-up and the sending tool. For the first two:

Action

Endpoint

Agent tool

Check if names are free

POST /check_domains

Yes (read-only)

List domains

GET /domains

Yes, without the registrant contact

List mailboxes

GET /mailboxes

Yes, without passwords

Connect domains you own

POST /domains/transfer/checkout

After you approve a plan (free)

Nameserver setup progress

GET /domains/transfer/status

Yes (read-only)

Buy domains

POST /domains/purchase

No: charges the card at once

Replace DNS records

POST /domains/{domain}/dns

No: replaces the record set

Order mailboxes

the app

No

Delete mailboxes

DELETE /mailboxes

No: removes paid mailboxes

The two "No" rows that matter most:

  • Buying. POST /domains/purchase charges the saved card when the request arrives. It has no quote step, no confirm step and no idempotency key, so a request that times out and is retried can charge twice (details in how to buy domains via API). An agent can suggest names and check them; you buy them.

  • DNS writes. POST /domains/{domain}/dns replaces 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

check_domains

Status of up to 50 names: free, taken or not_allowed

list_domains

Domains with status, mailbox count and redirect target

list_mailboxes

Mailboxes with status, optionally for one domain

plan_connect_domains

Prepares a list of your own domains to connect; changes nothing

apply_connect_plan

Connects the domains of an approved plan

connect_status

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["O2D_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:

  1. You: "Connect acme-outreach.com and acme-mail.com, redirect them to acme.com." The agent calls plan_connect_domains and shows the plan: two domains, redirect target, cost free.

  2. You approve. The agent calls apply_connect_plan, which sends POST /domains/transfer/checkout once.

  3. The agent calls connect_status and gives you the nameservers for each domain. You set them at your registrar. The agent cannot do this step: it happens in your registrar account.

  4. Later, the agent calls connect_status again. setupStage moves through creating_zone, waiting_nameservers, configuring_dns and active, or error with an errorMessage. 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:

  1. 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.

  2. Plan, approve, apply. A tool that changes something takes a plan ID, not free-form arguments. The person approves the plan in the conversation.

  3. Retries do not run twice. Either the API has an idempotency key (warm-up does, see the warm-up MCP post) or the server stores the first result and returns it.

  4. 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).

  5. Cap batch sizes. 50 domains, 100 mailboxes. Larger lists are split into plans a person can read.

  6. Strip secrets from results. Mailbox passwords and registrant contacts never enter the model's context.

  7. 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).

How Other Infrastructure Vendors Expose MCP

  • Maildoso runs a hosted MCP server at mcp.maildoso.com/mcp with 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 warm-up (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 warm-up with the warm-up 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.

Get Started

Create an account, make your first order in the app, then create a key on the API keys page and run the server above.

Get an API key

Free guide

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, warm-up, leads, copy and scaling.

Step-by-step playbook · 19 min read
  • Email tools8 min read

    Cold Email API for Domains, Warm-Up and Replies

  • Email tools7 min read

    How to Buy Domains via API for Cold Email

  • Email tools7 min read

    MCP Server for Email Warmup With Claude

See deliverability issues before they kill performance

Monitor mailbox health in real time, spot degradation early, and keep warmup, protection, and sending in one place.