# io.github.YS-projectcalc/agent-cold-email (remote · agent-cold-email-api.yaakovscher.workers.dev)

Agent-run cold-email infra: 24 MCP tools, live sending, free sandbox. $99/mo, concierge go-live.

- Trust score: 67/100 (medium)
- Change this week: +7
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- remote · `agent-cold-email-api.yaakovscher.workers.dev`: 67/100 (this document), [markdown](https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email-api.md), [page](https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email-api)
- npm · `agent-cold-email`: 35/100, [markdown](https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email.md), [page](https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email)

## Channel facts

- Endpoint: `https://agent-cold-email-api.yaakovscher.workers.dev/mcp`
- Transports: `streamable-http`
- Auth: `required`
- Version: `0.2.2`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Endpoint Security**: 71/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation is enforced on tool calls, but the challenge carries no valid RFC 9728 metadata, so a client cannot discover where to get a token.
  - HTTPS check failed: the endpoint is reachable over plaintext HTTP.
  - HSTS check failed: the Strict-Transport-Security header is absent.
  - DNSSEC check failed: this domain isn't protected by DNSSEC.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 60/100
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 4312 tokens (~172/item across 25 items; 25 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 80/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 40% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add --transport http ys-projectcalc-agent-cold-email https://agent-cold-email-api.yaakovscher.workers.dev/mcp
```

### Codex

```toml
[mcp_servers.ys-projectcalc-agent-cold-email]
url = "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ys-projectcalc-agent-cold-email": {
      "type": "remote",
      "url": "https://agent-cold-email-api.yaakovscher.workers.dev/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add ys-projectcalc-agent-cold-email --url https://agent-cold-email-api.yaakovscher.workers.dev/mcp --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  ys-projectcalc-agent-cold-email:
    url: "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
```

### Other

```json
{
  "mcpServers": {
    "ys-projectcalc-agent-cold-email": {
      "type": "http",
      "url": "https://agent-cold-email-api.yaakovscher.workers.dev/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-02 (score 67, +2)

- [security] Tool “setup_infrastructure” rewrote its description, which is the text the model reads
- [security] Tool “infrastructure_status” rewrote its description, which is the text the model reads
- [security] Tool “reply” rewrote its description, which is the text the model reads

### 2026-07-31 (score 65, +4)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-30 (score 61, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-29 (score 61, 0)

- [security] Tool “setup_infrastructure” rewrote its description, which is the text the model reads
- [cosmetic] “setup_infrastructure” added an optional parameter “registerDomains”
- [cosmetic] “setup_infrastructure” added an optional parameter “registrant”

### 2026-07-28 (score 61, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 7. That category is still filling its 30-day observation window: 1 days of observed history at the previous scan, 2 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-27 (score 60, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-26 (score 60)

First indexed and scored.

## MCP tools (25)

### `setup_infrastructure` (~519 tokens)

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). Async — returns { jobId, billing }; poll infrastructure_status for progress. Resend the same idempotencyKey on retry to avoid double-provisioning. `registerDomains` (default false) 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); leave it false/omitted unless you intend real domain spend, and only the operator's own global switch being armed too can ever actually enable it. When `registerDomains` is true, `registrant` is REQUIRED: a full registrant-of-record object { firstName, lastName, email, phone, addressLine1, city, state, country, postalCode, organization (optional, defaults to brand) } — this platform never invents a domain registrant's legal identity, so omitting it rejects the call at the boundary naming the missing fields.

Input parameters:

- `brand` (string, required)
- `domains` (integer, required)
- `idempotencyKey` (string): Optional idempotency key: resend the SAME key when retrying this call so a dropped-response retry is not applied twice (no duplicate campaign/provision/send).
- `inboxesEach` (integer, required)
- `persona` (string, required)
- `physicalAddress` (string, required)
- `primaryDomain` (string, required)
- `quoteOnly` (boolean)
- `registerDomains` (boolean)
- `registrant` (object)
- `senderIdentity` (string, required)

### `infrastructure_status` (~153 tokens)

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. Returns { domains, mailboxes, sendReady, mailboxHealth[] }; each mailbox: warmupDay, dailyCap, sentToday, sendReady, delivStatus (healthy/throttled/paused), complaint/bounce/softBounce rates (first-party measured), vendorReputationScore + vendorPlacementRate (VENDOR-REPORTED approximations, not first-party measurements — the control loop uses local signals only), lastPolledAt. Use account/metrics for account-wide rollups.

### `launch_campaign` (~178 tokens)

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 }. Resend the same idempotencyKey on retry to avoid a duplicate.

Input parameters:

- `idempotencyKey` (string): Optional idempotency key: resend the SAME key when retrying this call so a dropped-response retry is not applied twice (no duplicate campaign/provision/send).
- `leads` (array, required)
- `name` (string, required)
- `offer` (string, required)
- `sendWindow` (object)
- `sequence` (array, required)
- `stopOnReply` (boolean)
- `timezone` (string)

### `campaign_results` (~95 tokens)

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.

Input parameters:

- `campaignId` (string, required): The campaign id returned by launch_campaign.

### `metrics` (~73 tokens)

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.

### `inbox` (~148 tokens)

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.

Input parameters:

- `archived` (string)
- `campaign` (string)
- `cursor` (string)
- `includeNonreply` (boolean)
- `label` (string)
- `limit` (integer)
- `mailbox` (string)
- `read` (boolean)

### `thread` (~113 tokens)

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.

Input parameters:

- `threadId` (string, required): The thread id, e.g. from inbox() or campaign events.

### `reply` (~278 tokens)

Send a reply on an existing thread, from the mailbox that sent it. Inputs: threadId, body. Returns { messageId }. 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. 404 if no sending mailbox is on record for the thread.

Input parameters:

- `body` (string, required)
- `idempotencyKey` (string): Optional idempotency key: resend the SAME key when retrying this call so a dropped-response retry is not applied twice (no duplicate campaign/provision/send).
- `threadId` (string, required): The thread id, e.g. from inbox() or campaign events.

### `mark` (~121 tokens)

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.

Input parameters:

- `status` (string, required)
- `threadId` (string, required): The thread id, e.g. from inbox() or campaign events.

### `pause` (~81 tokens)

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.

Input parameters:

- `campaignId` (string, required): The campaign id returned by launch_campaign.

### `pause_all` (~53 tokens)

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` (~204 tokens)

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.

### `remove_mailboxes` (~128 tokens)

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, quote } where quote is the new projected monthly. To ADD mailboxes use setup_infrastructure / configure_byo_domain (request_managed_mailboxes).

Input parameters:

- `acknowledged` (boolean, required)
- `count` (integer, required)

### `get_dashboard` (~99 tokens)

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.

Input parameters:

- `id` (string): Omit to list every saved view (summary); pass a view id for its full layout + rev.

### `configure_dashboard` (~205 tokens)

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.

Input parameters:

- `action` (string, required)
- `id` (string): Required for update/promote/delete.
- `layout` (object): Required for create/update.
- `name` (string): Required for create. Optional for update — pass it to rename the view; omit to leave the name unchanged.
- `note` (string): Optional human-readable note recorded alongside this edit (edited_by_note).
- `rev` (integer): Required for update — the rev this edit is based on; stale vs. the view's CURRENT rev returns a structured conflict with currentRev/currentLayout to rebase onto.

### `label_thread` (~102 tokens)

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.

Input parameters:

- `label`
- `threadId` (string, required): The thread id, e.g. from inbox() or campaign events.

### `list_campaigns` (~69 tokens)

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.

### `activity` (~108 tokens)

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.

Input parameters:

- `cursor` (string)
- `kind` (string)
- `limit` (integer)

### `get_webhooks` (~109 tokens)

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.

Input parameters:

- `id` (string): Omit to list every subscription; pass an id for that subscription plus its recent delivery + attempt log.

### `configure_webhook` (~268 tokens)

Manage an outbound webhook subscription. action = create (needs url + eventTypes: reply|bounce|soft_bounce|complaint; 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).

Input parameters:

- `action` (string, required)
- `active` (boolean): Optional. On update, active:true re-enables an auto-disabled subscription; active:false pauses delivery.
- `eventTypes` (array): Required for create: which events to push (reply | bounce | soft_bounce | complaint).
- `id` (string): Required for update/delete.
- `note` (string): Ignored placeholder for symmetry; webhooks record no provenance note.
- `secret` (string): Optional signing secret (>=16 chars). Omit on create to have one generated; pass on update to rotate.
- `url` (string): Required for create. HTTPS endpoint; private/link-local/metadata IPs are rejected.

### `get_byo_domains` (~157 tokens)

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.

Input parameters:

- `id` (string): Omit to list every BYO domain; pass an id for that domain's full intake detail (scan result, abuse verdict, consent status).

### `configure_byo_domain` (~563 tokens)

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

Input parameters:

- `acknowledged` (boolean): Required (must be true) for acknowledge_consent — SPEC.md §20.4's separate, unbundled risk acknowledgment.
- `action` (string, required)
- `count` (integer): Required for request_managed_mailboxes — how many platform-provisioned mailboxes to attach.
- `domain` (string): Required for register.
- `domainRelationship` (string): Required for register: fresh_standalone | subdomain_of_primary | is_primary.
- `email` (string): Required for connect_mailbox — the existing mailbox address.
- `id` (string): Required for poll_dns/acknowledge_consent/request_managed_mailboxes/connect_mailbox — the domainId from register.
- `personaSlug` (string): Optional for request_managed_mailboxes — defaults to a slug of the domain.
- `quoteOnly` (boolean): Optional for request_managed_mailboxes — true previews the new mailbox count + projected monthly price WITHOUT provisioning (SPEC §18 quote-before-add).
- `transport`: Required for connect_mailbox — { kind: 'smtp', host, port, secure, user, pass } | { kind: 'gmail_api', clientId, clientSecret, refreshToken } | { kind: 'ms_graph', mode: 'delegated'|'app_only', tenan…

### `suppress_lead` (~154 tokens)

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.

Input parameters:

- `email` (string, required)
- `note` (string)
- `reason` (string)

### `update_lead` (~169 tokens)

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.

Input parameters:

- `email` (string, required)
- `interestStatus` (string)
- `notes` (string)
- `tags` (array)

### `list_leads` (~165 tokens)

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.

Input parameters:

- `campaign` (string)
- `cursor` (string)
- `interestStatus` (string)
- `limit` (integer)
- `replied` (boolean)
- `suppressed` (boolean)

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email-api#diagnostics

## Score history

- 2026-08-03: 67
- 2026-08-02: 67
- 2026-08-01: 65
- 2026-07-31: 65
- 2026-07-30: 61
- 2026-07-29: 61
- 2026-07-28: 61
- 2026-07-27: 60
- 2026-07-26: 60

## Links

- Remote endpoint: https://agent-cold-email-api.yaakovscher.workers.dev/mcp
- Authorisation metadata: https://agent-cold-email-api.yaakovscher.workers.dev/.well-known/oauth-protected-resource/mcp
- Repository: https://github.com/YS-projectcalc/agent-cold-email
- Website: https://coldrig.dev/
- Changelog RSS feed: https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email-api/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email-api/changelog.json
- HTML version of this page: https://verifymcp.io/servers/ys-projectcalc-agent-cold-email/agent-cold-email-api
