MCP

Why isn't it sending?

The campaign says Running, prospects are enrolled, and sentCount is still 0 (or stopped climbing). Work down this list — the first check usually ends it, and each step names the exact field to read.

First, check the clock. Sending is paced, not immediate — a first email typically lands within about a minute of enrolment but several minutes is entirely normal, and during that wait sentCount stays 0 while every prospect reads sendingStatus: NotSet. That combination is what a healthy campaign looks like before its first send, so do not start diagnosing on it. Give it a few minutes and re-read campaign/stats_by_id before working down this list.

Past that, if sentCount is 0 and none of the causes below apply, say so plainly rather than telling the user "it should start shortly".

1. Ask the prospects — they carry the reason

This is the fastest answer and the one most often skipped. Each prospect's sendingStatus names why that prospect isn't being mailed. Read the campaign's prospects with prospect/get using includeCampaignIds.campaignIds — a real array, e.g. [104772]. There is no campaignId argument; passing one is silently ignored and you get the whole account back, which looks like an answer and isn't.

Read the statuses off the returned rows. You can also narrow with the sendingStatus argument, but that runs as a bounded scan — its first page can come back with few or no matches before the data is exhausted, so treat a quiet first page as "keep paging with pagination.nextCursor", never as "none exist".

sendingStatus What is blocking it
NoSender No sender is assigned to the campaign — see step 3.
EspMatchNotFound ESP matching is on and no connected sender matches this prospect's email provider.
EspNotAllowed The prospect's provider is excluded by the campaign's ESP limits.
NoWarmup No warmed-up sender is available.
WarmupLimits The warmup daily limit is used up for now.
SendingLimits The campaign or sender daily limit is used up for now.
NotReceiving The sender is not accepting inbound mail.
EmptySubject / EmptyBody The campaign has no subject or no body.
MissingPlaceholder A personalization variable has no value on this prospect and the campaign deactivates rather than mails — see personalization-variables.
Stuck Sending is blocked for this prospect.
NotSet Not a blocker — no send outcome recorded yet. Normal both before the first send and on a campaign that is simply paced; see the note at the top.
Invalid, Blacklisted, Unsub, BounceHard, BounceSoft, Stopped Deliberately excluded — not a fault.

Full list and meanings: schemas/prospect.

SendingLimits and WarmupLimits describe a limit already spent, not a misconfiguration — check what the limit is before treating either as a fault.

Not every block shows up here. A campaign held by its send schedule leaves its prospects reading NotSet — exactly what a healthy campaign waiting for its first send looks like. So a page of NotSet does not mean "nothing is wrong", it means "no per-prospect blocker"; step 2 is where a schedule hold becomes visible. Only treat NotSet as reassuring once you have checked the schedule.

2. Is the campaign allowed to send right now?

Read the campaign with campaign/get_by_id and check, in this order:

  • scheduleSendingthe most likely cause, and the one nothing else will reveal. When true, sending only happens inside the configured windows: a campaign outside its window stays Running, sends nothing, and leaves its prospects on NotSet, so it is indistinguishable from a healthy campaign except by reading the schedule itself. Compare the current time in scheduleTimeZone (not your own timezone, and not the user's) against that weekday's window — sendMon…sendSun for whether the day is enabled at all, and the matching send<Day>After / send<Day>Before bounds, both minutes after midnight (so 060 means 00:00–01:00). Convert the timezone before comparing; this is invisible if you don't.
  • scheduleSendOnDateEnabled — when true, nothing sends before scheduleSendOnDate (plus scheduleSendOnDateHours / scheduleSendOnDateMinutes). A start date in the future is not a fault.
  • The daily-limit familydailyLimit, dailyLimitPer, dailyLimitWhichEmailsCount, dailyLimitInitialEnabled / dailyLimitInitial, and the ramp fields dailyLimitIncrease / dailyLimitIncreaseToMax / dailyLimitIncreasePercent. Read them, and read what has actually been sent today from campaign/stats_by_id — a campaign that has spent its allowance stops until the allowance resets. The prospects are the reliable signal here: if a limit is what stopped them they carry SendingLimits or WarmupLimits (step 1), so trust that over arithmetic on these fields.
  • delayMinSeconds — the between-sends gap, in seconds (140 is 140 seconds, not 140 minutes), so it is rarely why a campaign looks slow; when asked how long a list will take, answer from the daily limits above, not from this field.
  • espMatchEnabled / espMatchType — matching prefers a sender on the recipient's own provider. Treat it as a weak suspect: enabling it (MatchDomain) on a campaign whose only sender was on a different provider from the recipient still sent normally, so it does not simply refuse unmatched pairs. The EspMatchNotFound and EspNotAllowed prospect states do exist, so some configuration blocks — likely involving the espLimit* settings or a larger sender pool — but the exact trigger is not established. Check the prospects for those two states rather than inferring a block from espMatchEnabled: true alone.

3. Are there senders, and can they send?

  • fromEmails on the campaign lists the senders actually attached. Empty means nothing can send, whatever the campaign status says.
  • Check each sender with sender/get_by_id: warmup, dailyLimit, and whether it is connected.
  • sender/errors_by_id lists that sender's recent failures — authentication problems, provider refusals, blocked tenants. An empty list means no recent errors; older warmup or receive noise is not evidence of a send problem.

4. Is the account able to send at all?

account/credits — every send draws from the balance, and workspaces and clientspaces spend from their parent organization's shared pool. At zero, sends fail with InsufficientCredit.

5. Working in the right scope?

If you are org-scoped and the campaign lives in a workspace, pass workspaceId on your reads — see workspace-delegation. A campaign read from the wrong scope can look untouched while a perfectly healthy one runs elsewhere. Confirm your scope with account/get.

If every check above is clean

Then the visible state genuinely does not explain it, and you should say that rather than speculating. There is no send-queue depth, next-attempt time or last-attempt timestamp exposed today, so "queued but not yet attempted" and "silently not being attempted" look identical from here. Report what you verified — status, schedule, limits, senders, prospect statuses, credits — tell the user the remaining gap is not visible through this interface, and point them at support. Do not invent a reason, and do not promise it will start shortly.