protogrid

Search / io.github.YS-projectcalc/agent-cold-email

agent-cold-email

R2· OAuth, once

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

v0.2.5 · active · repository · website · descriptor JSON · versions

An agent can connect after a one-time OAuth consent by a human.Remote endpoint behind OAuth 2. Tokens stay with the agent; the registry only exposes the authorization metadata.
Quality63needs work
Trust96of 100
Uptime, 30 days94%
Latency p50441 ms

Connect

8 clients · secrets stay placeholders
{
  "mcpServers": {
    "agent-cold-email": {
      "type": "http",
      "url": "https://api.coldrig.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${AGENT_COLD_EMAIL_TOKEN}"
      }
    }
  }
}
claude mcp add --transport http agent-cold-email https://api.coldrig.dev/mcp --header "Authorization: Bearer ${AGENT_COLD_EMAIL_TOKEN}"
[mcp_servers.agent-cold-email]
url = "https://api.coldrig.dev/mcp"
bearer_token_env_var = "AGENT_COLD_EMAIL_TOKEN"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "agent-cold-email": {
      "type": "remote",
      "url": "https://api.coldrig.dev/mcp",
      "enabled": true,
      "headers": {
        "Authorization": "Bearer {env:AGENT_COLD_EMAIL_TOKEN}"
      }
    }
  }
}
{
  "mcpServers": {
    "agent-cold-email": {
      "type": "http",
      "url": "https://api.coldrig.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${AGENT_COLD_EMAIL_TOKEN}"
      }
    }
  },
  "deeplink": "cursor://anysphere.cursor-deeplink/mcp/install?name=agent-cold-email&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vYXBpLmNvbGRyaWcuZGV2L21jcCIsImhlYWRlcnMiOnsiQXV0aG9yaXphdGlvbiI6IkJlYXJlciAke0FHRU5UX0NPTERfRU1BSUxfVE9LRU59In19"
}
{
  "servers": {
    "agent-cold-email": {
      "type": "http",
      "url": "https://api.coldrig.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${AGENT_COLD_EMAIL_TOKEN}"
      }
    }
  }
}
{
  "mcpServers": {
    "agent-cold-email": {
      "httpUrl": "https://api.coldrig.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${AGENT_COLD_EMAIL_TOKEN}"
      }
    }
  }
}
extensions:
  "agent-cold-email":
    enabled: true
    name: "agent-cold-email"
    type: streamable_http
    uri: "https://api.coldrig.dev/mcp"
    headers:
      "Authorization": "Bearer ${AGENT_COLD_EMAIL_TOKEN}"
    timeout: 300

Placeholders to fill from your own secret store: ${AGENT_COLD_EMAIL_TOKEN}. The registry never accepts secret values.

Quality 63 / 100needs work

protocoln/a · weight 25
  • n/a
    protocol.modern, protocol.stateless, protocol.transport, protocol.list_ttlno remote answered a protocol handshake (not probed yet, unreachable, or local-only)
authorization83 · weight 15
  • pass
    auth.prmProtected Resource Metadata (RFC 9728) published
  • pass
    auth.as_metadataauthorization server metadata reachable
  • warn
    auth.cimdonly Dynamic Client Registration, deprecated in 2026-07-28 in favour of CIMD
  • n/a
    auth.secret_in_urlno templated URL
tool hygiene71 · weight 30
  • pass
    tools.descriptionsevery tool has a description
  • warn
    tools.description_length3 tools have descriptions over 1,024 characters, which crowds the context
  • pass
    tools.schemasevery tool has an object input schema
  • pass
    tools.annotations34 of 34 tools declare readOnlyHint or destructiveHint
  • fail
    tools.directory_hintsno tool declares all four of readOnlyHint, destructiveHint, idempotentHint and openWorldHint; missing: configure_byo_domain (readOnlyHint, idempotentHint, openWorldHint); suppress_lead (readOnlyHint, idempotentHint, openWorldHint); resume (readOnlyHint, idempotentHint, openWorldHint); …
  • warn
    tools.token_costabout 15,555 tokens to load every tool
  • pass
    tools.api_dumptools are not a one-to-one API dump
stability25 · weight 15
  • warn
    stability.changes36 tool changes in 30 days
  • fail
    stability.rug_pull6 tools kept their name but changed most of their description in 30 days; review before trusting them
dependenciesn/a · weight 15
  • n/a
    deps.known_vulns, deps.mcp_sdk_version, deps.resolvabledeps.dev does not know [email protected]

34 tools, about 15,555 tokens to load them all · tool set 1858047549e0 · tools last changed 2026-10-14 · checked 2026-10-20 15:57 UTC

Tool changes

  • disconnect_appadded

    Disconnect one app connected with "Sign in with Coldrig". Input: id from get_connected_apps. The app is signed out on its next call and cannot refresh its access; webhooks it created keep running until you delete them (configure_webhook). Disconnecting the app that is making this call is allowed: this call still answers, then that app is signed out. Unknown id: not found. Returns { disconnected: true }.

  • get_connected_appsadded

    List the apps connected to this account with "Sign in with Coldrig" (for example ChatGPT or Claude): [{ id, name, redirectHost, connectedAt }]. name is what the app called itself and is not verified; redirectHost is where its sign-in returned; connectedAt is when it connected (epoch ms). API keys are not listed. Read-only.

  • plan_campaignchangeddescription
    before

    Plan a dated outreach campaign (for example a fundraising drive) and get its price. Read-only: nothing is booked, charged or changed. Inputs: contacts, campaignStart (first campaign day, YYYY-MM-DD, UTC), campaignDays, bookingDate (the day you would book, normally today), and optionally domainsAlreadyHeld (default 0; infrastructure_status shows `domains`) and listIsOwnOptIn (your own opted-in donors; default false). Returns { mailboxes, domainsToBuy, bookBy, advanceStartOn, safeReach, priceCents, warnings[] }. safeReach is how many contacts the warm-up ramp can safely reach if you book on bookingDate; the price is for that many. warnings: over_60_mailboxes (above the 60-address self-serve ceiling), late_booking (safeReach is below contacts), own_list_cheaper_on_esp (an opted-in list may cost less on an email-service provider). A bookingDate after the last campaign day is refused 400. Campaign checkout and booking are not available yet: to book, contact support (contact_operator).

    after · 99% of words in common

    Plan a dated outreach campaign (for example a fundraising drive) and get its price. Read-only: nothing is booked, charged or changed. Inputs: contacts, campaignStart (first campaign day, YYYY-MM-DD, UTC), campaignDays, bookingDate (the day you would book, normally today), and optionally domainsAlreadyHeld (default 0; infrastructure_status shows `domains`) and listIsOwnOptIn (your own opted-in donors; default false). Returns { mailboxes, domainsToBuy, bookBy, advanceStartOn, safeReach, priceCents, warnings[] }. safeReach is how many contacts the warm-up ramp can safely reach if you book on bookingDate; the price is for that many. warnings: over_60_mailboxes (above the 60-address self-serve ceiling), late_booking (safeReach is below contacts), own_list_cheaper_on_esp (an opted-in list may cost less on an email-service provider). A bookingDate after the last campaign day is refused 400. Campaign checkout and booking are not available: to book, contact support (contact_operator).

  • suppress_leadchangeddescription
    before

    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.

    after · 99% of words in common

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

  • accountchangeddescription
    before

    Account overview: brand, plan, status, vertical ('crowdfunding' or null — set at signup), 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 on the live provisioned count: $49 platform + per mailbox, graduated: 1-5 at $10, 6-20 at $8, 21+ at $7; 5-mailbox minimum ($99 at 5); 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 limit; 'screening_hold' = account under review; 'sandbox' = demo/free. Use metrics for counts, infrastructure_status for per-mailbox health.

    after · 98% of words in common

    Account overview: brand, plan, status, vertical ('crowdfunding' or null — set at signup), 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 on the live provisioned count: $49 platform + per mailbox, graduated: 1-5 at $10, 6-20 at $8, 21+ at $7; 5-mailbox minimum ($99 at 5); the billed quantity tracks the real provisioned count (deprovision lowers it). activationState is the actual 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 limit; 'screening_hold' = account under review; 'sandbox' = demo/free. Use metrics for counts, infrastructure_status for per-mailbox health.

  • resumeadded

    Resume ONE paused campaign: its status → 'active'. Input: campaignId. Returns { resumed: true }. Only a paused campaign of this account can be resumed: 404 if unknown, refused (400) if it is not paused, is past its end date, or is a trial campaign on an account that has gone live. On a paid account, resuming sends nothing by itself: the held steps go out through the normal schedule, inside the send window and the daily caps. A step whose time passed while paused goes out at the next allowed slot, one step at a time per lead: the step after it still waits its own delayDays from when that one actually sent, so a pause never puts two steps in one day. Leads stopped on their own stay stopped: stopped with pause leadEmails, replied, unsubscribed, bounced or suppressed. Steps a campaign lost while it was paused before this tool existed are not restored.

  • add_leadsadded

    Add contacts to a running campaign. Inputs: campaignId, leads[] (same shape as launch_campaign: email, optional firstName, company, vars), listSource (REQUIRED on a crowdfunding account). Only an active or paused campaign of this account (404 if unknown; 400 if past its end date). Each new contact starts at step 1 on the campaign's own schedule, under every launch rule: a {{firstName}} or {{key}} the sequence uses must have a value on every new contact, or the call is refused (400) and nothing is added. Skipped and counted, never an error: an address already in this campaign (unchanged), an address on your suppression list (stored as suppressed, never emailed), an address repeated in the request. Adding never resumes a paused campaign; repeating the call adds nothing; a campaign holds at most 5000 contacts. A trial account can launch up to 5 test campaigns in any 24 hours, each holding up to 100 contacts, which is enough for a small test. Both are limits of the trial, not of Coldrig: going live removes them. Nothing in a campaign launched on the trial is ever really sent: when the account goes live, the emails it still has waiting are canceled and it cannot be resumed or added to, so launch a new campaign to send real email. People who already got this email are skipped: a contact is left out, never stored or emailed, when another campaign of this account has sent them, or still has waiting for them, an email with the same subject and body as this campaign's first email, as written before {{tokens}} are filled in (spacing differences do not count). A trial's test email counts only while the account is still on the trial, and the built-in demo campaign never counts. alreadyEmailed in the answer is how many were skipped (anyone also on your suppression list is counted there, not in suppressed): tell the person in plain words, for example "skipped 12 people who already got this email". To include them, send includeAlreadyEmailed: true on the launch itself (previewOnly: true shows the count first), or after a launch add them to that same campaign with add_leads and includeAlreadyEmailed: true. Never launch the same list again to include them: that emails everyone who was not skipped a second time. Naming a repeat with repeatOf includes them too. A launch on which everyone would be skipped is refused (400) and nothing is created; an add of contacts on which everyone is skipped adds no one, with no error. A launch, or an add of contacts, is refused with 409 duplicate_campaign when it would leave a campaign with exactly the same set of contact addresses and the same subjects and bodies in the same step order as another campaign of this account, however long ago that one was launched and whether it is running, paused, ended or finished, so the same people are not sent the same emails twice. repeatOf states a deliberate repeat, exactly as on launch_campaign. Returns { campaignId, added, suppressed, alreadyInCampaign, alreadyEmailed, duplicatesRemoved, contacts } — contacts is the campaign's total afterwards.

  • list_leadschangeddescription
    before

    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.

    after · 80% of words in common

    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, threadId (that lead's conversation: pass it to thread for the lead's timeline; null if nothing was scheduled). 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.

  • list_campaignschangeddescription
    before

    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.

    after · 97% of words in common

    List every campaign at once: [{ campaignId, name, status, counts{sent,reply,bounce,complaint,unsubscribe,failed,soft_bounce,auto_reply} }], newest first — no per-campaign lookup needed. Use campaign_results for one campaign's counts, metrics for account-wide totals.

  • pause_allchangeddescription
    before

    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.

    after · 56% of words in common

    Pause EVERY active campaign for the tenant at once (each active status → 'paused'; nothing more in them sends, and their remaining steps are held). No inputs. Returns { pausedAll: true }. Use pause to pause a single campaign by id, and resume (one call per campaign) to continue one.

Showing the latest 10. Full history: list_changes · trend: quality JSON.

Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed. How it is computed.

Trust 96 / 100

provenance90
liveness98
freshness100
hygiene100

+repository +namespace-matches-repo +website +reachable +uptime-94% +updated-25d-ago

Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed. How it is computed.

Dependencies

[email protected] npmthis version is not known to the dependency resolver

Dependency graphs from deps.dev; advisories from OSV.dev, including the GitHub Advisory Database and the PyPI Advisory Database; all CC BY 4.0. The graph is what a clean install of this version resolves today; nothing was installed or run. How it is checked.

Endpoints

remoteauthreachableuptime 30dp50protocollast ok
https://api.coldrig.dev/mcp streamable-httpoauth2 AS: https://api.coldrig.devyes94%441 ms–2026-10-20
packageregistryversionruntimesecrets
agent-cold-emailnpm0.2.2–AGENT_COLD_EMAIL_API_KEY

Tools (34)

accountread-onlyAccount overview: brand, plan, status, vertical ('crowdfunding' or null — set at signup), 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 on the live provisioned count: $49 platform + per mailbox, graduated: 1-5 at $10, 6-20 at $8, 21+ at $7; 5-mailbox minimum ($99 at 5); the billed quantity tracks the real provisioned count (deprovision lowers it). activationState is the actual 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 limit; 'screening_hold' = account under review; 'sandbox' = demo/free. Use metrics for counts, infrastructure_status for per-mailbox health.
ack_messageAcknowledge 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.
activityread-onlyUnified 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.
add_leadsdestructiveAdd contacts to a running campaign. Inputs: campaignId, leads[] (same shape as launch_campaign: email, optional firstName, company, vars), listSource (REQUIRED on a crowdfunding account). Only an active or paused campaign of this account (404 if unknown; 400 if past its end date). Each new contact starts at step 1 on the campaign's own schedule, under every launch rule: a {{firstName}} or {{key}} the sequence uses must have a value on every new contact, or the call is refused (400) and nothing is added. Skipped and counted, never an error: an address already in this campaign (unchanged), an address on your suppression list (stored as suppressed, never emailed), an address repeated in the request. Adding never resumes a paused campaign; repeating the call adds nothing; a campaign holds at most 5000 contacts. A trial account can launch up to 5 test campaigns in any 24 hours, each holding up to 100 contacts, which is enough for a small test. Both are limits of the trial, not of Coldrig: going live removes them. Nothing in a campaign launched on the trial is ever really sent: when the account goes live, the emails it still has waiting are canceled and it cannot be resumed or added to, so launch a new campaign to send real email. People who already got this email are skipped: a contact is left out, never stored or emailed, when another campaign of this account has sent them, or still has waiting for them, an email with the same subject and body as this campaign's first email, as written before {{tokens}} are filled in (spacing differences do not count). A trial's test email counts only while the account is still on the trial, and the built-in demo campaign never counts. alreadyEmailed in the answer is how many were skipped (anyone also on your suppression list is counted there, not in suppressed): tell the person in plain words, for example "skipped 12 people who already got this email". To include them, send includeAlreadyEmailed: true on the launch itself (previewOnly: true shows the count first), or after a launch add them to that same campaign with add_leads and includeAlreadyEmailed: true. Never launch the same list again to include them: that emails everyone who was not skipped a second time. Naming a repeat with repeatOf includes them too. A launch on which everyone would be skipped is refused (400) and nothing is created; an add of contacts on which everyone is skipped adds no one, with no error. A launch, or an add of contacts, is refused with 409 duplicate_campaign when it would leave a campaign with exactly the same set of contact addresses and the same subjects and bodies in the same step order as another campaign of this account, however long ago that one was launched and whether it is running, paused, ended or finished, so the same people are not sent the same emails twice. repeatOf states a deliberate repeat, exactly as on launch_campaign. Returns { campaignId, added, suppressed, alreadyInCampaign, alreadyEmailed, duplicatesRemoved, contacts } — contacts is the campaign's total afterwards.
campaign_resultsread-onlyOutcome counts for ONE campaign. Input: campaignId (from launch_campaign). Returns { campaignId, sent, reply, bounce, complaint, unsubscribe, failed, soft_bounce, auto_reply } — event counts, bounce = HARD only, soft_bounce separate, opens not tracked; auto_reply = automatic replies such as out-of-office, which are NOT replies (never in reply, replied, replyRate or positiveReplies; they never stop a sequence) — plus unique-contact numbers: contacts (leads in the campaign), contacted (leads sent at least once), replied (leads that replied), replyRate (replied / contacted, 0..1), positiveReplies (replied leads marked interested or meeting_booked via update_lead), and perStep[] of { step, sent, contacted, replied, bounce, unsubscribe } ordered by step (a reply, bounce or unsubscribe counts toward the lead's latest sequence step sent before it). 404 if unknown. To see each contact's own timeline use list_leads with campaign, then thread. Use metrics for account-wide totals, list_campaigns for every campaign at once.
configure_byo_domaindestructiveRegister or advance a BYO domain/mailbox intake. action = register (needs domain + domainRelationship: fresh_standalone|subdomain_of_primary|is_primary — runs pre-flight scan + abuse gate + reputation ladder, returns starting byoStatus) | poll_dns (needs id — re-checks DNS delegation, 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; documents informed consent, does not remove exposure) | request_managed_mailboxes (needs id + count — platform-provisioned mailboxes on an ALREADY-ACTIVE domain; every response carries a billing projection { provisionedAfter, projectedMonthlyCents, formula }; quoteOnly:true previews without provisioning) | connect_mailbox (needs id + email + transport — declares an EXISTING OAuth/SMTP+IMAP connection, bypassing provisioning; transport is smtp | gmail_api | ms_graph, each with its own credential fields).
configure_dashboarddestructiveWrite 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_webhookdestructiveManage 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).
contact_operatorReach a human operator for anything list_messages/infrastructure_status cannot 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 ticket, notifies the operator; returns { ticketId, note, deduplicated }. Works in every account state a token authenticates in (dunning-suspended, canceling, canceled) — admin-terminated is the one exception, rejected at auth. The reply arrives as a message on THIS account (poll list_messages/infrastructure_status — no reply-fetch call). Sending the IDENTICAL body+urgency again within an hour returns the SAME ticketId, no second ticket/alert (`deduplicated: true`). TEXT MATCH, NOT AN INTENT MATCH: a genuinely new message with identical wording collapses the same way, silently — vary the wording (or raise urgency, always a new ticket). `needs_human` bypasses the ~10-min ops-email throttle. Rate-limited to 5/hour/tenant — a 429 names retryAfter (seconds).
disconnect_appdestructiveDisconnect one app connected with "Sign in with Coldrig". Input: id from get_connected_apps. The app is signed out on its next call and cannot refresh its access; webhooks it created keep running until you delete them (configure_webhook). Disconnecting the app that is making this call is allowed: this call still answers, then that app is signed out. Unknown id: not found. Returns { disconnected: true }.
get_byo_domainsread-onlyList 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.
get_connected_appsread-onlyList the apps connected to this account with "Sign in with Coldrig" (for example ChatGPT or Claude): [{ id, name, redirectHost, connectedAt }]. name is what the app called itself and is not verified; redirectHost is where its sign-in returned; connectedAt is when it connected (epoch ms). API keys are not listed. Read-only.
get_dashboardread-onlyRead 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.
get_webhooksread-onlyList 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.
inboxread-onlyUnified reply inbox across mailboxes. Cursor-paginated → { threads[], nextCursor }; each row: threadId, campaignName, leadEmail, subject, mailboxEmail, label, lastEventType, markStatus. Filters: mailbox, campaign, label, read, includeNonreply (threads whose last event is a bounce, complaint or automatic reply such as out-of-office; default true), archived (exclude|include|only). Use thread for one thread's history.
infrastructure_statusread-onlyWarmup + provisioning progress per mailbox. Returns { domains, mailboxes, sendReady, mailboxHealth[], messages[], nextSteps }. New mailboxes ramp-limit server-side: 5 sends/day week 1, 40/day week 4 — dailyCap/sentToday show real capped sending from day 1, never zero. Per-mailbox `sendReady` is a FULLY-RAMPED flag, NOT a send gate — below-ramp mailboxes still send, capped at their own dailyCap; read dailyCap/sentToday for capacity. Top-level `sendReady` is the AND across ALL mailboxes (true once every one is ramped) — for one mailbox's capacity use its own flag. Each mailbox carries delivStatus (healthy/throttled/paused), complaint/bounce/softBounce rates; vendorReputationScore/vendorPlacementRate are VENDOR-REPORTED, null when unmeasured. Vendor-pool warmup is FEED-INVISIBLE by design. `messages[]` previews 5 unacked notices (severities: list_messages); reading never marks read. `nextSteps` names the next action (see setup_infrastructure). Use account/metrics for rollups.
label_threadSet 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_campaigndestructiveCreate and activate a campaign on a lead list. You supply name, offer, leads[], sequence[] (per step: subject, body, delayDays — subject is REQUIRED on step 1; a LATER step may omit it to keep step 1's subject line [recommended], which resolves at launch to "Re: " + step 1's subject (unchanged if that already starts with "Re: "), or set one explicitly to change only that step's subject line — every later step is still sent In-Reply-To step 1, though some mail clients display a changed subject as a separate conversation), and optionally timezone, sendWindow, stopOnReply, listSource (one sentence on where the list came from — REQUIRED on a crowdfunding account, where a missing or "unknown" source is refused 400) — the platform does not write copy. Each step's `step` number must be unique within the sequence; a duplicate step number is refused (400) naming it. Subjects and bodies may use {{firstName}} and {{company}} (filled from each lead; firstName and company are both optional) plus your own {{key}} for any key every lead carries in its optional `vars` map (word-keys to one-line strings, e.g. vars:{ icebreaker:"..." }; a key may not be firstName or company). A value is inserted once as plain text. A launch is refused (400), naming the token and how many leads lack it, if a subject or body uses {{firstName}} or a {{key}} that any lead has no non-empty value for — no fallback word is invented; it is also refused if any subject or body uses a {{…}} that is not one word, a single-brace form of a known token such as {firstName} or {company} (a typo for the double-brace form — it would otherwise reach the recipient unsubstituted), or a run of 3 or more consecutive `{` or `}` anywhere (e.g. {{{firstName}}} or a lopsided run like {{{firstName}} — it would otherwise reach the recipient with stray braces attached). The send window is evaluated in `timezone` (IANA, e.g. America/New_York; default UTC) — set it to your recipients' zone. sendWindow is { startHour, endHour, days? }: integer hours 0-23, endHour INCLUSIVE (the last hour a send may start in), days 0=Sunday … 6=Saturday. Each omitted field (or all of them, by omitting sendWindow) gets the platform's RECOMMENDED default {"startHour":8,"endHour":16,"days":[1,2,3,4,5]} — Monday-Friday business hours — except on a sandbox account, where omitted fields are open every hour of every day until it upgrades and they take that default; the decision is yours, e.g. include 0 and 6 in days to send on weekends. Setting exactly ONE of startHour/endHour combines it with the recommended default for the OTHER (08/16, including on a sandbox account, since that value re-resolves to the same default on upgrade) — if that combination would wrap the window past midnight (start > end), the launch is refused; set both explicitly, including for a deliberate overnight window such as {startHour:22, endHour:6}. Step 1 goes out from the least-loaded mailbox; each later step goes out delayDays after the previous step ACTUALLY sent, from the SAME mailbox, with In-Reply-To/References set to step 1's Message-ID — it waits while that mailbox is at its daily cap, and the lead's remaining steps are cancelled (a 'failed' event says why) once that mailbox is paused or released, since neither ever lifts on its own, or when a paid account's thread went out from a connected BYO mailbox, which this build never sends from; a step whose previous step never went out is skipped. Lead addresses are trimmed and lower-cased, and an address repeated in leads[] is kept once (its first row). Suppressed leads are skipped. Returns { campaignId, sendWindow, timezone, contacts, suppressed, duplicatesRemoved, alreadyEmailed, endByShortfall, warning, nextSteps } — sendWindow and timezone are what actually applied; contacts is how many leads were stored, suppressed how many of those are on your suppression list (never scheduled), duplicatesRemoved the repeated rows dropped, alreadyEmailed the leads left out because they already got this email. With an endBy, endByShortfall is at least how many of the contacts this campaign will email cannot get their first email before endBy on this account's current mailboxes (a guaranteed minimum: each mailbox's daily cap on each day, warmup and throttle included, on the sendWindow's days and hours in timezone, and on a paid account at most one email per recipient domain in each 5-minute send round, counted as if nothing else were sending), and warning is one plain sentence saying so when it is above 0, or saying that other campaigns' waiting emails, this campaign's own follow-ups, or more people at one company than there are sending days before endBy may still make some contacts miss endBy: tell the person before they confirm. Nothing is refused for it; whatever still waits at endBy is skipped. previewOnly:true is a dry run: every launch check runs (a launch that would be refused is refused the same way), nothing is created or sent, no idempotencyKey is recorded, and it returns { preview, sendWindow, timezone, contacts, suppressed, duplicatesRemoved, alreadyEmailed, endByShortfall, warning, samples } — the same endByShortfall and warning the launch would carry; samples[] is { email, steps[] } for the first three leads, each step its rendered subject and body (every real send then appends the required footer: sender identity, postal address, unsubscribe link). People who already got this email are skipped: a contact is left out, never stored or emailed, when another campaign of this account has sent them, or still has waiting for them, an email with the same subject and body as this campaign's first email, as written before {{tokens}} are filled in (spacing differences do not count). A trial's test email counts only while the account is still on the trial, and the built-in demo campaign never counts. alreadyEmailed in the answer is how many were skipped (anyone also on your suppression list is counted there, not in suppressed): tell the person in plain words, for example "skipped 12 people who already got this email". To include them, send includeAlreadyEmailed: true on the launch itself (previewOnly: true shows the count first), or after a launch add them to that same campaign with add_leads and includeAlreadyEmailed: true. Never launch the same list again to include them: that emails everyone who was not skipped a second time. Naming a repeat with repeatOf includes them too. A launch on which everyone would be skipped is refused (400) and nothing is created; an add of contacts on which everyone is skipped adds no one, with no error. A launch, or an add of contacts, is refused with 409 duplicate_campaign when it would leave a campaign with exactly the same set of contact addresses and the same subjects and bodies in the same step order as another campaign of this account, however long ago that one was launched and whether it is running, paused, ended or finished, so the same people are not sent the same emails twice. Nothing else is compared: a different name, offer, time zone, send window, delay, date, stopOnReply, listSource, row order or contact name, company or vars is still the same campaign. The refusal creates nothing and carries existingCampaignId, existingCampaignName and existingCampaignState. To repeat that campaign on purpose, send the request again with repeatOf set to that existingCampaignId: it is accepted once, and only while it names the most recent such campaign. To retry a call whose response you lost, resend it with the same idempotency key, which replays the original result instead. A campaign launched on a trial account emailed nobody and counts only while the account is still on the trial; the built-in demo campaign never counts. A trial account can launch up to 5 test campaigns in any 24 hours, each holding up to 100 contacts, which is enough for a small test. Both are limits of the trial, not of Coldrig: going live removes them. Nothing in a campaign launched on the trial is ever really sent: when the account goes live, the emails it still has waiting are canceled and it cannot be resumed or added to, so launch a new campaign to send real email.
list_campaignsread-onlyList every campaign at once: [{ campaignId, name, status, counts{sent,reply,bounce,complaint,unsubscribe,failed,soft_bounce,auto_reply} }], newest first — no per-campaign lookup needed. Use campaign_results for one campaign's counts, metrics for account-wide totals.
list_leadsread-onlyList/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, threadId (that lead's conversation: pass it to thread for the lead's timeline; null if nothing was scheduled). 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.
list_messagesread-onlyList this tenant's system + operator messages (a retryable setup step, a credential going live, an operator notice), cursor-paginated. Unacked messages sort first (newest within that group), then acked (also newest first). Returns { messages[], nextCursor }; each: id, kind, severity ('info' = resolves on its own | 'action_required' = act and it progresses | 'operator_pending' = platform stopped, only an operator can restart it, retrying the SAME call then works | 'terminal' = platform stopped, only a human can move it, do NOT retry), body, actionHint, source (system|operator), createdAt, readAt. `readAt` is set ONLY by ack_message — listing never marks messages read, so a null readAt does not mean never seen, only not yet acknowledged. Use ack_message to stop one resurfacing. infrastructure_status inlines the newest 5 unacked messages; this is the full paginated surface.
markSet 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.
metricsread-onlyAccount-wide outcome totals across ALL campaigns: { sent, reply, bounce, complaint, unsubscribe, failed, soft_bounce, auto_reply } — the same event counts as campaign_results but summed tenant-wide (no per-contact fields) (bounce = hard only, opens not tracked; auto_reply = automatic replies such as out-of-office, never counted in reply). Use campaign_results for one campaign, list_campaigns per-campaign, or account for billing/quota.
pausedestructivePause ONE campaign: its status → 'paused' and nothing more in it sends; its remaining steps are HELD, not cancelled (already-sent mail is unaffected) — call resume to continue it. Input: campaignId. Returns { paused: true }. 404 if not found. Add leadEmails to stop ONLY those leads in this campaign instead (e.g. a donor who already gave): their remaining steps are cancelled for good (resume does not bring them back), the campaign keeps running for everyone else, and nobody is suppressed — returns { paused: false }. Matching ignores capitalisation; if any address is not a lead of this campaign, nothing is stopped and the call is refused (404) naming them. Use pause_all to pause every active campaign at once.
pause_alldestructivePause EVERY active campaign for the tenant at once (each active status → 'paused'; nothing more in them sends, and their remaining steps are held). No inputs. Returns { pausedAll: true }. Use pause to pause a single campaign by id, and resume (one call per campaign) to continue one.
plan_campaignread-onlyPlan a dated outreach campaign (for example a fundraising drive) and get its price. Read-only: nothing is booked, charged or changed. Inputs: contacts, campaignStart (first campaign day, YYYY-MM-DD, UTC), campaignDays, bookingDate (the day you would book, normally today), and optionally domainsAlreadyHeld (default 0; infrastructure_status shows `domains`) and listIsOwnOptIn (your own opted-in donors; default false). Returns { mailboxes, domainsToBuy, bookBy, advanceStartOn, safeReach, priceCents, warnings[] }. safeReach is how many contacts the warm-up ramp can safely reach if you book on bookingDate; the price is for that many. warnings: over_60_mailboxes (above the 60-address self-serve ceiling), late_booking (safeReach is below contacts), own_list_cheaper_on_esp (an opted-in list may cost less on an email-service provider). A bookingDate after the last campaign day is refused 400. Campaign checkout and booking are not available: to book, contact support (contact_operator).
remove_mailboxesdestructiveDowngrade: release your N NEWEST live mailboxes, lowering the billed quantity. Inputs: count, acknowledged (must be true — release is immediate but irreversible; no mid-cycle credit, lower price starts next renewal, minimum 5 mailboxes/$99). Returns { releasedCount, failedCount, unreleased, billing, deduplicated }. `count` is RELATIVE (that many MORE, not a fleet target). ALWAYS pass an idempotencyKey: the FIRST call under a key fixes WHICH mailboxes to release; a later call with the SAME key can only finish that set. `releasedCount` can be less than `count` — `failedCount` names how many the vendor refused (still live, billed; `unreleased` lists them). Resend with the same key until `failedCount` is 0. `deduplicated: true` = NO new work — an earlier recorded outcome under that key was replayed. A genuinely NEW downgrade needs a NEW key. 409 = a release already running; re-check infrastructure_status before retrying. To ADD mailboxes use setup_infrastructure or configure_byo_domain.
replydestructiveSend a reply on an existing thread, from the mailbox that sent it. Inputs: threadId, body. Returns { messageId, deduplicated }. A reply is real send volume, governed like campaign sends: counts against the mailbox daily cap, and 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 next daily rollover, non-retryable does not — stop retrying, do not loop replies to manufacture volume. Idempotent: identical retries collapse to one send — pass a stable idempotencyKey (else a body hash is used, matched only 10 minutes) so a dropped-response retry cannot double-send. `deduplicated: true` = NO new email sent; `messageId` is from an earlier matching send. Pass a key, or vary the body, if a repeat must genuinely go out. 404 if no sending mailbox is on record for the thread.
resumedestructiveResume ONE paused campaign: its status → 'active'. Input: campaignId. Returns { resumed: true }. Only a paused campaign of this account can be resumed: 404 if unknown, refused (400) if it is not paused, is past its end date, or is a trial campaign on an account that has gone live. On a paid account, resuming sends nothing by itself: the held steps go out through the normal schedule, inside the send window and the daily caps. A step whose time passed while paused goes out at the next allowed slot, one step at a time per lead: the step after it still waits its own delayDays from when that one actually sent, so a pause never puts two steps in one day. Leads stopped on their own stay stopped: stopped with pause leadEmails, replied, unsubscribed, bounced or suppressed. Steps a campaign lost while it was paused before this tool existed are not restored.
setup_infrastructureProvision sending infra: buy lookalike domains, create mailboxes, start warmup. Inputs: brand, primaryDomain, domains + inboxesEach (or distribution), persona, physicalAddress, senderIdentity. Billing is per provisioned mailbox; quoteOnly:true previews cost first. `domains`/`inboxesEach` are the infra you want to HAVE, not an amount to add — a repeat call never buys twice; each ordinal 0..domains-1 fills to its own count. Mailbox addresses are DETERMINISTIC from persona+ordinal+slot — keep persona unchanged on a retry. `registerDomains:true` is required consent before any NEW domain purchase; omitting it on a buy call is refused 400 registrar_optin_missing (self-correct by resending true, never an operator escalation) — also needs `registrant`, unless already on file. NO background retry exists: a `provisioning` result (pending|capacity_pending) needs YOU to retry to progress; capacity_pending needs contact_operator instead. Returns { jobId, billing, provisioning?, nextSteps }.
submit_feedbackSend private feedback to the Coldrig founders about how the product is working for you. Inputs: score (integer 0-10, optional) and/or text (1-2000 chars, optional); at least one is required. It is stored privately and emailed to the founders, never published, and it opens no support ticket and changes nothing on your account (use contact_operator for a problem that needs an answer). Each call is a separate submission. Returns { feedbackId, received }.
suppress_leaddestructivePermanently 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 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.
threadread-onlyFull 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/auto_reply/bounce/...; auto_reply = an automatic reply such as out-of-office), step (the sequence step of a sent message, null otherwise), ts, messageId, metadata }, oldest first. 404 if unknown. Use inbox to LIST threads; reply to respond; mark/label_thread to triage.
update_leadRecord 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.

Schemas: list_tools.

Similar MCP servers

Official server.json
{
  "name": "io.github.YS-projectcalc/agent-cold-email",
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "remotes": [
    {
      "url": "https://api.coldrig.dev/mcp",
      "type": "streamable-http",
      "headers": [
        {
          "name": "Authorization",
          "format": "string",
          "isSecret": true,
          "isRequired": true,
          "description": "Bearer token from POST /signup (free, no card) — or run `npx agent-cold-email demo` first"
        }
      ]
    }
  ],
  "version": "0.2.5",
  "packages": [
    {
      "version": "0.2.2",
      "transport": {
        "type": "stdio"
      },
      "identifier": "agent-cold-email",
      "registryType": "npm",
      "environmentVariables": [
        {
          "name": "AGENT_COLD_EMAIL_API_KEY",
          "format": "string",
          "isSecret": true,
          "description": "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."
        },
        {
          "name": "AGENT_COLD_EMAIL_BASE_URL",
          "format": "string",
          "description": "Override the API base URL (default: https://api.coldrig.dev)"
        }
      ]
    }
  ],
  "repository": {
    "url": "https://github.com/YS-projectcalc/agent-cold-email",
    "source": "github"
  },
  "websiteUrl": "https://coldrig.dev/",
  "description": "Coldrig — cold-email infra run by your agent: 28 MCP tools, live sending, free sandbox, from $99/mo."
}