Skip to content

Outreach Sequences

Overview

Outreach Sequences is the multi-channel sales engagement engine in CRED Commercial. It replaces the legacy MessageSequence / MessageSequenceStep system (email-only, single-variant) with a full-featured orchestration platform supporting Email, LinkedIn, Phone, and Action steps, A/B testing via template variants, configurable sending schedules, automated rulesets, and granular per-channel metrics.

Migration from Legacy System

The previous system lived under src/domain/message-sequence/ and only supported email steps with simple sequential scheduling. The new system under src/domain/sequence/ is a ground-up redesign that retains the core concept of ordered outreach steps while adding multi-channel support, variant-based A/B testing, a dedicated execution engine with channel handlers, and comprehensive metrics tracking.


Core Concepts

Entity Relationship Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Outreach Sequences                                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                          β”‚
β”‚   SequenceTemplate                                                       β”‚
β”‚   β”œβ”€β”€ 1:N  SequenceTemplateVariant  (A/B test branches)                  β”‚
β”‚   β”‚        └── 1:N  SequenceStep    (ordered steps per variant)          β”‚
β”‚   β”œβ”€β”€ 1:N  SequenceEnrollment       (contacts enrolled in the sequence)  β”‚
β”‚   β”‚        └── 1:N  SequenceStepExecution  (one per step per enrollment) β”‚
β”‚   β”œβ”€β”€ N:M  SequenceRuleset          (via SequenceTemplateRuleset)        β”‚
β”‚   β”œβ”€β”€ N:1  SendingSchedule          (optional linked schedule)           β”‚
β”‚   └── 1:N  SequenceMetrics          (aggregated performance data)        β”‚
β”‚                                                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Entities

SequenceTemplate

The top-level container that defines a reusable outreach sequence.

Field Description
name Display name for the sequence
description Optional long-form description
isActive Whether the sequence accepts new enrollments and processes steps
defaultTimezone Fallback timezone for scheduling
defaultSendingScheduleId FK to the SendingSchedule used by default
enableAbTesting Enables variant-level A/B testing
pauseOnReply Auto-pause enrollment when recipient replies (default: true)
pauseOnMeetingBooked Auto-pause when a meeting is booked (default: true)
pauseOnOutOfOffice Auto-pause when OOO is detected (default: true)
maxEnrollmentsPerDay Throttle for daily enrollment volume
enableMailboxRotation Distribute sends across multiple mailboxes
scheduledStartAt / startTimezone Delayed start for the entire sequence
sendTimeStrategy IMMEDIATELY, SENDER_TIMEZONE, or RECIPIENT_TIMEZONE
tags Freeform tags for organization
totalEnrollments / activeEnrollments / completedEnrollments Denormalized counters

SequenceTemplateVariant

An A/B testing branch within a template. Each variant holds its own set of steps and tracks independent performance counters.

Field Description
sequenceTemplateId Parent template FK
name Variant label (e.g. "Control", "Shorter cadence")
weight Distribution weight (0-100, default 50) for enrollment assignment
isControl Marks the control group in an A/B test
isActive Whether this variant participates in enrollment distribution
enrollmentCount / sentCount / openedCount / clickedCount / repliedCount / bouncedCount / optedOutCount Denormalized performance counters

Computed rates (openRate, replyRate, clickRate, bounceRate) are calculated on-read from the counters.

SequenceStep

A single action in a variant's cadence. Steps are ordered by stepOrder and can target any supported channel.

Field Description
sequenceTemplateVariantId Parent variant FK
stepOrder 1-based execution order
channelType EMAIL, LINKEDIN, PHONE, or ACTION
name Step label
delayInHours Hours to wait before executing (from the previous step or from delayFromStepId)
delayFromStepId Optional FK to a specific step to measure delay from
Email fields emailSubject, emailBody, emailBodyHtml
LinkedIn fields linkedinMessage, linkedinConnectionRequest
Phone fields phoneScript, phoneType (CALL or SMS)
Action fields actionDescription, actionType (TASK, REMINDER, CALENDAR)
dynamicFields JSON object for merge fields / personalization tokens
messageGeneratorId / llmPromptId / llmSettingsId Optional LLM-based content generation
attachmentFileIds File attachments (email steps)
conditions Optional conditional logic (JSON)
trackOpens / trackClicks Email tracking toggles (default: true)
isApproved / approvedAt / approvedByUserId Manual approval gate

SequenceEnrollment

Represents a single contact's journey through a sequence.

Field Description
sequenceTemplateId Template FK
sequenceTemplateVariantId Variant FK (assigned based on weight distribution)
contactId / personId The enrolled contact or person
recipientEmail Resolved email for this enrollment
status Current lifecycle state (see state machine below)
pauseReason Why the enrollment was paused
currentStepId / currentStepOrder Pointer to the active step
enrolledAt / enrolledBy Enrollment origin
startedAt / pausedAt / completedAt / failedAt / optedOutAt Lifecycle timestamps
nextScheduledAt When the next step is due
enrollmentSource MANUAL, API, TRIGGER, CAMPAIGN, or IMPORT
sendingScheduleId / mailboxId Per-enrollment overrides
timezone Per-enrollment timezone
stepsCompleted / stepsFailed / emailsOpened / emailsClicked / emailsReplied / emailsBounced Per-enrollment metrics

SequenceStepExecution

A record of a single step being executed for a single enrollment. One execution per step per enrollment.

Field Description
sequenceEnrollmentId Enrollment FK
sequenceStepId Step FK
stepOrder Snapshot of the step's order at execution time
status Execution status (see statuses below)
channelType Channel type for this execution
scheduledAt / executedAt / completedAt Timing
Email tracking emailId, emailOpened, emailOpenedAt, emailClicked, emailClickedAt, emailReplied, emailRepliedAt, emailBounced, emailBouncedAt, bounceReason
LinkedIn tracking linkedinMessageId, linkedinConnectionRequestId, linkedinSent, linkedinSentAt
Phone tracking phoneCallId, phoneCallDurationSeconds, phoneCallStatus (COMPLETED, NO_ANSWER, BUSY, FAILED)
Action tracking taskId, actionCompleted, actionCompletedAt
retryCount / lastRetryAt Retry tracking
contentSnapshot Frozen copy of the content that was sent

SendingSchedule

Defines when outreach can be sent. Configurable per day of week with start/end times.

Field Description
name / description Schedule label
timezone Base timezone for the schedule
{day}Enabled Boolean toggle per day (Mon-Sun). Weekdays default true, weekends default false
{day}StartTime / {day}EndTime HH:MM send window per day (default 09:00-17:00)
holidays Array of dates to skip
skipHolidays Whether to honor the holidays list (default: true)
minDelayBetweenSendsMinutes Throttle between consecutive sends (default: 60)
useContactTimezone Use the contact's timezone instead of the schedule timezone

SequenceRuleset

Reusable automation rules that can be attached to templates via the SequenceTemplateRuleset junction table.

Field Description
name / description Rule label
ruleType ENROLLMENT_TRIGGER, STEP_CONDITION, PAUSE_TRIGGER, RESUME_TRIGGER, or EXIT_TRIGGER
triggerType For enrollment triggers: CONTACT_CREATED, CONTACT_UPDATED, FIELD_CHANGED, TAG_ADDED, LIST_ADDED, OPPORTUNITY_STAGE_CHANGED, CUSTOM_EVENT, API, MANUAL
config JSON object containing rule-specific parameters
priority Evaluation order when multiple rules of the same type exist
isActive Toggle
isReusable Whether the rule can be shared across templates

SequenceMetrics

Aggregated daily performance data, queryable at the template, variant, or step level, and filterable by channel.

Tracks:

  • Enrollment metrics: created, active, paused, completed, failed, opted-out
  • Step metrics: sent, failed, skipped
  • Email metrics: sent, opened, clicked, replied, bounced (total and unique)
  • LinkedIn metrics: messages sent, connections sent, connections accepted
  • Phone metrics: calls, completed calls, total duration, SMS sent
  • Action metrics: created, completed
  • Calculated rates: open, click, reply, bounce, opt-out
  • Business metrics: opportunities created, meetings booked, revenue generated

Channel Types

Channel Enum Value Description
Email EMAIL Sends emails via Nylas-connected mailboxes. Supports tracking, threading, attachments, and LLM-generated content.
LinkedIn LINKEDIN Sends LinkedIn messages or connection requests via Unipile integration.
Phone PHONE Logs phone calls (CALL) or sends SMS (SMS). Tracks call duration and outcome.
Action ACTION Creates internal tasks (TASK), reminders (REMINDER), or calendar entries (CALENDAR) for the sales rep.

Enrollment Lifecycle

                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚ PENDING β”‚
                  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
                       β”‚  enrollment activated
                       β–Ό
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”Œβ”€β”€β”€β”€ β”‚ ACTIVE  β”‚ ────┐───────────────┐
           β”‚     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜     β”‚               β”‚
           β”‚          β”‚          β”‚               β”‚
     pause β”‚          β”‚ all      β”‚ recipient     β”‚ failure
     (manual,        β”‚ steps    β”‚ opts out      β”‚
      reply,         β”‚ done     β”‚               β”‚
      OOO,           β–Ό          β–Ό               β–Ό
      bounce,   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      rate      β”‚COMPLETED β”‚ β”‚ OPTED_OUTβ”‚ β”‚ FAILED  β”‚
      limit)    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚ PAUSED  β”‚
      β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
           β”‚  resume
           β”‚
           └──────► ACTIVE

Pause reasons: MANUAL, REPLIED, OUT_OF_OFFICE, MEETING_BOOKED, BOUNCED, RATE_LIMIT

Enrollment sources: MANUAL, API, TRIGGER, CAMPAIGN, IMPORT


Step Execution Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Execution Engine Flow                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                      β”‚
β”‚  1. Background job picks up SCHEDULED step executions                β”‚
β”‚                    β”‚                                                 β”‚
β”‚                    β–Ό                                                 β”‚
β”‚  2. Validate enrollment is ACTIVE                                    β”‚
β”‚                    β”‚                                                 β”‚
β”‚                    β–Ό                                                 β”‚
β”‚  3. Check step approval gate                                         β”‚
β”‚     β”œβ”€β”€ Not approved β†’ mark SKIPPED, reschedule next step            β”‚
β”‚     └── Approved β†’ continue                                          β”‚
β”‚                    β”‚                                                 β”‚
β”‚                    β–Ό                                                 β”‚
β”‚  4. Transition status: SCHEDULED β†’ SENDING                           β”‚
β”‚                    β”‚                                                 β”‚
β”‚                    β–Ό                                                 β”‚
β”‚  5. Route to channel handler                                         β”‚
β”‚     β”œβ”€β”€ EmailChannelHandler     (Nylas, mailbox rotation, tracking)   β”‚
β”‚     β”œβ”€β”€ LinkedInChannelHandler  (Unipile, connection requests)        β”‚
β”‚     β”œβ”€β”€ PhoneChannelHandler     (call logging, SMS)                   β”‚
β”‚     └── ActionChannelHandler    (task/reminder/calendar creation)     β”‚
β”‚                    β”‚                                                 β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”                                           β”‚
β”‚              β–Ό           β–Ό                                           β”‚
β”‚          Success      Failure                                        β”‚
β”‚           SENT        FAILED                                         β”‚
β”‚              β”‚           β”‚                                           β”‚
β”‚              β–Ό           β–Ό                                           β”‚
β”‚  6. Update enrollment metrics                                        β”‚
β”‚  7. Reschedule next step (respect delayInHours from completion time)  β”‚
β”‚                                                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step execution statuses: PENDING β†’ SCHEDULED β†’ SENDING β†’ SENT | FAILED | SKIPPED | CANCELLED

Delay Handling

When a step completes (sent or skipped), the engine recalculates the next step's scheduledAt to ensure the configured delayInHours is measured from the actual completion time, not the original schedule. This prevents back-to-back execution after outages or delays.

Approval Gate

Steps with isApproved = false are automatically skipped during execution. The engine marks them as SKIPPED with metadata { skipReason: "STEP_NOT_APPROVED" } and reschedules the following step accordingly.


A/B Testing

When enableAbTesting is turned on for a template:

  1. Multiple variants are created, each with its own set of steps and a weight (0-100).
  2. On enrollment, the system assigns the contact to a variant based on weight distribution.
  3. Each variant independently tracks enrollmentCount, sentCount, openedCount, clickedCount, repliedCount, bouncedCount, and optedOutCount.
  4. Computed rates (openRate, replyRate, clickRate, bounceRate) are derived on-read for real-time comparison.
  5. One variant can be marked isControl to serve as the baseline.

Sending Schedules

Sending schedules control when outreach is delivered.

  • Per-day windows: Each day of the week has an independent enable/disable toggle and start/end time (defaults: Mon-Fri 09:00-17:00, Sat-Sun disabled).
  • Timezone awareness: The schedule operates in a configured timezone. Can optionally use the contact's timezone (useContactTimezone).
  • Holiday handling: An array of dates can be configured as holidays. When skipHolidays is true (default), sends are deferred past holidays.
  • Send throttling: minDelayBetweenSendsMinutes (default: 60) enforces a minimum gap between consecutive sends.

The SendTimeStrategy on the template determines how the schedule is applied:

Strategy Behavior
IMMEDIATELY Send as soon as the delay elapses (no schedule gating)
SENDER_TIMEZONE Gate sends to the schedule window in the sender's timezone
RECIPIENT_TIMEZONE Gate sends to the schedule window in the recipient's timezone

Rulesets & Automation

Rulesets define automated behaviors that can be attached to any template.

Rule Type Purpose
ENROLLMENT_TRIGGER Automatically enroll contacts when a condition is met (e.g. contact created, tag added, opportunity stage changed)
STEP_CONDITION Conditional logic to skip or modify a step based on runtime data
PAUSE_TRIGGER Auto-pause enrollment on events (reply, bounce, OOO detection)
RESUME_TRIGGER Auto-resume paused enrollments
EXIT_TRIGGER Remove contact from sequence on events

Rules are reusable across templates via the SequenceTemplateRuleset junction table and are evaluated in priority order.

Enrollment Trigger Types

Trigger Fires When
CONTACT_CREATED A new contact is created
CONTACT_UPDATED A contact is updated
FIELD_CHANGED A specific field value changes
TAG_ADDED A tag is applied to a contact
LIST_ADDED A contact is added to a list
OPPORTUNITY_STAGE_CHANGED A deal moves to a target stage
CUSTOM_EVENT A custom event fires
API Triggered via the API
MANUAL Manually enrolled by a user

Out-of-Office Detection

The system includes built-in OOO detection with multiple methods:

Method Description
HEADER_AUTO_REPLY Checks email headers for auto-reply indicators
SUBJECT_KEYWORD Matches OOO-related keywords in the subject line
CONTENT_PATTERN Pattern matching on email body content
ML_CLASSIFIER ML-based classification of response content

When OOO is detected and pauseOnOutOfOffice is enabled on the template, the enrollment is automatically paused with reason OUT_OF_OFFICE.


GraphQL API Surface

Queries

Query Description
sequenceTemplate(id) Get a single template with variants, steps, and metrics
sequenceTemplates(filters) List templates with filtering and pagination
sequenceEnrollment(id) Get enrollment details with step executions
sequenceEnrollments(filters) List enrollments for a template
sequenceMetrics(templateId) Get aggregated metrics for a template
sendingSchedule(id) Get a sending schedule
sendingSchedules List all sending schedules

Mutations

Template management:

  • createSequenceTemplate / updateSequenceTemplate
  • activateSequenceTemplate / deactivateSequenceTemplate
  • deleteSequenceTemplate

Variant management:

  • createTemplateVariant / updateTemplateVariant / deleteTemplateVariant

Step management:

  • createSequenceStep / createBulkSequenceSteps
  • updateSequenceStep / deleteSequenceStep
  • reorderSequenceSteps
  • approveSequenceStep

Enrollment management:

  • enrollContactInSequence / enrollContactsInSequence / bulkEnrollContacts
  • pauseEnrollment / resumeEnrollment
  • completeEnrollment / optOutEnrollment
  • unenrollContact

Scheduling:

  • createSendingSchedule / updateSendingSchedule / deleteSendingSchedule

Key Differences from Legacy System

Aspect Legacy (MessageSequence) New (Sequence)
Channels Email only Email, LinkedIn, Phone, Action
A/B Testing Not supported Variant-based with weight distribution
Enrollment tracking Implicit (sequence = one contact) Explicit SequenceEnrollment entity with lifecycle
Execution model Direct send via service Execution engine with pluggable channel handlers
Step execution records Status on the step itself Dedicated SequenceStepExecution per enrollment
Metrics Basic step status counts Dedicated SequenceMetrics entity with daily aggregation per channel
Scheduling Simple delay (hours/days between messages) SendingSchedule with per-day windows, timezone, holidays
Automation Manual enrollment only Rulesets with trigger-based auto-enrollment and pause/resume
OOO detection Not supported Built-in multi-method detection
Mailbox rotation Not supported Configurable rotation across connected mailboxes
Approval workflow Per-sequence flag Per-step approval gate with approval tracking

Codebase Reference

Layer Path
Entities & Enums src/domain/sequence/entity/
Repository interfaces src/domain/sequence/repository/
Repository implementations src/data/models/sequence/
Use cases β€” Templates src/domain/sequence/usecases/template/
Use cases β€” Variants src/domain/sequence/usecases/variant/
Use cases β€” Steps src/domain/sequence/usecases/step/
Use cases β€” Enrollments src/domain/sequence/usecases/enrollment/
Use cases β€” Scheduling src/domain/sequence/usecases/schedule/
Use cases β€” Metrics src/domain/sequence/usecases/metrics/
Execution engine src/domain/sequence/engine/
Channel handlers src/domain/sequence/engine/channel-handlers/
Email services src/domain/sequence/email/
Scheduling services src/domain/sequence/scheduling/
OOO detection src/domain/sequence/ooo/
Metrics aggregation src/domain/sequence/metrics/
Event handlers src/domain/sequence-events/
GraphQL resolvers src/graphql-api/sequence/resolvers/
GraphQL types src/graphql-api/sequence/types/
GraphQL inputs src/graphql-api/sequence/resolvers/inputs/
Background jobs src/worker/queue/sequences-task-queue.ts
Migrations src/data/migrations/20260126* through 20260217*

Troubleshooting

Issue Check Resolution
Steps not executing Enrollment status, template isActive, step isApproved Ensure enrollment is ACTIVE, template is active, and step is approved
Emails not sending Nylas account connection, mailbox status Verify Nylas account is connected and mailbox is ACTIVE (not WARMING_UP or DISABLED)
Steps executing back-to-back delayInHours config, sending schedule Engine reschedules from actual completion time; verify schedule windows
Enrollment stuck in PAUSED pauseReason field Check reason; resume manually or configure RESUME_TRIGGER ruleset
Metrics not updating Background job status, metrics aggregation Trigger recalculateSequenceMetrics or check record-daily-metrics job
A/B test not distributing Variant weight and isActive Ensure at least two active variants with non-zero weights
LinkedIn steps failing Unipile account, connection status Verify Unipile account is connected and active