A linear sequence sends the same follow-up to a contact who clicked your demo link and a contact who never opened the first email. That equivalence wastes the engaged contact's attention — they receive content calibrated for cold prospects — and misses the conversion window when intent is highest. iso-25010:2011 functional-suitability.functional-completeness treats this as a completeness failure: the system claims to run campaigns but cannot differentiate its behavior based on the core input it tracks. At the business level, a branching gap means every click and reply is detected but never acted on, turning your engagement data into a vanity metric.
High because linear-only sequences ignore real-time engagement signals, wasting conversion opportunities when contact intent is measurably highest.
Add step_on_engage and step_on_no_engage fields to step records, then resolve the next step at advancement time rather than at sequence design time.
interface SequenceStep {
id: string
action: 'send_email' | 'wait' | 'condition'
delayDays: number
templateId: string | null
stepOnEngage: string | null // jump here if contact opened or clicked
stepOnNoEngage: string | null // use if not engaged (null = continue linear)
}
async function resolveNextStep(enrollment: Enrollment, step: SequenceStep): Promise<string | null> {
if (step.stepOnEngage) {
const engaged = await checkEngagement(enrollment.contactId, step.emailId)
return engaged ? step.stepOnEngage : step.stepOnNoEngage
}
return step.defaultNextStepId
}
At minimum, treat clicks and replies as the engagement signals — opens alone are unreliable under Apple Mail Privacy Protection.
ID: campaign-orchestration-sequencing.sequence-architecture.branching-logic
Severity: high
What to look for: Check whether sequences support conditional branching based on contact behavior. Look for if/else logic in step handlers that routes contacts to different next steps based on: whether an email was opened, whether a link was clicked, whether a reply was received, or whether a score threshold was crossed. This may be implemented as a next_step_if_engaged and next_step_if_not_engaged field on step records, or as a condition evaluation function in step handlers.
Pass criteria: Sequence steps can specify different next steps based on engagement signals. Contacts are routed to appropriate branches based on their behavior. Quote the actual branching condition logic or schema field names found in the codebase. Count all sequence definitions and list all that support at least 1 branch point.
Fail criteria: All contacts follow the same linear path through a sequence regardless of whether they open, click, or reply to emails.
Skip (N/A) when: The project has no sequence functionality, or sequences are intentionally single-path (e.g., all transactional).
Cross-reference: The Campaign Analytics & Attribution Audit validates that A/B testing uses deterministic variant assignment, which is the experimental counterpart to engagement-based branching.
Detail on fail: "All sequences are linear — no branching based on opens, clicks, or replies" or "Step records have no branching fields — single next_step_index only"
Remediation: Add branch conditions to step definitions:
interface SequenceStep {
id: string
action: 'send_email' | 'wait' | 'condition'
delayDays: number
templateId: string | null
// Branching: if engaged (opened/clicked), go to stepOnEngage; else stepOnNoEngage
stepOnEngage: string | null // step ID to jump to if engaged
stepOnNoEngage: string | null // step ID to use if not engaged (null = continue linear)
}
async function resolveNextStep(enrollment: Enrollment, step: SequenceStep): Promise<string | null> {
if (step.stepOnEngage) {
const engaged = await checkEngagement(enrollment.contactId, step.emailId)
return engaged ? step.stepOnEngage : step.stepOnNoEngage
}
return step.defaultNextStepId
}