mcpbeat Sign in

Agent Cold Email MCP Server

by ys-projectcalc Your server? Claim it
answering

Agent Cold Email is answering right now. Last checked 11 min ago. 33 installs a week from npm. It exposes 28 tools. Last commit 25 Aug 2026.

Coldrig — cold-email infra run by your agent: 28 MCP tools, live sending, free sandbox. $99/mo.

Installs per day peak 135 · avg 20 · +293% w/w
a month agotoday
Uptime history 47 days of history
47 days agonow
100.0%
Uptime 24h
91 of 91 checks
28
Tools
read from the server
191 ms
Response time
average over 24h
33
Installs / week
npm and PyPI

What changed 24

Every tool that appeared, vanished or quietly changed what it asks for. Recorded since 10 August 2026. No other catalogue keeps this.

20 Aug 6 tool descriptions were rewritten8 times that day configure_webhook, contact_operator, infrastructure_status and 3 more
20 Aug 2 tools changed the parameters they ask for configure_webhook, setup_infrastructure
18 Aug 5 tool descriptions were rewritten7 times that day ack_message, infrastructure_status, list_messages and 2 more
11 Aug a tool appeared contact_operator
10 Aug 3 tool descriptions were rewritten launch_campaign, remove_mailboxes, setup_infrastructure
10 Aug 2 tools appeared ack_message, list_messages
10 Aug a tool changed the parameters it asks for remove_mailboxes
and 4 more, back to 10 August 2026

What the code does

We read the source, 18 h ago · tools taken from the live server · rules 3dff92dd89df

A tool parameter reaches a dangerous call

A value the model can set ends up inside a file or shell call. That is not a flaw by itself — for a terminal server it is the job — but it is where things go wrong when it is not.

A tool parameter reaches a file or shell call configure_webhook.url → pw-scripts/waitlist-form-drive.js:15
  res.end(fs.readFileSync(f));
Capabilities

What this server is able to do. For an MCP server this is often the job itself — a terminal server runs commands because that is what it is for. Listed so you know what you are plugging in, not as an accusation.

Builds a file path from a variable pw-scripts/waitlist-form-drive.js:12
  const f = path.join(ROOT, p);

Is this your server and something here is wrong? Tell us — corrections are free and do not require a plan.

A tool parameter here reaches a dangerous call

That is not a flaw by itself — but it is where things go wrong when it is not the job. We re-read this code on every release. Watch it and you hear from us the day another one appears.

Three servers free · no card

Connect this server

Endpoint below is the one we actually reach during checks — not the one copied from a README. Last verified 11 min ago.

run in your terminal
claude mcp add agent-cold-email --transport http https://agent-cold-email-api.yaakovscher.workers.dev/mcp
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "agent-cold-email": {
      "url": "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
    }
  }
}
~/.codex/config.toml
[mcp_servers.agent-cold-email]
url = "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
.cursor/mcp.json
{
  "mcpServers": {
    "agent-cold-email": {
      "url": "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
    }
  }
}
.vscode/mcp.json
{
  "mcpServers": {
    "agent-cold-email": {
      "url": "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
    }
  }
}

This one needs environment variables set before it will start: AGENT_COLD_EMAIL_API_KEY (Bearer token from POST /signup (free, no card) — or run `npx agent-cold-email demo` first. Optional at startup: without it, `initialize`/`tools/list` still work (unauthenticated introspection), but every `tools/call` fails until a key is set.), AGENT_COLD_EMAIL_BASE_URL (Override the API base URL (default: https://agent-cold-email-api.yaakovscher.workers.dev)). The author declared them in the registry entry; get the values from the project itself.

Available tools 28

Read directly from the server with tools/list, grouped by what they act on. If a tool disappears, we record the date.

configure
configure_byo_domain
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? }).
configure_dashboard
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.
configure_webhook
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).
pause
pause
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.
pause_all
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.
account
account
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.
ack
ack_message
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.
activity
activity
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.
byo
get_byo_domains
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.
campaign
campaign_results
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.
campaigns
list_campaigns
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.
contact
contact_operator
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.
dashboard
get_dashboard
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.
inbox
inbox
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.
infrastructure
infrastructure_status
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.
label
label_thread
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.
launch
launch_campaign
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.
lead
update_lead
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.
leads
list_leads
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.
mailboxes
remove_mailboxes
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).
mark
mark
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.
messages
list_messages
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.
metrics
metrics
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.
reply
reply
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.
setup
setup_infrastructure
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.
suppress
suppress_lead
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.
thread
thread
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.
webhooks
get_webhooks
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.

Endpoints

URLTransportStateLatencyChecked
https://agent-cold-email-api.yaakovscher.workers.dev/mcp streamable-http answering 259 ms 11 min ago

Alternatives to Agent Cold Email

same job, measured the same way
EmailMCP
by setip

AI agent email — 23 tools free to read inbox, 28 paid to send. $9.99/mo. Token auto-provisioned.

56 tools answering
Commune
by shanjai-raj

Email and SMS MCP server for AI agents — 41 tools for inboxes, sending, search, and SMS.

147 installs/wk local only
Email Inbox API + Sending by Sendmux
by sendmux

AI email inbox and sending tools with attachments, search, live events, and webhooks.

135 installs/wk answering
Campaignstack
by campaignstack

Run B2B outreach from your AI agent: 250+ tools for campaigns, leads, LinkedIn and email workflows.

answering
namailu.cz Agent Email
by namailu

namailu.cz gives your AI agent its own email inbox: read and send mail over MCP, with OAuth.

answering
Email
by primitive

Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.

30 tools answering
ARC-Relay
by arc-relay

DMARC-safe email forwarding for agents: aliases, DNS health checks, and MCP tools on your domains.

6 tools answering
MCP Mailtrap
by pijusz

MCP server for the Mailtrap email platform — analytics, sending, contacts, sandbox

30 installs/wk local only

Agent Cold Email — questions

Answers built from our own checks of this server.

What can Agent Cold Email do?
It exposes 28 tools, read directly from the server on our last check. Among them: account, ack_message, activity, campaign_results, configure_byo_domain, configure_dashboard and 22 more. The full list with descriptions is on this page — we take it from the server itself via tools/list, not from a README. How MCP servers expose tools in the first place →
What is Agent Cold Email mostly used for?
Its tools cluster around configure and pause. That is what this server is built to work with — the grouping comes from the actual tool names, not from a category we assigned.
Is Agent Cold Email working right now?
We send a real MCP handshake every 15 minutes. Over the last 24 hours 91 of 91 checks got a reply (100.0%), average response time 191 ms. The bar chart above shows every period we have measured.
How do I connect Agent Cold Email?
Copy the ready config from this page — we generate it for Claude Code, Claude Desktop, Codex, Cursor and VS Code, each with the file path that client actually reads. It is a remote server, so there is nothing to install — the client connects to the address.
Does Agent Cold Email need an API key?
No. Agent Cold Email completed a full MCP handshake with us as an anonymous client and listed its tools without asking for anything. All 28 of them are readable on this page. This is what we observed, not what the docs claim.
How fast is Agent Cold Email?
It answers our handshake in 191 ms on average, which is faster than 68% of all working MCP servers we measure. The comparison comes from our own checks across the whole registry, every 15 minutes.
How many people use Agent Cold Email?
The npm package agent-cold-email was installed 33 times in the last week. Week over week that is +293%. We show installs rather than GitHub stars on purpose: a star is a bookmark, an install is someone actually running it.
Is Agent Cold Email open source?
Yes — it is published under the MIT licence, written in TypeScript and 1 stars on GitHub. The source link is on this page, so you can read exactly what it does with your data before you connect it.