Skip to content

Drip Sequences

A drip sequence is a series of emails sent to contacts automatically, each after a configurable delay from the previous one. Common use cases: welcome sequences, onboarding flows, re-engagement campaigns.

How it works

  1. You create a drip_sequence campaign and add steps to it (each step is one email with a delay)
  2. Contacts get enrolled — either at subscribe time, or explicitly via DripService
  3. A cron job runs courier:process-drips frequently; it finds enrollments whose next_send_at has passed, sends the email, and advances the contact to the next step
  4. When a contact completes all steps, their enrollment is marked completed; if they unsubscribe, all active enrollments are cancelled

Creating a drip campaign

<?php
$campaignService = service('campaignService');

$campaign = $campaignService->create([
    'name'       => 'Welcome Sequence',
    'subject'    => 'Welcome!',  // overridden per-step
    'from_name'  => 'Acme Team',
    'from_email' => 'hello@acme.com',
    'type'       => 'drip_sequence',
]);

Adding steps

Each step needs a view, a subject, and a delay_hours — how many hours after the previous step (or enrollment) to wait before sending.

<?php
// Step 1: send immediately (0-hour delay)
$campaignService->addDripStep($campaign->id, [
    'subject'     => 'Welcome to Acme!',
    'view'        => 'App\Views\emails\welcome_step1',
    'delay_hours' => 0,
]);

// Step 2: send 24 hours after step 1
$campaignService->addDripStep($campaign->id, [
    'subject'     => 'Getting started with Acme',
    'view'        => 'App\Views\emails\welcome_step2',
    'delay_hours' => 24,
]);

// Step 3: send 72 hours after step 2
$campaignService->addDripStep($campaign->id, [
    'subject'     => 'Pro tips you\'ll love',
    'view'        => 'App\Views\emails\welcome_step3',
    'delay_hours' => 72,
]);

Steps are ordered by position, which is assigned automatically (1, 2, 3…) if you don't set it.

Enrolling contacts

At subscribe time

The easiest way — pass dripCampaignId to ContactService::subscribe():

<?php
service('contactService')->subscribe(
    ['email' => 'ada@example.com'],
    dripCampaignId: $campaign->id
);

Explicitly

<?php
$dripService = service('dripService');
$enrollment  = $dripService->enroll($contact->id, $campaign->id);

enroll() returns null if the contact is already enrolled (any status — prevents double-enrolling). It throws a RuntimeException if the contact isn't subscribed or the campaign has no steps yet.

Processing due steps

The courier:process-drips command does the actual sending. Run it frequently via cron:

# Send due drip steps every minute
* * * * * php /path/to/your/app/spark courier:process-drips

Each run processes up to $batchSize enrollments (default: 200). If you have more than that in the queue, they'll drain across successive runs — that's intentional so you don't blast your email provider.

Cancelling an enrollment

To cancel a specific contact from one campaign:

<?php
$dripService->cancel($contact->id, $campaign->id);

To cancel all drip enrollments for a contact (this happens automatically on unsubscribe):

<?php
$dripService->cancelAllForContact($contact->id);

Checking enrollment status

<?php
$enrollment = $dripService->getEnrollmentStatus($contact->id, $campaign->id);
// Returns DripEnrollmentDTO or null

Enrollment statuses: active, cancelled, completed.

Enrollment lifecycle

(subscribe or enroll()) → active → [step 1] → [step 2] → ... → completed
                                                          ↘ cancelled (unsubscribe or manual)