Drip plans — /api/v1/drip-plans
A rep writes fifty individually-worded drafts and then has to send them. Sending them all at once is worse than not sending them: a mailbox that normally emits five a day suddenly emitting fifty in ten minutes is a spam signal in its own right, whatever the emails say.
A drip plan takes a set of drafts and spaces them out — one at a time, with a random gap between each, inside a sending window, capped per mailbox per day, and pulled from the queue if that contact replies first.
Nothing is sent by these endpoints
Creating a plan stamps a send time on each draft and returns the schedule. The every-minute cron is what releases them. You can see the whole queue, and the finish date, before a single email moves.
Create a plan
POST /api/v1/drip-plans HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"name": "Champion closeout round",
"emailIds": ["cmtsv6ts404hzii01dnmu4k5t", "cmtsv6ts404hzii01dnmu4k5u"],
"startAt": "2026-09-16T13:00:00.000Z",
"stopOnReply": true,
"pacing": { "minGapMinutes": 12, "maxGapMinutes": 35, "dailyCapPerMailbox": 25 }
}Requires the write:emails scope (or *).
| Field | Description |
|---|---|
name | What this batch is, for your own reference |
emailIds | Ids of emails currently in DRAFT, in the order they should go out (1–500) |
startAt | When to begin. Defaults to now; a time outside the window rolls to its next open |
stopOnReply | Pull a queued message if that contact replies first. Default true |
pacing | Any of the fields below; anything omitted keeps its default |
Pacing
| Field | Default | Meaning |
|---|---|---|
minGapMinutes / maxGapMinutes | 12 / 35 | The gap between two sends from the same mailbox, drawn at random per send |
dailyCapPerMailbox | 25 | Sends allowed from one mailbox on one calendar day |
businessHoursOnly | true | Clamp every send into the window below |
timezone | America/Toronto | IANA zone the window and the daily cap's calendar day are read in |
windowStartHour / windowEndHour | 9 / 17 | The window, in that zone. windowEndHour is exclusive |
daysOfWeek | [1,2,3,4,5] | Allowed weekdays, 0 = Sunday |
Pacing is applied per mailbox, not per plan. Two From addresses are two connected mailboxes with two reputations and two daily counters, so a 52-draft batch split across both drains in about half the wall-clock time.
Every id must be a DRAFT in your workspace. A batch containing anything else — already sent,
already scheduled, or belonging to another workspace — is refused whole rather than silently
shortened, so the schedule you get back is always the real one.
201 returns the plan and the computed time for every message:
{
"dripPlan": { "id": "cmq2drip0alk34567890", "name": "Champion closeout round", "status": "ACTIVE", "...": "..." },
"schedule": [
{ "emailId": "cmtsv6ts404hzii01dnmu4k5t", "scheduledAt": "2026-09-16T13:00:00.000Z", "toEmail": "buyer@example.com", "subject": "..." },
{ "emailId": "cmtsv6ts404hzii01dnmu4k5u", "scheduledAt": "2026-09-16T13:21:00.000Z", "toEmail": "other@example.com", "subject": "..." }
]
}Check a plan
GET /api/v1/drip-plans/cmq2drip0alk34567890Requires read:emails. Returns the plan with counts (total, scheduled, sent, skipped,
failed) and every message in send order — still-queued mail first, by time.
A message that was pulled from the queue is back in DRAFT with a skipReason:
skipReason | What happened |
|---|---|
REPLIED | The contact answered before their turn, so the batch stopped for them |
SUPPRESSED | They unsubscribed, bounced, or are marked do-not-contact |
CANCELED | The plan was cancelled while this one was still queued |
GET /api/v1/drip-plans lists the workspace's plans, newest first, with optional status and
limit.
Pause, resume, cancel
POST /api/v1/drip-plans/cmq2drip0alk34567890/pause
POST /api/v1/drip-plans/cmq2drip0alk34567890/resume
DELETE /api/v1/drip-plans/cmq2drip0alk34567890All three require write:emails and return { "dripPlan": { ... } }.
Pause stops sending immediately; queued mail keeps its place. Resume re-spaces whatever is left from now using the plan's own pacing — a plan paused across a day does not fire its backlog the moment it comes back, which would be exactly the burst the feature exists to prevent.
Cancel returns every still-queued email to DRAFT, stamped CANCELED. Nothing is deleted:
you wrote those words, and they are still there to re-schedule or send by hand. Mail already sent
is untouched — nothing can unsend it, and an API that implied otherwise would be lying.
What a plan defers rather than fails
A paced batch runs for days, so some failures are "not yet" rather than "never". When the
workspace's sending is paused for reputation, when it hits its rolling 24-hour cap, or when the
mailbox runs out of its own daily slots, the message stays SCHEDULED and moves to the next open
slot in the window. Only an actual delivery error — a rejecting server, a broken mailbox — marks
a message FAILED.
| Status | Meaning |
|---|---|
400 | Invalid body, an id that is not a DRAFT, or an id from another workspace |
401 | Missing, invalid, or expired API key |
403 | Key lacks read:emails / write:emails |
404 | No such plan here — or, for pause/resume/cancel, it is not in a state that allows it |