Concepts

Sequences & angles

Multi-step LinkedIn campaigns as an ordered steps array, with messaging angles as the A/B-testing primitive.

A sequence is an ordered steps array - typically a connection request, an acceptance condition, then a few spaced follow-ups. Create with create_sequence, edit with update_sequence, read (and get stats) with get_sequence.

{
  "messaging_angles": ["quick_question", "worth_a_look"],
  "steps": [
    { "id": "view", "type": "view_profile" },
    { "id": "invite", "type": "send_connection_request",
      "message": "Hi {{first_name}} - would love to connect.",
      "variants": [
        { "id": "quick_question", "message": "Hi {{first_name}} - quick question." },
        { "id": "worth_a_look", "message": "Hi {{first_name}} - worth a look?" }
      ] },
    { "id": "c_accept", "type": "condition",
      "check": "linkedin.accepted", "monitor": "7d",
      "yes": "li_msg", "no": "fallback" },
    { "id": "li_msg", "type": "send_message",
      "message": "Thanks for connecting.", "goto": "stop" }
  ]
}

Step types

typeNotes
view_profileSends a visible "viewed your profile" notification (that's the point)
react_to_recent_postTargets the most recent post; skips (and advances) if they have none
send_connection_requestNote capped at 300 chars. Pre-checks the connection - an already-connected lead soft-succeeds and a following condition short-circuits down yes
send_messageCapped at 8000 chars. If the enrollment already opened a chat, replies in that thread instead of starting a new one
send_inmailBody capped at 1900, subject 200. Requires a seat with InMail
condition{"check": "linkedin.accepted", "monitor": "7d", "yes": id, "no": id} - timeout takes the no branch

Steps run top-to-bottom; delay pauses before a step ("10m""30d"), goto: "stop" ends the flow early.

Template variables

{{first_name}}, {{last_name}}, {{full_name}}, {{headline}}, {{company_name}}, {{title}}, {{public_identifier}}, {{profile_url}}, plus {{lead.<field>}} and {{custom.<key>}}. Unknown variables are rejected at create/update time - they would otherwise be sent verbatim to the prospect.

Empty text is never sent. Missing values are checked at enroll time (per-person missing_variables in failed[]), again at send time as a backstop (the entry cancels instead of going out with a hole), and on every one-off send.

Angles & A/B testing

Angles are the copy-testing primitive. The contract, end to end:

  1. Declare on the sequence. messaging_angles: ["pain-point", "social-proof"], with an optional default_angle that must be in the list. This list is the single source of truth - every variant id and every enrollment angle is validated against it.

  2. Carry variants on steps. Any step whose copy should differ per angle gets variants whose ids match the declared angle names. One angle per lead, sequence-wide: an enrollment holds exactly one selected_angle, and every step with a matching variant uses it - reuse the same ids across steps and a lead gets a coherent pitch on the opener and the follow-ups. Variants are message copy only (message, subject, reaction) - no delay in variants.

  3. Assign at enrollment.

    • angle on add_to_sequence / add_lead_group_to_sequence - one angle for the enrollee(s); an undeclared angle is a validation_error.
    • angle_split on add_lead_group_to_sequence - {"pain-point": 50, "social-proof": 50} splits the batch deterministically (smooth weighted round-robin over enrollment order - the same call lands the same split; 60/40 over 5 people = 3/2 exactly). Weights are proportional, need not sum to 100, and are mutually exclusive with angle. Skipped people don't consume split slots; the result reports per-person angle in enrolled[] plus angle_counts.
    • No angle at all - everyone gets default_angle (or the first declared angle). A deterministic fallback, not a split: an angle-less bulk enroll is not an A/B test.

    update_lead_messages {angle} re-assigns a live enrollment and re-bakes the queued unsent send.

  4. Read results per angle. get_sequence include_stats=true returns by_angle: { angle: { total, active, replied, exited, reply_rate_pct } }. Declared angles always appear (zeros = arm not enrolled yet).

Testing flow or length instead of copy (different delays, 5 vs 3 steps): variants can't express that - create two sequences and split the group between them (the people param on add_lead_group_to_sequence narrows to an intersection, the safe way to split one group), then compare their include_stats.

Enroll-time copy

Copy resolution at send time: message_overrides.by_step → variant for the enrollment's angle → step default message. Bulk re-enrolls skip people whose prior enrollment ended in replied (previously_replied) - a reply is never followed by more cold touches from a routine re-enroll.

Lifecycle & pacing

Sequences are born active - create_sequence never leaves a draft. update_sequence status="paused" is a real pause: queued sends stop dispatching and wait timers freeze, both resuming untouched on active. remove_from_sequence is the permanent per-person cancel; stop_sequence_and_send stops and sends a manual message instead.

Enrolling is not an immediate send: the first touch waits out start_delay_minutes (default 30), and outreach paces at roughly 20–30 sends per day per LinkedIn seat (product-tier caps, ramp-up multipliers for new accounts, working-hours windows, and per-action cooldowns all apply). Copy stays editable via update_lead_messages until each send dispatches. A reply on any channel always stops the enrollment.

Multiple sequences sharing one seat split its caps fairly (least-recently-served rotation), and each account hosts at most a handful of concurrent sequences - enrolling a new sequence onto a full account is rejected with a validation_error naming the cap.

On this page