Sequencer Overview

The sending half of Superkabe — campaigns, sequences, dispatch, and how it hands off to the protection layer.

In one paragraph

A Superkabe Campaign owns a multi-step Sequence. Each step has one or more Variants (A/B). Leads enrolled in the campaign become CampaignLeads. A 60-second dispatcher finds CampaignLeads whose next-send time is due, picks a mailbox via ESP-aware routing, and queues a batched send job. The send adapter dispatches via Gmail API / Microsoft Graph / SMTP with HMAC-signed tracking pixels and List-Unsubscribe headers. Replies are detected by the IMAP poller and stop the sequence. Bounces are detected by SMTP transcripts and DSN parsing, and trigger the protection layerif rates cross thresholds.

The Entity Model

Six tables make up the sequencer's sending model. Most operations the dispatcher does are joins across these — knowing the model is the fastest way to predict what the system will do in any given situation.

EntityRepresentsLifetime
CampaignA named send program with a sequence, schedule, and a pool of mailboxesPersistent — paused when finished
SequenceStepOne step in the campaign's sequence (subject + body + delay before next step)Persistent
StepVariantA/B variant of a step. Picked at send-time by weighted rotationPersistent
CampaignLeadA lead enrolled in this campaign. Carries current_step, next_send_at, sticky mailbox, statusPer enrollment — terminal on reply / bounce / unsubscribe / completion
CampaignAccountJunction row: which mailboxes the campaign can dispatch from. Optional per-mailbox override capsPersistent
SendEventOne row per email actually sent. Source of truth for analytics + bounce-rate windows90-day retention

The protection layer adds Mailbox andDomain rows alongside ConnectedAccount — see the State Machine doc for how those interact with the sequencer.

A Send's Lifecycle, Step by Step

A single email going from "lead enrolled" to "reply received" passes through ten distinct stages. Each is owned by a specific service so failures are debuggable in isolation.

1
Lead intake

Lead arrives via CSV upload, Clay webhook, manual create, REST API, or migration import. Lands in the org-wide Lead table.

2
Health gate classification

leadHealthService scores the lead GREEN / YELLOW / RED based on email validation + domain signals. RED is rejected at intake.

3
Campaign enrollment

A CampaignLead row is created in the chosen campaign. status=active, current_step=0, next_send_at=now (for the first step) or in the future (for follow-ups).

4
Dispatcher tick (every 60s)

sendQueueService scans active campaigns, finds CampaignLeads where next_send_at ≤ now, applies suppression filters (bounced / unsubscribed / paused).

5
Mailbox selection

For each due lead, ESP-aware routing scores each campaign mailbox: 0.6 × remaining_capacity + 0.4 × per-ESP performance. Highest score wins. Sticky pinning preserves the same mailbox for subsequent steps.

6
Variant pick + personalization

Weighted rotation picks a variant. Spintax + token replacement personalize the subject/body. CAN-SPAM postal-address footer + List-Unsubscribe URL are appended.

7
Send batch enqueue

Emails are batched per mailbox (one BullMQ job per mailbox per tick) so a single SMTP connection sends all of that mailbox's emails for the tick. Reduces connection churn and improves rate predictability.

8
Send adapter dispatch

gmailSendService / microsoftSendService / emailSendAdapters fire the actual API call. Tracking pixel and click-tracking link rewrites are injected here. SendEvent row written on success.

9
CampaignLead advance

On send success: current_step++, last_sent_at=now, next_send_at=now+step.delay. If this was the last step, status=completed.

10
Reply / bounce ingest

imapReplyWorker polls every 60s. A reply flips CampaignLead.status=replied and stops further sends. A hard bounce triggers the protection layer's suppression cascade.

Sending Window & Daily Caps

The dispatcher applies four independent caps before assigning sends. The smallest remaining capacity wins. If any one is exhausted, the lead is held until the next tick.

1. Mailbox global daily cap

ConnectedAccount.daily_send_limit. The absolute cap on what this mailbox sends per day across all campaigns combined.

2. Campaign daily cap

Campaign.daily_limit. The cap on what this campaign sends per day, summed across all its assigned mailboxes.

3. Per-mailbox-per-campaign override

CampaignAccount.daily_limit_override. Caps how much one specific mailbox contributes to one specific campaign — used when a mailbox is shared across campaigns.

4. Recovery-phase warmup cap

Mailbox.warmup_limit. Active only when the mailbox is in QUARANTINE / RESTRICTED_SEND / WARM_RECOVERY. The protection layer owns this.

Sending window: each campaign declares schedule_timezone,schedule_start_time,schedule_end_time, and a list of valid days (e.g. Mon–Fri). The dispatcher resolves "is it in the sending window now?" against the campaign's timezone, not UTC. Outside the window, due leads accumulate and dispatch resumes when the next valid hour begins.

Send gap: Campaign.send_gap_minutes spaces sends from the same mailbox apart by at least N minutes (with a small random jitter). This makes the sending pattern look human and prevents tight bursts that ISPs flag as automation.

Stop Conditions

A CampaignLead is removed from the dispatch pool the moment any of these terminal events fires. Each maps to a distinct status; the dispatcher's pre-send filter checks them all.

EventDetected byResulting CampaignLead.status
Reply receivedimapReplyWorker (60s polling)replied
Hard bounceSMTP 5xx transcript / RFC 3464 DSNbounced
Unsubscribe (footer click or List-Unsubscribe)trackingController.processUnsubscribeunsubscribed
Sequence finished (last step sent)sendQueueService.advanceStepcompleted
Operator paused (UI / API)campaignController2.pauseLeadpaused

Org-wide suppression: the dispatcher also runs a defense-in-depth check against the org-wideLead.status table. A lead markedunsubscribed orbounced at the org level is filtered out of every campaign — not just the one where the event happened. This is required for CAN-SPAM § 5(a)(4)(A), CASL § 11(3), and GDPR Art. 21 compliance.

Handoff to the Protection Layer

The sequencer is the active half of the platform — it dispatches mail. The protection layer is the reactive half — it watches what the sequencer just did and intervenes if the data goes bad. The handoff between them happens at three exact points:

1. Every send writes a SendEvent

monitoringService aggregates SendEvents into rolling windows to compute bounce rate per mailbox and per domain. Threshold breach triggers pauseMailbox → cooldown → quarantine → 5-phase healing.

2. Every bounce writes a BounceEvent + cascades suppression

A hard bounce flips Lead.status=bounced ANDCampaignLead.status=bounced in one transaction. The dispatcher won't pick that lead up on subsequent ticks.

3. The dispatcher honors recovery state

If a mailbox is connection_status='disconnected' (e.g. paused, expired, recovering), the dispatcher skips it. Ifrecovery_phase is in a constrained phase, the warmup_limit cap kicks in. The sequencer never overrides protection — protection always wins.

For the full picture of how protection takes over once it's triggered, read Auto-Healing Pipeline and State Machine.

Compliance Surfaces in the Sequencer

Five regulatory surfaces are enforced inside the dispatcher itself, not as opt-in features. None can be disabled because deliverability + legal exposure both depend on them:

  • CAN-SPAM § 5(a)(5) — every commercial email must carry a valid postal address. The dispatcher refuses to send any campaign where Organization.mailing_address is null and fires a Slack alert until the operator configures one.
  • RFC 8058 / Gmail bulk-sender policy — every send carriesList-Unsubscribe +List-Unsubscribe-Post headers. One-click unsubscribe is mandatory above 5K sends/day.
  • GDPR Art. 21 / CASL § 11(3) — unsubscribes propagate org-wide via the Lead.status cascade. The dispatcher checks it as defense-in-depth against partial transaction failures.
  • EU compliance mode — when Campaign.eu_compliance_mode=true, the tracking pixel is suppressed (ePrivacy Art. 5(3) requires explicit consent for tracking cookies/pixels for EU recipients).
  • RFC 3464 DSN parsing — async bounces (mailbox accepted then later rejected) are captured by the IMAP poller and converted to BounceEvents so suppression can fire.

What's NOT in the Sequencer

Worth being explicit about — these are protection-layer responsibilities and the sequencer never touches them:

  • Pausing a mailbox — owned by monitoringService.pauseMailbox
  • The 5-phase healing pipeline — owned by healingService
  • DNSBL / IP blacklist checks — owned by dnsblService + mailboxIpBlacklistWorker
  • SPF / DKIM / DMARC validation — owned by infrastructureAssessmentService
  • Domain warming / cooldowns — owned by warmupService + warmupTrackingWorker

Related Reading