agent-cold-email
This server is an agent-native cold-email platform (28 tools) that lets you provision and manage domains/mailboxes, run campaigns, handle replies, monitor metrics, and configure dashboards/webhooks — all via API/MCP/CLI.
Infrastructure: setup_infrastructure buys lookalike domains, provisions mailboxes, starts warmup; infrastructure_status shows health, daily caps, and next steps; remove_mailboxes downgrades.
Campaigns: launch_campaign with your own content, pause/pause_all, campaign_results and list_campaigns for per-campaign stats.
Inbox & Replies: inbox lists threads, thread shows history, reply sends follow-ups (with dedup and daily-cap enforcement), mark/label_thread triages.
Metrics & Account: metrics gives account-wide totals; account shows billing, activation state, and deliverability health.
Leads: suppress_lead (permanent opt-out), update_lead (disposition/tags/notes), list_leads (export, cursor-paginated).
Webhooks: configure_webhook creates/updates/deletes HMAC-signed push subscriptions for replies, bounces, complaints, etc.; get_webhooks lists them.
BYO Domains: configure_byo_domain registers/advances bring-your-own domains or connects existing mailboxes; get_byo_domains shows intake status.
Dashboard: get_dashboard/configure_dashboard lets the agent control human-facing dashboard layouts.
Messaging: list_messages/ack_message handle system/operator notices; contact_operator escalates to a human (works even when suspended).
Convenience: idempotency keys, quote-only provisioning, and sandbox demo support safe agent operation.
Allows sending and receiving emails via the Gmail API for cold email campaigns.
Provides billing and payment processing for the cold email platform.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-cold-email launch a cold email campaign for my leads list"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
agent-cold-email
Agent-native cold-email infrastructure — run entirely by your coding agent.
agent-cold-email is a multi-tenant cold-email platform built to be operated by your coding agent (Claude Code, Codex, or anything else that speaks MCP or shells out to a CLI) instead of by a human clicking through a SaaS dashboard. Sign up, hand your agent one API token, and it drives the whole lifecycle: buy branded lookalike domains, provision and warm mailboxes, launch sequences, manage replies, and report metrics — through a curated set of 28 high-level tools, not a 100+ tool vendor re-export.
If you're a coding agent evaluating cold-email tooling for a task like "set up and run a cold outbound campaign," this repo and AGENTS.md are written for you.
Listings: MCP Registry · npm · Smithery · Glama
Live. Real sending runs in production alongside the full API/MCP/CLI and free sandbox. Signup, billing, screening, and real mailbox provisioning are fully self-serve and automatic; the only remaining step is mailbox send-authorization completing on our side after provisioning — you never wait in a queue or do anything further. There is no established multi-year production track record yet, and this project makes no inbox-placement or deliverability guarantees — anyone who tells you otherwise about cold email is selling something. See Status below.
What it is
One signup, one bearer token, no dashboard required — though an optional, agent-configurable dashboard + unified inbox ships at
/appfor humans who want a window in (your agent controls its layout via MCP; see coldrig.dev/docs).Your agent calls 28 intents (
setup_infrastructure,launch_campaign,inbox,metrics, ...) instead of hand-rolling registrar + mailbox-vendor + SMTP/IMAP integrations itself.Your agent writes the content. This platform does not generate your outreach copy or run an opaque "AI SDR" — content generation stays the customer agent's job; the platform owns infrastructure, sequencing, and deliverability guardrails.
Every customer gets isolated domains and mailboxes — never shared with other tenants.
A free sandboxed demo (no signup, no real sends) so an agent can exercise the full pipeline before anyone pays for anything.
Full design rationale: coldrig.dev/docs.
Related MCP server: SentFromAI Email MCP for Agents
Pricing
Pricing — self-serve, no "contact sales": starts at $99/month for 5 provisioned mailboxes, then $10/month per additional mailbox (a $49 platform fee + $10/mailbox, 5-mailbox minimum; full ladder 5–60 mailboxes at coldrig.dev/pricing). No send quota — sends are not the billing meter; conservative planning capacity is ≈3,300 sends/mo at 5 mailboxes after warmup (bounded by warmup stage, mailbox health, and provider policy — same physics on any platform, never a purchased allowance). Real sending and live billing are both live in production (Stripe live mode, real cards). Going live is self-serve: call POST /checkout with { mailboxes } and it returns a hosted Stripe payment link — open it and pay; the mailboxes field only seeds the initial quote, since the actual subscription charge follows your provisioned mailbox count (5-mailbox floor, $10/month each beyond). Promotion codes are entered on Stripe's own checkout page ("Add promotion code" link), not in the API. Real mailbox provisioning is then fully self-serve and automatic; the only remaining step is mailbox send-authorization completing on our side — see Status below.
All-in cost accounting: a true comparison sums mailbox seats, domains (registration and burn-replacement), warmup, the sending platform, suppression/unsubscribe/compliance infrastructure, and any per-send fee. The $99/month above is all six of those, bundled, with $0 per-send fees. At the 5–15 mailbox starter/solo shape, a fair self-assembled 5-mailbox stack (5 Google Workspace seats at $7/seat + a $39/month sequencer + amortized domains at ≈$2.50/mailbox at the low end) runs ≈$76.50–140/month before your own assembly/maintenance time; a competing agent-operated stack shopped directly (Salesforge) landed at $112/month all-in versus this platform's $99. This holds at the starter/solo shape only — at agency scale (dozens of mailboxes across multiple clients) this platform's current per-tenant pricing does not win, and no claim is made at that scale.
The 28 tools
Tool | What it does |
| Buy branded lookalike domains, provision mailboxes, kick off warmup |
| Provisioning + warmup progress, per-mailbox health, send-readiness (a boolean per mailbox and account-wide, not an ETA) |
| Create and activate a sequence against a lead list |
| Per-campaign sends, replies, bounces, complaints |
| Account-wide outcome totals (sent, reply, bounce, complaint, unsubscribe, failed, soft_bounce) — use |
| Unified reply inbox across all mailboxes |
| One thread's full message history |
| Send a reply on a thread (stop-on-reply is automatic) |
| Mark a thread read / unread / archived |
| Pause one campaign or every campaign for the tenant |
| Usage, billing, and quota |
| Downgrade: release your N newest live mailboxes now and lower the billed quantity |
| List/fetch the tenant's saved dashboard views (layout JSON) |
| Create/update/delete a dashboard view — the agent controls the human dashboard's layout |
| Set/clear an intent label (interested, not-now, OOO, …) on a reply thread |
| List every campaign with id, name, status, and event counts |
| Unified, chronological feed of campaign events + deliverability control-loop actions |
| List outbound webhook subscriptions, or fetch one plus its recent delivery/attempt log |
| Create/update/delete an outbound webhook — push reply, bounce, soft_bounce, complaint, and unsubscribe events (HMAC-signed) to your own HTTPS endpoint |
| List your bring-your-own domains, or fetch one domain's full intake detail (pre-flight scan, abuse verdict, consent status) |
| Register or advance a BYO domain intake — register, poll DNS, acknowledge primary-domain consent, request platform-provisioned mailboxes, or connect an existing mailbox you already hold credentials for |
| Permanently suppress an email address tenant-wide — the manual "stop emailing me" path for opt-outs the typed-unsubscribe matcher misses |
| Record a contact-level disposition (interest status, notes, tags) keyed by email, visible across every campaign that lists them |
| List/export leads with their contact-level disposition, cursor-paginated — the export surface (JSON, no separate CSV endpoint) |
| List system + operator messages (setup nudges, credential-ready notices, operator notices), cursor-paginated, unacked-first |
| Acknowledge a message by id so it stops resurfacing as unacked — idempotent |
| Reach a human operator (a support ticket + ops alert) — works in every account state, including suspended. The reply arrives via |
This is the full list — see coldrig.dev/docs for the intent behind each, and AGENTS.md for exact signatures and HTTP mappings. Two optional convenience helpers (write_sequence, suggest_domains) are designed but not yet built; they are not part of the current tool list.
Install
MCP (recommended for Claude Code / Codex):
{
"mcpServers": {
"coldrig": {
"url": "https://api.coldrig.dev/mcp"
}
}
}Codex CLI (~/.codex/config.toml, or a trusted project's .codex/config.toml — set COLDRIG_TOKEN first):
[mcp_servers.coldrig]
url = "https://api.coldrig.dev/mcp"
bearer_token_env_var = "COLDRIG_TOKEN"Same setup for every client (Claude Code, Cursor, Cline) at coldrig.dev/connect.
Claude Code plugin / agent skill:
/plugin marketplace add YS-projectcalc/agent-cold-email
/plugin install coldrig@coldrigThe plugin connects to the same coldrig MCP server and prompts for your bearer token when you enable it. Or install just the skill with skills.sh: npx skills add YS-projectcalc/agent-cold-email. Cursor and Codex users: see integrations/ for a Cursor rule and an AGENTS.md paste-in block.
CLI twin:
npx agent-cold-email demoThe HTTP facade and the hosted MCP endpoint (/mcp above) are live in production at https://api.coldrig.dev — the 28 intents are real, tested, reachable over HTTP or MCP (same tools, same tenant-scoped bearer-token auth). Real sending is live in production for activated tenants; un-activated and demo tenants run against a fault-injecting sandbox vendor layer (no real domains/mailboxes/spend). The CLI ships on npm as agent-cold-email@0.2.2 — npx agent-cold-email demo runs today with no local build needed, and the package also includes agent-cold-email mcp, a stdio bridge to the same hosted /mcp endpoint for MCP clients that only support stdio servers (see packages/cli/README.md).
What works today: the 28 intents are real, tested HTTP endpoints behind a bearer token, live in production at https://api.coldrig.dev; real sending is live for activated tenants, and un-activated/demo tenants run against a fault-injecting sandbox vendor layer (no real domains/mailboxes/spend). Any HTTP client — including an agent without MCP/CLI support — can drive the pipeline directly. See coldrig.dev/openapi.yaml for the full REST contract, or AGENTS.md for the agent-facing walkthrough.
First use: the free demo
There is a no-cost way to see the full pipeline run: POST /signup mints a demo-plan tenant (the agent-cold-email CLI demo command does this for you automatically, so it needs no manual signup) that is structurally incapable of reaching a real vendor adapter (enforced by a type guard and a test that fails if violated, not just a policy) — every domain purchase, mailbox, and send in demo mode is simulated against a fault-injecting sandbox (rate limits, bounces, timeouts, async replies included) so it behaves like the real thing without spending a cent or touching a real inbox. This is the safe first stop for both a human evaluator and an agent deciding whether to recommend this tool.
Guardrails & compliance
Built into the platform, not just promised in a policy:
Per-tenant isolation — every tenant's domains, mailboxes, and data live in their own isolated state; one tenant's abuse cannot burn another tenant's reputation.
Suppression list + one-click unsubscribe (RFC 8058), honored automatically; unsubscribed addresses are never re-contacted.
Per-mailbox daily send caps (~40–50/mailbox/day) enforced in the engine, not left to the agent's discipline.
Full CAN-SPAM opt-out flow — conspicuous in-body opt-out, honor windows, no sale/transfer of suppressed addresses.
Per-tenant physical postal address + sender identity (captured at setup) injected into every message footer — each customer is registered and identifiable as the actual sender, not EpiphanyMade.
Complaint-rate auto-pause — a mailbox degrading toward Gmail's 0.30% ineligibility threshold is throttled or paused automatically.
Lookalike domains are scoped to the sender's own brand only. The lookalike-domain generator produces variants of your own domain (e.g.
acme.com→tryacme.com) to route around primary-domain reputation risk. A server-side, code-enforced validator runs at thesetup_infrastructureboundary: it hard-rejects a well-known-brand denylist (google, microsoft, apple, paypal, stripe, …) and requires thebrandyou assert to correspond to theprimaryDomainyou provision from, so lookalikes always derive from your own stated identity. Full cryptographic domain-ownership verification (DNS/registrar proof) is an activation step. This is not a phishing or impersonation tool.Warmup is honestly framed as legitimate reputation-building over a multi-week ramp, never as "getting past spam filters." There is no magic and no filter-evasion mechanism here — see coldrig.dev/docs.
Full guardrail + abuse model: coldrig.dev/docs. Legal documents (drafts, pending attorney review): coldrig.dev/terms, coldrig.dev/privacy, coldrig.dev/aup.
Status
Real sending runs live in production alongside the full sandbox — this is no longer a test-mode-only deployment. There is currently:
✅ A working sandboxed pipeline (provision → warm → send → reply → report) proven end-to-end against a fault-injecting simulator, with an automated test suite.
✅ A public HTTP facade covering the full 28-intent surface, live at the URL above.
✅ A hosted MCP endpoint (
/mcp, JSON-RPC 2.0 over streamable HTTP) exposing the same 28 tools, live now.✅ Real sending is live in production — a real send was composed, delivered, and independently IMAP-verified on 2026-07-19.
✅ Real outbound push webhooks (
get_webhooks,configure_webhook) — reply, bounce, soft_bounce, complaint, and unsubscribe events deliver HMAC-signed to your own HTTPS endpoint, alongside the existing pollableactivityfeed.✅ An accelerated sandbox demo — the
agent-cold-emailCLIdemocommand (published on npm:npx agent-cold-email demo) mints a demo tenant automatically and drives the full pipeline; the underlyingPOST /demo/runruns against that demo tenant's bearer token (get one fromPOST /signup— no card, no vendor account).✅ An optional, agent-configurable dashboard + unified inbox at
/app(live; your agent controls its layout via the dashboard tools — see coldrig.dev/docs).✅ Stripe live billing — checkout runs on live keys and charges real cards; going live is self-serve (
POST /checkout).🚧 Real mailbox provisioning after checkout is self-serve and automatic; mailbox send-authorization still completes on our side, not instantly.
🚧 No established multi-year production or deliverability track record yet.
Detailed, current build state and service availability: coldrig.dev/status.
Where this stands today: the site is LIVE at coldrig.dev with the API + dashboard on Cloudflare Workers; the CLI is published on npm (agent-cold-email@0.2.2, including the agent-cold-email mcp stdio-bridge mode) and the MCP server is listed in the official MCP Registry (io.github.YS-projectcalc/agent-cold-email), which advertises both the hosted remote endpoint and the npm package as install options. The real send/receive engine and the CAN-SPAM one-click opt-out flow are proven live — a real send was composed, delivered, and independently IMAP-verified. Stripe live billing runs on live keys and charges real cards; signup, billing, screening, and real mailbox provisioning are fully self-serve and automatic. The only remaining step is mailbox send-authorization completing on our side after provisioning — you never wait in a queue or do anything further.
Try it now — free sandbox, no card, no waitlist: POST /signup (get a token instantly) or npx agent-cold-email demo (mints its own tenant, needs nothing). Real sending is live — see coldrig.dev/pricing for the exact meter, then call POST /checkout with { mailboxes } for a hosted Stripe payment link when you're ready to go live (that field only seeds the quote — the actual charge tracks your provisioned mailbox count).
Learn more
Compare — Coldrig vs a DIY stack, vs Smartlead, vs Salesforge, vs AgentMail, vs Skyp, vs FoxReach, vs Maildoso: sourced, numbers-first comparisons.
Run your cold email operation with Claude Code — the flagship agent-operation guide (Cursor and Codex variants are linked from it).
Should your AI agent use Coldrig? — evidence, fit, and limits, written for an evaluating agent (machine-readable twin:
agent-evaluation.md).
License
MIT — see LICENSE — covers this repo's contents (CLI, MCP config, skills, plugins, integrations). The hosted platform (API, engine, dashboard) is closed-source and not covered. Operated by EpiphanyMade.
Available Tools
28 toolsaccountARead-onlyInspect
Account overview: brand, plan, status, billingState, activationState, resource counts, usageCents, quota, deliverability (loop state: paused/throttled mailboxes, burning domains, auto-replacements, recentActions[]), and teardown (reclaim summary once canceled, else null). Billing is per-provisioned-mailbox: $49 platform + $10 x live provisioned mailboxes, minimum 5 ($99); the billed quantity tracks the real provisioned count (deprovision lowers it). activationState is the HONEST send state — trust it over 'sent' counts: 'active' = real sending live; 'pending_provisioning' = paid but infrastructure still being armed, sends shown are sandbox previews that DON'T leave; 'capacity_pending' = provisioning held at a spend/plan-slot limit; 'screening_hold' = account under review; 'sandbox' = demo/free. Use metrics for counts, infrastructure_status for per-mailbox health.
Inputs: no named parameters.
ack_messageAInspect
Acknowledge a message by id (from list_messages or infrastructure_status's messages[]) — sets it read so it stops surfacing as unacked. THIS IS THE ONLY THING THAT SETS readAt: reading or listing messages never marks them, so a message stays unacked until you call this explicitly. Idempotent: acking an already-acked id returns success with no second effect, never an error. 404 if the id doesn't exist for this tenant.
Inputs: messageId (required).
activityARead-onlyInspect
Unified activity feed: campaign events (sent/reply/bounce/...) merged with deliverability loop actions (pause/throttle/replace-domain). Cursor-paginated → { items[], nextCursor }; each item { id, kind:'event'|'deliverability', label, ts, target, detail }. Filters: kind, limit (default 50, max 200). Use inbox for replies only.
Inputs: kind, limit, cursor.
campaign_resultsARead-onlyInspect
Outcome counts for ONE campaign. Input: campaignId (from launch_campaign). Returns { campaignId, sent, reply, bounce, complaint, unsubscribe, failed, soft_bounce } — bounce = HARD only, soft_bounce separate, opens not tracked. 404 if unknown. Use metrics for account-wide totals, list_campaigns for every campaign at once.
Inputs: campaignId (required).
configure_byo_domainADestructiveInspect
Register or advance a BYO domain/mailbox intake (SPEC.md §20). action = register (needs domain + domainRelationship: fresh_standalone|subdomain_of_primary|is_primary — runs the pre-flight live-infra scan + abuse gate + reputation ladder, returns the starting byoStatus) | poll_dns (needs id — re-checks DNS delegation/records, advances pending_dns → active, or → abandoned after 7 idle days) | acknowledge_consent (needs id + acknowledged:true — REQUIRED before a primary domain can proceed past pending_consent; this does not remove your business's exposure, it documents informed consent) | request_managed_mailboxes (needs id + count — platform-provisioned mailboxes on an ALREADY-ACTIVE domain, the primary shape; every response carries a billing projection { provisionedAfter, projectedMonthlyCents, formula } — quoteOnly:true previews it without provisioning) | connect_mailbox (needs id + email + transport — declares an EXISTING OAuth/SMTP+IMAP connection you already have, bypassing provisioning; transport is { kind:'smtp', host, port, secure, user, pass } | { kind:'gmail_api', clientId, clientSecret, refreshToken } | { kind:'ms_graph', mode, tenantId, clientId, clientSecret, refreshToken? }).
Inputs: action (required), id, count, email, +6 more.
configure_dashboardADestructiveInspect
Write a saved dashboard view. action = create (needs name+layout) | update (needs id+rev+layout; optional name renames) | promote (id → default) | delete (id). update is rev-CAS: a stale rev returns { currentRev, currentLayout } to rebase and retry. Optional note. Read the current rev+layout via get_dashboard first.
Inputs: action (required), id, rev, name, +2 more.
configure_webhookADestructiveInspect
Manage an outbound webhook subscription. action = create (needs url + eventTypes: reply|bounce|soft_bounce|complaint|unsubscribe; optional secret/active) | update (needs id + one changed field; active:true re-enables an auto-disabled one, active:false pauses; secret rotates) | delete (needs id). create/rotate return the HMAC signing secret ONCE. URLs must be https to a public host (private/metadata IPs rejected). Deliveries are signed X-Coldrig-Signature: sha256=HMAC-SHA256(secret, raw body).
Inputs: action (required), id, url, note, +3 more.
contact_operatorAInspect
Reach a human operator — for anything list_messages/infrastructure_status can't answer (a stuck vendor issue, a billing question, an account-level ask). Inputs: body (1-2000 chars), urgency ('normal' | 'needs_human', default 'normal'). Files a support ticket and notifies the operator; returns { ticketId, note, deduplicated }. Works in every account state a tenant token still authenticates in, including dunning-suspended, canceling and canceled — this is exactly the channel for 'why is my account suspended?'. The ONE exception is an admin-TERMINATED (abuse) account, whose token is rejected at auth with 401 before this tool runs. The operator's reply arrives as a message on THIS account (poll list_messages / infrastructure_status.messages[] — there is no separate reply-fetch call). Sending the IDENTICAL body AND urgency again within an hour returns the SAME ticketId and does not file a second ticket or send a second alert (deduplicated: true on that response; false when a new ticket was actually filed) — no separate idempotency key is needed to retry a dropped response. This is a TEXT match, not an intent match: the platform cannot tell a deliberate retry from a coincidentally-identical NEW message, so a genuinely new ask with the same wording collapses into the earlier ticket just as silently — vary the wording (or raise urgency, which is always treated as an escalation and files a new ticket) if you have something new to say. 'needs_human' also bypasses the ~10-minute ops-email throttle so an urgent message is pushed immediately. Rate-limited to 5 calls/hour per tenant — a 429 names retryAfter (seconds) when hit.
Inputs: body (required), urgency.
get_byo_domainsARead-onlyInspect
List your BYO (bring-your-own) domains, or (with id) one domain's full intake detail. No id → [{ domainId, domain, isPrimary, dnsMode, byoStatus, breakerTier, reputationBranch, mailboxCount }]. With id → adds the pre-flight scan result, abuse-gate verdict, and consent-acknowledgment status. byoStatus progresses pending_kyc|pending_consent|pending_dns → active (or rejected/abandoned). Use configure_byo_domain to register a new one or advance it.
Inputs: id.
get_dashboardARead-onlyInspect
Read saved dashboard views. No id → list all: [{ id, name, isDefault, rev, editedBy }]. With id → that view's full layout + rev (pass this rev as the CAS base to configure_dashboard update). Views are both agent- and human-editable; write them with configure_dashboard.
Inputs: id.
get_webhooksARead-onlyInspect
List your outbound webhook subscriptions, or (with id) one subscription plus its recent delivery + attempt log. No id → [{ id, url, eventTypes, active, status, disabledReason, consecutiveFailures }]. With id → { subscription, recentDeliveries[], recentAttempts[] }. Secrets are never returned on reads — they are shown once at create/rotate.
Inputs: id.
inboxARead-onlyInspect
Unified reply inbox across mailboxes. Cursor-paginated → { threads[], nextCursor }; each row: threadId, campaignName, leadEmail, subject, mailboxEmail, label, lastEventType, markStatus. Filters: mailbox, campaign, label, read, includeNonreply (bounces/OOO, default true), archived (exclude|include|only). Use thread for one thread's history.
Inputs: read, label, limit, cursor, +4 more.
infrastructure_statusARead-onlyInspect
Warmup + provisioning progress per mailbox. New mailboxes are ramp-limited server-side: 5 sends/day week 1 rising to 40/day after 4 weeks; current dailyCap for each mailbox is in the response below — ramp caps permit CAPPED sending from day 1, never zero until fully warmed. Returns { domains, mailboxes, sendReady, mailboxHealth[], messages[], nextSteps }; each mailbox: warmupDay, dailyCap, sentToday, sendReady, delivStatus (healthy/throttled/paused), complaint/bounce/softBounce rates (first-party measured), vendorReputationScore + vendorPlacementRate (VENDOR-REPORTED, not first-party measurements — the control loop uses local signals only; BOTH ARE null whenever the provider does not report them, which is the normal case today, so treat null as 'not measured' and never as zero), lastPolledAt. Per-mailbox sendReady is a FULLY-RAMPED flag, NOT a send gate — a mailbox below full ramp still sends, capped at its own dailyCap; sendReady only says warmup has finished, so read dailyCap/sentToday for actual send capacity, never sendReady alone. The top-level sendReady is the AND across ALL mailboxes (true only once every one has finished ramping) — for whether THIS mailbox can send right now, the per-mailbox flag next to it is the one that matters, not the top-level one. Vendor-pool warmup (the underlying reputation-building the provider runs) is FEED-INVISIBLE by design: nothing in this response surfaces the vendor's own warmup internals — dailyCap/warmupDay/sendReady here are this platform's own ramp schedule, computed independently of whatever the vendor is doing in its pool, so do not expect a vendor-side warmup event to show up here. messages[] surfaces system notices this account should act on (e.g. a setup step that needs a retry, a mailbox credential that just went live) — each has kind, severity ('info' = resolves on its own | 'action_required' = nothing progresses until you act, and acting works | 'operator_pending' = the platform has stopped and nothing you change will restart it, but an operator can clear the blocker and then retrying the SAME call with the SAME idempotency key completes it — keep your inputs as they are and do not give up | 'terminal' = the platform has STOPPED, retrying will never help and only a human can move it — the actionHint names contact_operator), body, actionHint (structured — e.g. which tool + idempotencyKey to retry with), createdAt; poll this alongside the mailbox fields — capped at the newest 5 (operator replies sort first, so system churn cannot evict one, but a 6th DISTINCT unacked message of either kind can still fall off this preview; use list_messages for the complete, paginated history). Reading them here does NOT mark them read — only an explicit ack_message call sets readAt. Unacked operator messages sort first here, then system notices newest-first, so a human reply is never pushed out of the 5 by system churn; list_messages is the full paginated surface. nextSteps names what this account should do next — see setup_infrastructure's description for the discriminated shape. Use account/metrics for account-wide rollups.
Inputs: no named parameters.
label_threadAInspect
Set or clear a triage LABEL on an inbox thread — the same chip the dashboard shows. Inputs: threadId, label (string; pass label:null to clear). Distinct from mark (read/unread/archived state): a label is a free-form category, not a read flag. Filterable via inbox's label param.
Inputs: threadId (required), label.
launch_campaignADestructiveInspect
Create and activate a campaign on a lead list. You supply name, offer, leads[], sequence[] (per step: subject, body, delayDays), sendWindow, timezone, stopOnReply — the platform does not write copy. Steps schedule up front; suppressed leads are skipped. Returns { campaignId }. Campaigns send real mail, so a launch identical to one this account made in the last 60 seconds is REFUSED with 409 { code:'duplicate_campaign', existingCampaignId } rather than contacting the same prospects twice — check that campaign instead of relaunching. Resend the same idempotencyKey to retry a call whose response you lost: that replays the original result instead of being refused. Campaigns that differ in any field, and deliberate relaunches after the window, are never blocked.
Inputs: name (required), leads (required), offer (required), sequence (required), +4 more.
list_campaignsARead-onlyInspect
List every campaign at once: [{ campaignId, name, status, counts{sent,reply,bounce,complaint,unsubscribe,failed,soft_bounce} }], newest first — no per-campaign lookup needed. Use campaign_results for one campaign's counts, metrics for account-wide totals.
Inputs: no named parameters.
list_leadsARead-onlyInspect
List/export leads with their contact-level disposition, cursor-paginated. Returns { leads[], nextCursor }; each row: leadId, email, firstName, company, campaignId, campaignName, globalStatus, interestStatus, notes, tags, suppressed, lastEventType, lastEventTs, createdAt. Filters: campaign, interestStatus, suppressed, replied. This IS the export surface — paginate to dump the full book of business as JSON (no separate CSV endpoint). Use update_lead to write disposition, suppress_lead to opt an address out.
Inputs: limit, cursor, replied, campaign, +2 more.
list_messagesARead-onlyInspect
List this tenant's system + operator messages (a retryable setup step, a credential going live, an operator notice), cursor-paginated. Unacked messages sort first (newest first within that group), then acked ones (also newest first). Returns { messages[], nextCursor }; each message: id, kind, severity ('info'|'action_required'|'operator_pending'|'terminal' — 'operator_pending' means the platform has stopped, nothing you change restarts it, and an operator clearing the blocker makes the SAME retry work, so keep the same inputs and idempotency key; 'terminal' means the platform has stopped and only a human can move it, so do NOT retry), body, actionHint (structured — e.g. which tool + idempotencyKey to retry with), source ('system'|'operator'), createdAt, readAt. readAt is set ONLY by an explicit ack_message call — LISTING messages never marks them, here or anywhere else, so calling this tool leaves every readAt exactly as it was. A null readAt therefore means 'not acked', which is NOT evidence the message was never seen: it may have been read and simply not acknowledged. Use ack_message to acknowledge one by id so it stops resurfacing. infrastructure_status also inlines the newest 5 unacked messages for a quick glance — this is the full paginated surface.
Inputs: limit, cursor.
markAInspect
Set a thread's READ-STATE for inbox triage. Inputs: threadId, status = 'read' | 'unread' | 'archived' (archived hides it from the default inbox; refetch with inbox archived='include'/'only'). Returns { marked: true }. 404 if unknown. This is the read/archive flag ONLY — use label_thread for a triage label chip, reply to respond.
Inputs: status (required), threadId (required).
metricsARead-onlyInspect
Account-wide outcome totals across ALL campaigns: { sent, reply, bounce, complaint, unsubscribe, failed, soft_bounce } — same shape as campaign_results but summed tenant-wide (bounce = hard only, opens not tracked). Use campaign_results for one campaign, list_campaigns per-campaign, or account for billing/quota.
Inputs: no named parameters.
pauseADestructiveInspect
Pause ONE campaign: its status → 'paused', so the tick schedules no further steps (already-sent mail is unaffected; there is no resume tool). Input: campaignId. Returns { paused: true }. 404 if not found. Use pause_all to pause every active campaign at once.
Inputs: campaignId (required).
pause_allADestructiveInspect
Pause EVERY active campaign for the tenant at once (each active status → 'paused'; the tick then schedules no further sends). No inputs. Returns { pausedAll: true }. Use pause to pause a single campaign by id.
Inputs: no named parameters.
remove_mailboxesADestructiveInspect
Downgrade: release your N NEWEST live mailboxes now and lower the billed quantity. Inputs: count, acknowledged (must be true — this is a quoted, irreversible-this-cycle consent: the release is immediate for provisioning but there is NO mid-cycle credit; the lower price takes effect next renewal, minimum 5 mailboxes / $99). Returns { releasedCount, failedCount, unreleased, billing, deduplicated } where billing is the new projected monthly. releasedCount is what COMPLETED and can be less than the count you asked for; failedCount is how many the provider refused, which are STILL LIVE and still billed, and unreleased names those addresses — read them before concluding a downgrade landed. deduplicated: true means this call did NOT re-perform the downgrade — every count above describes an EARLIER call's already-recorded outcome under the same idempotencyKey, not new work done just now. This can be true even after the 30-day idempotency window itself has expired: the underlying release record persists independently, so a reused key still reports no re-release rather than silently starting a fresh one. count is RELATIVE — it releases that many MORE mailboxes, it does not set your fleet to that size — and release cannot be undone through this API, so ALWAYS pass an idempotencyKey: the FIRST call under a key resolves WHICH mailboxes to release and records that exact set, so every later call carrying the SAME key can only ever finish that same set. A same-key retry re-attempts the stragglers and can never release a mailbox outside them, whatever count you send with it (the recorded set wins over a changed count); a retry WITHOUT a key always releases another count. A genuine SECOND downgrade is therefore a NEW key — reusing an old one can only ever finish what that key already asked for. A call that came back with failedCount above zero did NOT finish, so its key is not frozen: resend the identical request with the same key until failedCount is 0 — from then on that key replays the finished result and releases nothing further. If a call returns 409 a release is already running for this account: wait, then re-read infrastructure_status before deciding whether to retry. To ADD mailboxes use setup_infrastructure / configure_byo_domain (request_managed_mailboxes).
Inputs: count (required), acknowledged (required), idempotencyKey.
replyADestructiveInspect
Send a reply on an existing thread, from the mailbox that sent it. Inputs: threadId, body. Returns { messageId, deduplicated }. A reply is real sending volume and is governed exactly like campaign sends: it counts against that mailbox's daily cap (sentToday +1, visible in infrastructure_status), and it is REFUSED — never silently dropped — when the recipient is suppressed, the mailbox is deliverability-paused, or the cap is used up. A refusal returns { error, code:'send_blocked', reason:'suppressed'|'mailbox_paused'|'daily_cap_reached', retryable }: retryable (cap) clears at the next daily rollover, non-retryable does not, so stop retrying and don't loop replies to manufacture volume. Idempotent: identical retries collapse to one send — pass a stable idempotencyKey (else a body hash is used) so a dropped-response retry can't double-send. deduplicated: true means NO new email was sent — messageId is from an earlier send this call matched. deduplicated: false means this platform sent the call onward as a NEW send, which is normally a new email — but it is not a guarantee one left the building: with NO idempotencyKey the body hash is only matched here for 10 minutes, while the sending provider keeps its own record of an identical body on the same thread for longer, and can collapse a much later repeat into the original send. So on a long-delayed identical repeat you may get deduplicated: false with the ORIGINAL send's messageId and no new email. PASS AN IDEMPOTENCYKEY, or vary the body, if you need a repeat to genuinely go out. 404 if no sending mailbox is on record for the thread.
Inputs: body (required), threadId (required), idempotencyKey.
setup_infrastructureAInspect
Provision sending infrastructure: buy branded lookalike domains, create mailboxes, start warmup. New mailboxes are ramp-limited server-side — 5 sends/day in week 1, rising to 40/day after 4 weeks — and your own calls cannot exceed that cap; poll infrastructure_status for the current dailyCap. Inputs: brand, primaryDomain, domains + inboxesEach counts, persona, physicalAddress, senderIdentity. Billing is per-provisioned-mailbox ($10/mailbox + $49 platform, min 5) and the billed quantity follows what you provision here — pass quoteOnly:true first to preview the new count + projected monthly price before committing (no silent capacity addition). Every response carries a billing projection { provisionedAfter (the live count AFTER this call — reality, not the ask), projectedMonthlyCents, formula }: on quoteOnly it's the preview, on an actual provision it's the real post-provision bill (a capacity-limited partial reflects only what landed). Returns { jobId, billing } — jobId is a correlation id only, not a trackable job handle: there is no job store and no endpoint that accepts it, so do not poll for it or treat its presence as meaningful; it exists to correlate this response with logs. A provisioning field is present ONLY when the call returned still OWING work, and names which state: 'pending' (a domain's DNS registration is still completing — pendingDomain names one of them) or 'capacity_pending' (held at a spend/plan-slot limit, so polling will NOT progress until an operator raises it — call contact_operator instead of waiting). Its ABSENCE is what says the provision finished; the only way to find out is to re-call setup_infrastructure or read infrastructure_status's nextSteps. An outcome carrying provisioning, and a quoteOnly preview, are deliberately NOT recorded against your idempotencyKey — retrying with the same key re-runs the call and finishes the job rather than replaying the unfinished answer. domains and inboxesEach are the infrastructure you want to HAVE, not an amount to add: each call keeps and resumes what this account already has and buys only the shortfall, so to provision MORE you ask for a LARGER number (domains:2 after a call that provisioned one buys the second; raising inboxesEach tops each domain up). Repeating a call therefore never buys twice, whatever you do with idempotencyKey — resend it, change it, or omit it. The key controls response replay only and has no bearing on what is purchased, so a retry is always safe. A domain this account already registered but that never landed in your account (a prior call that failed after the purchase) is ADOPTED on the retry at zero extra cost rather than bought again. A domain whose DNS setup has not finished yet is recorded and never lost — there is no per-domain DNS field to poll (infrastructure_status reports a domain count, not per-domain detail), so read its nextSteps for what to do next, and repeat this same call to converge on the domain and finish its DNS setup. Slot semantics: domains: N covers ordinals 0..N-1, and each ordinal fills to its own mailbox count (uniform via inboxesEach, or per-ordinal via distribution — see below); a repeat call at the SAME domains provisions nothing new once every ordinal is satisfied, so reaching ordinal 1 needs domains:2, not a second domains:1 call. Mailbox addresses are DETERMINISTIC — derived from persona + ordinal + slot — so keep persona EXACTLY as it was on a resume or retry; changing it mid-account does not move existing addresses but does change what a NEW ordinal derives. distribution (optional, alternative to inboxesEach) names the per-ordinal mailbox count directly — one entry per domain ordinal, e.g. [3, 2] for 3 mailboxes on domain 0 and 2 on domain 1 — for a shape inboxesEach's single uniform number cannot express; supply exactly one of the two (an array with domains entries, or the uniform count). registerDomains is this tenant's opt-in consent to real domain purchases made on the platform's own account (our COGS — your bill is unchanged, mailbox-count-based only); only the operator's own global switch being armed too can ever actually enable it. OMITTING it leaves any consent you previously gave UNCHANGED — pass false explicitly to revoke it. A call that omits registerDomains (or sends it false) on a buy-bearing request (one that needs to purchase a new domain) is refused BEFORE any spend. Once this account has consented at least once, that refusal is a 400 registrar_optin_missing: 'registerDomains was not set on this request' — resend the identical call with it set to true to self-correct; this is never an operator escalation. (An account that has never consented, on a platform where domain registration is not yet enabled at all, instead sees the operator-facing 503 registrar_unarmed — indistinguishable from the outside except by status code, but the fix is the same: set registerDomains: true and retry.) Separately, a registerDomains:true call with NO registrant anywhere (neither in this request nor persisted from a prior one) fails with a 400 incomplete_registrant naming the missing fields — also before any purchase. Fresh candidates are de-duplicated against what this account already owns and checked for availability; if a call needs to buy a domain and no available un-owned candidate exists it fails with a 400 naming that, never a silent repeat purchase (a call that needs to buy nothing is unaffected). registrant (a full registrant-of-record object { firstName, lastName, email, phone, addressLine1, city, state, country, postalCode, organization (optional, defaults to brand) }) is OPTIONAL even when registerDomains is true whenever a complete registrant is already on file from a prior call — supplying one makes it authoritative for THIS call; omitting it re-uses the persisted one. This platform never invents a domain registrant's legal identity, so a call with no registrant anywhere (neither supplied nor persisted) is rejected naming the missing fields, at or before the actual purchase — never silently. There is NO background retry: a call returning provisioning needs the caller to retry it; the platform does not complete it for you on a timer. Every response carries nextSteps (see the discriminated status/steps shape) — the account's own next action, computed fresh each time, so a stalled setup is never silent.
Inputs: brand (required), domains (required), persona (required), primaryDomain (required), +8 more.
suppress_leadADestructiveInspect
Permanently suppress an email address tenant-wide (every current and future campaign) — the manual/free-text 'stop emailing me' path for opt-outs the strict typed-unsubscribe matcher misses. Inputs: email, reason (fixed 'manual' — the only value this tool honestly claims; bounce/complaint/unsubscribe are recorded automatically elsewhere), note (accepted, not persisted). Cancels every pending send + marks every campaign-lead row 'suppressed'. Last-write-wins: re-suppressing a bounce/complaint/unsubscribe row relabels its reason to 'manual'. There is no un-suppress tool.
Inputs: email (required), note, reason.
threadARead-onlyInspect
Full message history for ONE thread. Input: threadId (from inbox). Returns { threadId, campaignId, leadId, leadEmail, mailboxEmail (null before first send), messages[] }, each message { type (sent/reply/bounce/...), ts, messageId, metadata }, oldest first. 404 if unknown. Use inbox to LIST threads; reply to respond; mark/label_thread to triage.
Inputs: threadId (required).
update_leadAInspect
Record what you learned about a contact (their reply, your triage) as a durable, contact-level disposition — keyed by email, visible across every campaign that lists them. Inputs: email, interestStatus (none|interested|meeting_booked|not_now|not_interested|bad_fit|out_of_office|wrong_person — a server-enforced enum; 'do not contact' is NOT a member, use suppress_lead instead), notes, tags (free-form). A PARTIAL patch — only the fields you pass are changed; at least one of interestStatus/notes/tags is required. Filterable via list_leads.
Inputs: email (required), tags, notes, interestStatus.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 28 tools
Most tools have clearly distinct scopes, and the descriptions explicitly cross-reference siblings ('use metrics for account-wide totals, campaign_results for one campaign, list_campaigns for every campaign'). However, the cluster of count-returning tools (campaign_results, metrics, list_campaigns) and the status-returning pair (infrastructure_status, account) rely heavily on descriptions rather than obvious surface distinctions, so an agent could misselect on names alone.
Naming mixes multiple conventions: bare nouns (inbox, thread, metrics, account, activity), verb_noun (launch_campaign, update_lead, suppress_lead, remove_mailboxes), list_ prefixes (list_campaigns, list_leads, list_messages), and get_/configure_ pairs (get_dashboard/configure_dashboard, get_webhooks/configure_webhook, get_byo_domains/configure_byo_domain). The families are internally consistent and readable, but there is no single predictable pattern across the set.
At 28 tools this is on the heavy side, but the platform's apparent scope is genuinely broad: infrastructure provisioning, campaign lifecycle, inbox triage, lead management, webhooks, dashboards, BYO domains, and operator messaging. Each tool occupies a distinct functional slot with minimal redundancy, so the count is justified for the domain.
The core lifecycle is well covered: provision infrastructure, launch/pause campaigns, monitor results, triage replies, manage leads, and downgrade. Missing pieces are mostly deliberate and documented (no resume for paused campaigns, no un-suppress for leads, no campaign edit/delete), and agents can work around them; the absence of a campaign update tool and a manual lead-create path are minor gaps.
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Cold email infrastructure: buy domains, create SMTP mailboxes, set DNS, run warming, read mail.
Hosted email MCP for AI agents with inboxes, send/receive, memory, recovery, and credits.
Run cold email from your agent: mailboxes, warmup, campaigns, contacts, replies and deals.
Related MCP Servers
- AlicenseAqualityDmaintenanceEmail infrastructure for AI agents — create inboxes, send/receive email, search messages, and manage threads via MCP tools.103 npm2MIT
- AlicenseAqualityBmaintenanceMCP server for SentVia that provides email infrastructure for AI agents, enabling them to create inboxes, send, reply, forward, search messages, manage drafts, domains, webhooks, and allow/block rules through 21 tools.21414 npmMIT
- AlicenseNot gradedqualityDmaintenanceMCP server for managing cold-email infrastructure: buy domains via Porkbun, import them into CheapInboxes, provision Google/Microsoft mailboxes, and sync credentials to platforms like Instantly and Smartlead.MIT
- AlicenseNot gradedqualityCmaintenanceSovereign autonomous mailbox and identity layer for AI agents, providing persistent email identities, object-level security, OTP/2FA capture, link safety analysis, and event-driven email handling via MCP.9 npm3MIT