campaigns

Create and activate a campaign against a lead list.

Changed on

The caller supplies the offer and sequence content — this API does not generate outreach copy.

Lead addresses are trimmed and lower-cased before anything else, and an address repeated inside leads is kept once (its first row); the response's duplicatesRemoved counts the rows dropped. The same address in two different campaigns is two contacts. The response also carries contacts (leads stored) and suppressed (how many of those are on your suppression list and were never scheduled).

Dry run: previewOnly: true runs every launch check and returns 200 with { preview: true, sendWindow, timezone, contacts, suppressed, duplicatesRemoved, alreadyEmailed, endByShortfall, warning, samples } — samples is { email, steps[] } for the first three leads, each step its rendered subject and body (every real send then appends the required footer). Nothing is created or sent, and no Idempotency-Key is recorded, so the real launch that follows is neither refused as a duplicate nor answered with the preview. A launch that would be refused is refused the same way as a preview.

End date check: with an endBy, the answer (launch and preview alike) also carries endByShortfall, about how many of the contacts this campaign will email will not get their first email before that date on this account's current mailboxes. It is an estimate: each mailbox's normal warm-up climb day by day, with a slowed or resting mailbox held at today's cap, the sendWindow days and hours in timezone, and at most one email per recipient domain in each 5-minute send round, counted as if nothing else were sending. A warning sentence to pass on before the person confirms comes with it when it is above 0, and when other campaigns' waiting emails, this campaign's own follow-ups, or more people at one company than there are sending days before the date may still make some contacts miss the date. Nothing is refused for it; whatever still waits at endBy is skipped, as always.

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.

Duplicate protection: 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.

post/campaigns

Request

  • Base URL: https://api.coldrig.dev
  • URL: https://api.coldrig.dev/campaigns
  • Auth: HTTP bearer

Headers

Idempotency-Keystring

Optional, and strongly recommended on every mutating call. Resend the SAME key on retry: the first call runs, a replay with the same key returns the recorded result without re-executing — no duplicate campaign, no duplicate send, no second release of mailboxes. Honored on POST /campaigns, /setup-infrastructure, /remove-mailboxes and /threads/{id}/reply.

Only a FINISHED outcome replays. A call that returned while still owing work — a 202 that is still setup, or a /remove-mailboxes response whose failedCount is above zero because the provider refused a mailbox — is not recorded, so resending its key RE-RUNS the request and finishes the job instead of handing back the partial. That is what makes "retry with the same key" a true instruction rather than a guaranteed no-op; the response body always says which case you are in.

It is a REPLAY key, never a request discriminator: on /setup-infrastructure, changing it or omitting it does not cause anything extra to be set up. On /remove-mailboxes it is the only thing that makes a retry safe, because count is relative and releasing mailboxes cannot be undone through this API: the first call under a key records the exact mailboxes it resolved, so a same-key retry finishes that set and can never release one outside it, whatever count the retry carries.

The mechanism differs by transport — REST takes this as the Idempotency-Key header (this parameter); the MCP tools take the equivalent value as an idempotencyKey argument field instead, since MCP tool calls have no separate header channel.

Request body

namestring required
offerstring required
timezonestring
stopOnReplyboolean
endBystring date-time

Optional fixed end date — an ISO 8601 instant with a zone. From then on nothing more in this campaign sends: every step still waiting is skipped and recorded as a failed event with reason campaign_ended. Paid accounts only: a trial account that sends one is refused 400. Must be later than now and later than every step's notBefore, or the launch is refused 400.

listSourcestring

Where the lead list came from, in a sentence. REQUIRED on a crowdfunding account (a missing or "unknown" source is refused 400); optional elsewhere.

previewOnlyboolean

Optional. true = a dry run — every launch check runs and the rendered steps for the first three leads come back with the counts a launch would report; nothing is created or sent.

repeatOfstring

Optional. Send it only to repeat a campaign on purpose: the existingCampaignId that a 409 duplicate_campaign refusal named. Accepted once, and only while it names the most recent campaign of this account with the same contacts and the same subjects and bodies as written; any other value is refused and nothing is created.

includeAlreadyEmailedboolean

Optional. true also stores and emails the contacts who already got this campaign's first email from another campaign of this account; without it they are skipped and counted in alreadyEmailed.

Response

Preview only (previewOnly: true) — nothing was created.

previewtrue required

Always true — nothing was created

timezonestring required
contactsinteger required

Contacts a launch would store

suppressedinteger required

Of those, how many are on your suppression list and would never be scheduled.

duplicatesRemovedinteger required
alreadyEmailedinteger required

Contacts a launch would leave out because they already got this campaign's first email; never stored.

endByShortfallinteger

Only with an endBy: the same count the launch would carry.

warningstring

Only when the launch would carry one: the same sentence. Tell the person before they confirm.

Changes

    • warning

      added the new large_order_payment_pending enum value to the /// response property for the response status

    • info

      added the optional property //////// to the response with the status

    • info

      added the optional property //////// to the response with the status