Back to blog
SequencesEmail AutomationAPIDeveloper GuideDrip Campaigns

Drip Email API: Build Automated Email Sequences in Code

How to build drip campaigns and email sequences programmatically with an API. Define multi-step flows, triggers, and branching logic in code — not a drag-and-drop editor.

SendPigeon TeamApril 16, 20267 min read

A drip email API lets you create and manage automated email sequences programmatically — define steps, set triggers, manage enrollments, and track analytics via API calls or SDKs instead of a visual editor.

If you're building a SaaS product, this means sequences that are version-controlled, testable, and deployed like the rest of your code.

TL;DR

What it is: An API for building multi-step automated email sequences in code.

Why developers need it: Version control, testing, CI/CD, programmatic enrollment, conditional branching — things visual editors can't do well.

Best option: SendPigeon — full sequences API with 5 step types, event-based triggers, and SDKs for Node.js, Python, Go, PHP.


Why Build Drip Campaigns in Code?

Visual editorAPI-first
Click to configureDefine in code
Can't version controlGit-tracked
Manual testingAutomated tests
One-off changesCI/CD deployable
Limited logicFull branching + webhooks
Hard to replicate across environmentsSame code in staging + production

If your app already has event tracking, user segmentation, and deployment pipelines — your email sequences should plug into the same workflow.


Anatomy of a Drip Sequence

Trigger (event, tag, signup, API call)
  ↓
Step 1: Send welcome email
  ↓
Step 2: Wait 2 days
  ↓
Step 3: Branch — has the user activated?
  ├─ Yes → Send tips email
  └─ No  → Send nudge email
       ↓
Step 4: Wait 3 days
  ↓
Step 5: Send feature highlight
  ↓
Done (enrollment complete)

Each step is an object with a type and config. The sequence engine handles scheduling, retries, and state management.


Building a Sequence with the API

1. Create the Sequence

import { SendPigeon } from "sendpigeon";

const pigeon = new SendPigeon("sp_live_xxx");

const sequence = await pigeon.sequences.create({
  name: "Onboarding",
  description: "7-day new user onboarding flow",
  triggerType: "CONTACT_CREATED",
});

console.log(sequence.id); // seq_abc123

2. Add Steps

// Step 1: Welcome email (sent immediately on enrollment)
const step1 = await pigeon.sequences.steps.create(sequence.id, {
  type: "SEND_EMAIL",
  config: {
    templateId: "tmpl_welcome",
    subject: "Welcome to {{appName}}!",
    fromName: "Alice from YourApp",
  },
});

// Step 2: Wait 2 days
const step2 = await pigeon.sequences.steps.create(sequence.id, {
  type: "WAIT",
  config: { type: "delay", duration: 172800 }, // 2 days in seconds
});

// Step 3: Branch on activation status
const step3 = await pigeon.sequences.steps.create(sequence.id, {
  type: "BRANCH",
  config: {
    condition: { type: "has_tag", tag: "activated" },
  },
});

// Step 4a: Tips email (true branch — user activated)
const step4a = await pigeon.sequences.steps.create(sequence.id, {
  type: "SEND_EMAIL",
  config: {
    templateId: "tmpl_tips",
    subject: "3 things you can do with {{appName}}",
  },
});

// Step 4b: Nudge email (false branch — user hasn't activated)
const step4b = await pigeon.sequences.steps.create(sequence.id, {
  type: "SEND_EMAIL",
  config: {
    templateId: "tmpl_nudge",
    subject: "Need help getting started?",
  },
});

3. Wire the Flow

// Connect the branch to its true/false paths
await pigeon.sequences.steps.update(sequence.id, step3.id, {
  branchTrueStepId: step4a.id,
  branchFalseStepId: step4b.id,
});

// Set the step order
await pigeon.sequences.steps.reorder(sequence.id, [
  { stepId: step1.id, position: 0, nextStepId: step2.id },
  { stepId: step2.id, position: 1, nextStepId: step3.id },
  { stepId: step3.id, position: 2 },
  { stepId: step4a.id, position: 3 },
  { stepId: step4b.id, position: 4 },
]);

4. Activate

await pigeon.sequences.activate(sequence.id);

New contacts matching the trigger are automatically enrolled.


Step Types

SEND_EMAIL

Sends a template to the enrolled contact.

{
  type: "SEND_EMAIL",
  config: {
    templateId: "tmpl_xxx",
    subject: "Optional override",
    fromEmail: "hello@yourdomain.com",
    fromName: "Your App",
    replyTo: "support@yourdomain.com",
  }
}

Templates support personalization: {{firstName}}, {{email}}, {{contact.customField}}, and trigger data like {{event.plan}}.

WAIT

Pauses the enrollment before the next step.

// Wait a fixed duration
{ type: "WAIT", config: { type: "delay", duration: 86400 } } // 1 day

// Wait until a specific time of day (UTC)
{ type: "WAIT", config: { type: "time_of_day", hour: 9, minute: 0 } }

// Wait for a condition (with timeout)
{ type: "WAIT", config: { type: "condition", condition: { type: "has_tag", tag: "verified" }, timeout: 604800 } } // 7 days

BRANCH

Splits the flow based on a condition. Contacts go to branchTrueStepId or branchFalseStepId.

{
  type: "BRANCH",
  config: {
    condition: { type: "has_tag", tag: "premium" }
  }
}

UPDATE_CONTACT

Modifies the contact mid-sequence. Executes instantly (no delay).

{
  type: "UPDATE_CONTACT",
  config: {
    actions: [
      { type: "add_tag", tag: "onboarded" },
      { type: "set_field", field: "onboardingStage", value: "complete" },
    ]
  }
}

WEBHOOK

Calls an external service. Use it to sync with your CRM, trigger Slack notifications, or update external systems.

{
  type: "WEBHOOK",
  config: {
    url: "https://yourapp.com/api/sequence-hook",
    method: "POST",
    headers: { "Authorization": "Bearer xxx" },
    body: { action: "user_onboarded" },
    retries: 3,
    failureAction: "continue", // or "exit"
  }
}

Triggers

TriggerWhen it firesUse case
CONTACT_CREATEDNew contact addedOnboarding
TAG_ADDEDTag applied to contactUpgrade flow, activation
EVENTCustom event sent via APIAny app event
API_CALLManual enrollment via /enrollBatch operations, migrations
DATE_PROPERTYContact reaches a date fieldTrial expiry, renewal reminders

Event-Based Triggers

The most flexible approach. Fire events from your app, and matching sequences auto-enroll:

// In your app: user upgrades to Pro
await pigeon.events.send({
  email: "user@example.com",
  name: "plan_upgraded",
  properties: { plan: "pro", previousPlan: "free" },
});

// Any sequence with triggerType: "EVENT" and eventName: "plan_upgraded"
// will auto-enroll this contact

Enrollment Management

Manual Enrollment

await pigeon.sequences.enroll(sequence.id, {
  contacts: [
    { email: "user1@example.com" },
    { email: "user2@example.com" },
    { contactId: "ct_existing123" },
  ],
});

Pause / Resume / Exit

// Pause an enrollment (e.g., user opened a support ticket)
await pigeon.sequences.enrollments.pause(sequenceId, enrollmentId);

// Resume later
await pigeon.sequences.enrollments.resume(sequenceId, enrollmentId);

// Exit immediately (e.g., user churned)
await pigeon.sequences.enrollments.exit(sequenceId, enrollmentId);

List Active Enrollments

const { enrollments } = await pigeon.sequences.enrollments.list(sequenceId, {
  status: "ACTIVE",
});

for (const enrollment of enrollments) {
  console.log(`${enrollment.contactEmail} — step ${enrollment.currentStepId}`);
}

Analytics

const analytics = await pigeon.sequences.analytics(sequenceId);

console.log(`Enrolled: ${analytics.totalEnrolled}`);
console.log(`Active: ${analytics.activeEnrolled}`);
console.log(`Completed: ${analytics.completedCount}`);
console.log(`Exited: ${analytics.exitedCount}`);

// Per-step analytics
const stepStats = await pigeon.sequences.steps.analytics(sequenceId, stepId);
console.log(`Entered: ${stepStats.enteredCount}`);
console.log(`Completed: ${stepStats.completedCount}`);

Why Not Use Customer.io or Loops?

SendPigeonCustomer.ioLoops
Built forDevelopersGrowth teamsMarketers
Sequence managementAPI + SDK + dashboardDashboard + APIDashboard only
Also sends transactionalYesYesLimited
Inbound parsingYesNoNo
Pricing$10/mo (Starter)$100/mo+$49/mo+
SDK languagesNode, Python, Go, PHPNode, Ruby, PythonNode
Self-serveYesYesYes

Customer.io is feature-rich and built for growth/marketing teams. Loops is focused on a visual-first experience with limited API access. SendPigeon is API-first — sequences are a core developer feature, not an add-on.


Pricing

Email sequences are included in all SendPigeon paid plans. Starter ($10/mo) includes 3 sequences with 500 active enrollments. Growth ($15/mo) includes 10 sequences with 5,000 enrollments. Pro ($39/mo) includes 50 sequences with 50,000 enrollments. For a full comparison with Customer.io and Loops pricing, see SendPigeon vs Loops.


Next Steps