Skip to main content

Drip Campaigns

Drip campaigns are multi-step automated SMS sequences with per-step delays, send-window enforcement, and auto-stop on reply. Created campaigns start in active status and are picked up by the hourly drip cron. Pause/resume toggles let you hold the sequence without losing enrollments.

For the newer trigger-driven sequence engine (speed-to-lead, missed-call text-back), see Nurture Campaigns.

Create a drip campaign

POST /api/v1/drip-campaigns creates a campaign with steps and optionally enrolls leads on creation. Scope: write_crm.

Request
{
"name": "5-step cold lead nurture",
"campaign_type": "lead_nurture",
"steps": [
{ "delay_minutes": 0, "message_template": "Hi {first_name}, quick question about your home..." },
{ "delay_days": 1, "message_template": "Still thinking it over? Happy to chat anytime." },
{ "delay_days": 3, "message_template": "Last check-in from us. Reply STOP to opt out." }
],
"target": { "tag": "spring-promo-2026" }
}
Response (201 Created)
{
"id": "c1af...",
"name": "5-step cold lead nurture",
"status": "active",
"campaign_type": "lead_nurture",
"steps": [ { "id": "...", "step_number": 1 } ],
"enrolled": 342,
"created_at": "2026-04-20T14:30:00Z",
"started_at": "2026-04-20T14:30:00Z"
}

Steps

  • Max 10 steps per campaign.
  • Each step's message_template is capped at 306 characters (2 SMS segments).
  • Delays are per step: delay_minutes or delay_days relative to the prior step.

Targeting and enrollment caps

Target options (pass at most one): list_id, tag, or lead_ids (array, max 10000). Omit target to create the campaign without enrollments and add them later via POST /api/v1/drip-campaigns/:id/enrollments.

Preview with dry_run

Pass dry_run: true in the create body to resolve the target and validate steps without writing anything: no campaign, steps, or enrollments are created. Use it to confirm "this will enroll N leads, the first send fires at T, and the rendered message looks like X" before committing a large campaign.

Response (dry_run: true)
{
"dry_run": true,
"would_resolve": 350,
"would_enroll": 342,
"would_create_steps": 3,
"target_resolution": {
"method": "tag",
"list_id": null,
"tag": "spring-promo-2026",
"explicit_lead_ids_count": null,
"eligible_count": 342,
"skipped_ineligible": 0
},
"next_send_at_preview": "2026-04-20T14:30:00Z",
"send_window_local": "Mon Apr 20 at 10:30 AM America/New_York",
"step_1_preview": {
"message_template": "Hi {first_name}, quick question about your home...",
"rendered_sample": "Hi Jane, quick question about your home..."
},
"note": "No campaign, steps, or enrollments were created. Re-run without dry_run to commit."
}

The preview renders step 1's template against a sample lead from the resolved target; unknown merge keys are left as the literal {key} so you can spot them.

List and get campaigns

GET /api/v1/drip-campaigns lists drip campaigns for the tenant (paginated, cursor-based). Scope: read. Optional ?status= filter: draft, active, paused, completed.

Example
GET /api/v1/drip-campaigns?limit=50&status=active
Authorization: Bearer ar_live_...

200 { "data": [...], "has_more": false }

GET /api/v1/drip-campaigns/:id returns campaign detail with the step list and live enrollment stats. Scope: read.

Response
{
"id": "c1af...",
"name": "5-step cold lead nurture",
"status": "active",
"steps": [
{ "id": "...", "step_number": 1, "total_sent": 342, "total_delivered": 338 }
],
"stats": {
"enrollments": { "total": 342, "active": 120, "completed": 180, "replied": 30, "opted_out": 12, "failed": 0, "paused": 0 },
"counters": { "total_enrolled": 342, "total_completed": 180 }
}
}

Enrollments

Enroll additional leads

POST /api/v1/drip-campaigns/:id/enrollments enrolls additional leads into an active campaign. Scope: send_sms (enrolling is a commitment to send).

Request
{
"lead_ids": ["a5b2-...", "c3d7-...", "e9f1-..."]
}
Response
{
"enrolled": 2,
"skipped_already_enrolled": 1,
"skipped_not_eligible": 0,
"next_send_at": "2026-04-20T14:30:00Z"
}
  • Max 1000 lead_ids per call.
  • Leads must belong to your tenant, have has_usable_phone=true, and not be opted out.
  • Duplicates against existing enrollments are silently deduped.

Remove an enrollment

DELETE /api/v1/drip-campaigns/:id/enrollments/:enrollmentId removes a single enrollment so the lead stops receiving future steps. Scope: write_crm.

DELETE /api/v1/drip-campaigns/c1af.../enrollments/f4d2...

200 { "removed": true, "enrollment_id": "f4d2...", "lead_id": "...", "status": "paused" }

The enrollment is marked paused rather than hard-deleted so stats and audit history survive.

Pause and resume

Both endpoints are idempotent. Scope: write_crm. Completed campaigns cannot be paused or resumed.

POST /api/v1/drip-campaigns/:id/pause pauses a campaign; the hourly cron stops processing it. Pausing an already-paused campaign returns already_paused: true.

POST /api/v1/drip-campaigns/c1af.../pause

200 { "id": "c1af...", "status": "paused", "already_paused": false }

POST /api/v1/drip-campaigns/:id/resume resumes a paused campaign. Returns already_active: true if it was already active.

POST /api/v1/drip-campaigns/c1af.../resume

200 { "id": "c1af...", "status": "active", "already_active": false }