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 ofNotSetdoes not mean "nothing is wrong", it means "no per-prospect blocker"; step 2 is where a schedule hold becomes visible. Only treatNotSetas 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:
scheduleSending— the most likely cause, and the one nothing else will reveal. Whentrue, sending only happens inside the configured windows: a campaign outside its window staysRunning, sends nothing, and leaves its prospects onNotSet, so it is indistinguishable from a healthy campaign except by reading the schedule itself. Compare the current time inscheduleTimeZone(not your own timezone, and not the user's) against that weekday's window —sendMon…sendSunfor whether the day is enabled at all, and the matchingsend<Day>After/send<Day>Beforebounds, both minutes after midnight (so0–60means 00:00–01:00). Convert the timezone before comparing; this is invisible if you don't.scheduleSendOnDateEnabled— whentrue, nothing sends beforescheduleSendOnDate(plusscheduleSendOnDateHours/scheduleSendOnDateMinutes). A start date in the future is not a fault.- The daily-limit family —
dailyLimit,dailyLimitPer,dailyLimitWhichEmailsCount,dailyLimitInitialEnabled/dailyLimitInitial, and the ramp fieldsdailyLimitIncrease/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 carrySendingLimitsorWarmupLimits(step 1), so trust that over arithmetic on these fields. delayMinSeconds— the between-sends gap, in seconds (140is 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. TheEspMatchNotFoundandEspNotAllowedprospect states do exist, so some configuration blocks — likely involving theespLimit*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 fromespMatchEnabled: truealone.
3. Are there senders, and can they send?
fromEmailson 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.