agentleFS
creatop-gtmcreatop-gtm/apollo-operator/skills/apollo-sequence-builder/SKILL.md

apollo-sequence-builder

Skill8 starsChanged 46 days agoMITView on GitHub

Write a 3-step, 2-variation cold email sequence matched to the persona (ATL or BTL) and build it in Apollo as an inactive draft. Use to write and create outbound sequences on Apollo.

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 6 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Rotate the angle across steps: Never repeat the same value prop.

    line 23
  2. Very specific:

    Before choosing words, decide who you are writing to.

    line 32
  3. Distinctive:

    Write 2 variations of steps 1 and 2, each testing one idea (different pain, different proof, with vs without a lead magnet).

    line 41
  4. Very specific:

    Run sequence-reviewer on the draft.

    line 47
  5. Very specific:

    Read them from apollo_emailer_campaigns_show (MCP), which returns every step, touch, and template id with its content.

    line 87
  6. Distinctive:

    All four are the writer being visible in a message that should sound like a person typing:

    line 102
  7. Very specific:

    If you have a CSV with an opener or email_body column, that column is the input.

    line 123
  8. Distinctive:

    Write the field so it fits the template: If the template supplies the greeting and the CTA, the field holds only the middle.

    line 151
  9. Distinctive:

    Subjects on steps 2 and 3 still thread: If you set a subject there, set the same one as step 1 or leave the step on the template, or you …

    line 171
  10. Distinctive:

    Use it when you want personalization without doing the research yourself.

    line 179
  11. Very specific:

    label_names is a full replacement, not an append: Read the current labels first (the search response returns label_ids, so resolve them …

    line 211

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Apollo Sequence Builder (Message)

Write the sequence, then build it in Apollo as an inactive draft ready for a human to review and activate. Copy is where most outbound dies, so this level is opinionated: short, one idea per email, matched to the person you are writing to, and never activated by the machine.

When to use

  • profile.yaml has the offer, the ICP, and the voice.
  • A graded list exists (List done), or you at least know the persona you are writing to.

The rules that do not move

From ../operator-context/references/outbound-principles.md and the core copy rules. Non-negotiable regardless of framework:

  • 3 steps, 2 variations per step. Day 0, Day 3, Day 7.
  • Word count by step: 60 to 75 for step 1, 45 to 60 for step 2, and step 3 the shortest of the three: one or two sentences, three at most. Step 3 asks a routing question and closes the loop. It does not need setup, and length works against it.
  • One CTA, soft. "Worth a look?" not "Book a 30-minute demo."
  • Never ask for a "quick call." Hard rule, along with "hop on a call", "quick chat", "quick sync", and "jump on a call". "Quick" is the sender pre-apologising for their own ask: it says the request is an imposition, and no call has ever been quick anyway. Minimising the ask makes it easier to decline, not easier to accept. Use a real number or a question about interest instead: "Worth 15 minutes?", "Worth a look?", "Open to hearing more?", "Want me to show you what that looks like?" Naming the actual time is fine. Advertising the smallness of the ask is not.
  • One focus per email: outcome or mechanism, never both.
  • Rotate the angle across steps. Never repeat the same value prop.
  • Subject: lowercase, 1 to 3 words. Reused down the thread (steps 2 and 3 auto-reply in-thread), but a different angle or offer usually earns its own subject (see step 5).
  • Plain text. No em dashes. No buzzwords (leverage, synergy, revolutionary, best-in-class). Both read as machine-written. Beyond those two, write in the operator's house style rather than imposing one.
  • Give every merge field a fallback. A contact with no first name must not receive "Hi ,". Set a default that reads naturally, or exclude the records missing that field.
  • Step 3 is a routing question or a soft breakup. No new pitch.

Process

1. Match the person (ATL or BTL)

Before choosing words, decide who you are writing to. Executives (VP, C-level, director) and managers/ICs read completely different emails. See references/atl-btl.md. This decides length and language for the whole sequence. Pull seniority from the list or profile.yaml.

2. Pick the angle

Choose a framework that fits the offer and the person from references/frameworks.md (Do the Math, Ask Before Pitch, Challenge of Similar Companies, and others). One angle per email, a different one each step.

3. Write the three steps

  • Step 1 (Day 0): one focus (a pain, an outcome, a question, or specific proof), a personalized opener, one soft CTA.
  • Step 2 (Day 3, same thread): context and credibility, a different angle, a real case snippet, and materially shorter than step 1.
  • Step 3 (Day 7, same thread): routing ("am I reaching the right person?") or a soft breakup. No new pitch.
  • Write 2 variations of steps 1 and 2, each testing one idea (different pain, different proof, with vs without a lead magnet).

4. Personalize the opener honestly

The opener should read like you spent ten minutes on this person. Claude does the research (reads the site, finds a real, specific fact) and writes one suggestive opening line, merged per lead. Do not rely on a black-box AI opener, and never fake specificity. (Apollo has its own per-recipient AI opener, has_personalized_opener, which spends credits; our default is to write the opener ourselves so a human can approve it.)

5. Review, then build

Run sequence-reviewer on the draft. Fix what it flags. Then build it in Apollo with apollo_sequences_create:

  • active: false. Always. The machine never turns a sequence on.
  • 3 auto_email steps. Step 1 is a new_thread; steps 2 and 3 are reply_to_thread (same thread, no subject, keeps the conversation).
  • Delays are relative to the previous step: step 1 wait_time: 0, step 2 wait_time: 3, step 3 wait_time: 4 (that is Day 7, not Day 11). This is the mistake everyone makes.
  • 2 emailer_touches per step (the A/B variants). creation_type: manual (a human wrote it). Tone from the profile voice (Direct, Formal, or Casual).

The exact shape, verified live 2026-08-31. Each step nests a template inside a touch. Sending the variants as emailer_templates on the step is rejected with 422 Step 1 (auto_email) requires a template. Include an emailer_touches entry with a template.

{"emailer_steps":[
  {"type":"auto_email","wait_time":0,"wait_mode":"day",
   "emailer_touches":[
     {"emailer_template":{"subject":"pipeline","body_html":"<div>…</div>"}},
     {"emailer_template":{"subject":"pipeline","body_html":"<div>…</div>"}}]},
  {"type":"auto_email","wait_time":3,"wait_mode":"day","emailer_touches":[…]},
  {"type":"auto_email","wait_time":4,"wait_mode":"day","emailer_touches":[…]}]}

Leave subject empty on steps 2 and 3 on the CLI so they reply in-thread rather than starting a new one. On MCP, apollo_sequences_create rejects an empty subject on any step ("Please make sure your template has a non-empty subject before turning it on", 2026-11-08, on an inactive create), so repeat step 1's subject there instead. --schedule-id comes from apollo sequences schedules. A sequence created without --active lands inactive, which is what you want: create, verify, then activate deliberately.

You can read the copy back now, but not on every lane. apollo_emailer_campaigns_show (MCP) returns every step, touch, subject, and body, and GET /emailer_campaigns/{id} returns the same with a master API key, both verified 2026-09-14. The CLI lanes still cannot: the create response, apollo sequences search, and the MCP campaign search all report touches: 0 on every step, and GET /emailer_campaigns/{id} returns 403 on the CLI's OAuth token. So after a CLI create, read the sequence back over MCP before anything else.

A human still opens the sequence before activation. Reading the bodies back proves they landed; it does not approve them. Do not treat a successful create, or a clean read-back, as sign-off. The failure mode is sending wrong-bodied emails to a real list, and it is unrecoverable once sent.

Always hand back the sequence URL. Every create, every update.

Because approval happens by eye, the operator needs a link, not an id. End every create and every update by printing it:

https://app.apollo.io/#/sequences/<sequence_id>

Say plainly what it is for: open this and confirm the bodies are right before anyone is enrolled. An id alone makes the human go hunting, and a step they have to hunt for is a step they will skip. This is the whole handoff between what a script can do and what only a person can check, so make it one click.

(An agent can now summarise the saved sequence from apollo_emailer_campaigns_show next to the link, which makes the human check faster. It does not replace it.)

Updating an existing sequence needs the step ids

sequences update replaces the full set of steps, and sending your original payload back will fail with 422 Missing Step 1 for this sequence. Each step in the payload must carry the id of the step it is replacing.

Read them from apollo_emailer_campaigns_show (MCP), which returns every step, touch, and template id with its content. apollo sequences search expands emailer_steps with id, position, type, wait_mode, and wait_time but never the touches, so on the CLI alone step structure is readable and step content is not.

Two consequences. On the CLI, keep the steps payload in a local file as the source of truth, because the CLI cannot read the bodies back; on MCP, read them with apollo_emailer_campaigns_show first. Either way, re-send every step on every update, with its id, since an omitted step is a deleted step.

Shape the greeting to the step

The greeting tightens as the thread goes on, and this is what makes three emails read as one conversation rather than three separate approaches:

StepShape
1Block greeting on its own line, then a blank line, then the body
2Hi again {{first_name}}, with the body continuing on the same line
3Tighter still, and it can drop the greeting word entirely: {{first_name}}, are you the right person…

Four things to cut from every follow-up

All four are the writer being visible in a message that should sound like a person typing:

  • Throat-clearing lines. "Following up on this." / "Quick follow-up." / "Last one from me." These spend the most valuable line in the email on nothing, and Hi again has already said it.
  • Labelling phrases. "How it works:" / "The part that matters is". Say the thing rather than announcing that you are about to.
  • Setup-and-contrast. "Most vendors hand over a report. We hand over the asset." The contrast is your reasoning, not their need. Cutting it usually takes 20 to 30 words out of a follow-up and loses nothing.
  • The summarising payoff. A sentence explaining a benefit you just stated ("That is why month six beats month one") is admiring your own point. Trust the reader.

Contractions are fine and usually help. Formal follow-ups read like documents.

  • Format the body so it breaks into paragraphs. Write 2 to 3 short paragraphs, not one wall of text. Separate them with <br><br> inside a single <div>, like <div>First paragraph.<br><br>Second paragraph.<br><br>Closing line and CTA.</div>. This is not cosmetic: live-tested on Apollo, <br><br> is the only structure Apollo keeps as real line breaks in the sent text. Multiple <div> blocks collapse to a single space and <p> tags collapse to nothing, so both send as a run-on paragraph. Never dump the whole email into one flat <div>.
  • Subjects: reuse down the thread, vary across angles. Two different axes, do not confuse them. Down a thread (step 1 to 2 to 3) the subject carries so the follow-ups stay threaded, and Apollo handles this for you: steps 2 and 3 are reply_to_thread with an empty subject. Across A/B variants that test different angles or offers, the norm is a different subject per variant, because a new angle usually earns its own subject line. Each lead still threads correctly under whichever subject it got on step 1. Only reuse the same subject across variants when you already know which subject lines win (usually after running outbound for a while), which is the exception, not the default.

6. Hand to a human to activate

Report the sequence id and a short step summary. Activation is apollo_emailer_campaigns_approve, and only a person runs it, after confirming the sender mailbox, the schedule, and the copy. Recommend, do not activate.

Fully individual emails at scale (custom-field merge)

Standard merge tags ({{first_name}}, company, title) personalise the surface of a shared template. There is a second mechanism that personalises the entire body, and most people do not know it exists.

Store each contact's own written email body in a long-text contact custom field, then reference that field by name as a merge variable inside the sequence step. At send time Apollo resolves the variable per contact, so every enrolled person receives a genuinely different email while the sequence stays a single object with a single set of steps.

If you have a CSV with an opener or email_body column, that column is the input. The mechanic is: column → custom field → merge variable in the template.

The four steps

1. Create the custom field, once. It must be long-text / multi-line, on the contact modality. apollo_fields_index only lists fields, so on MCP alone you still cannot create one. The cheapest route is now apollo fields create (CLI, v2.1.0+), which needs no API key: before that release this step forced you onto POST /fields over REST, and that is no longer true. POST /fields (lanes 3 and 4) and the Apollo UI both still work. Note both the field's id and its name: you need the id to write values and the name to reference it in copy.

Trap, verified 2026-08-31: the two surfaces return the id in different shapes. apollo fields create returns it modality-prefixed, as contact.66e34b81740c50074e3d1bd4. apollo fields custom and apollo_fields_index return the same field bare, as 66e34b81740c50074e3d1bd4. typed_custom_fields wants the bare form, and the MCP schema validates it against ^[a-f0-9]{24}$, so the prefixed form is rejected outright. Piping the create output straight into a contact write therefore fails. Strip everything up to and including the dot, or just re-read the field from the index after creating it.

2. Write each contact's body into that field. Pass typed_custom_fields keyed by field id on apollo_contacts_create, apollo_contacts_bulk_create (up to 100 per call), or apollo_contacts_update for contacts that already exist.

{ "email": "…", "first_name": "…",
  "typed_custom_fields": { "<field_id>": "Saw you're hiring two SDRs in Austin, most founders…" } }

3. Reference it by name in the step template. In body_html, write {{custom_email_body_seq_1}} using the field's name, not its id. Mix it with normal tags freely:

<div>Hi {{first_name}},</div><div><br /></div>
<div>{{custom_email_body_seq_1}}</div><div><br /></div>
<div>Worth 15 minutes?</div>

4. Set the values before enrolling. The merge resolves at send time, but a contact enrolled with an empty field will send an email with a hole in it.

Cautions

  • A blank field renders as nothing, silently. Verify coverage before activating: every enrolled contact needs a value, or the surrounding copy has to still read correctly without it.
  • Write the field so it fits the template. If the template supplies the greeting and the CTA, the field holds only the middle. Decide that boundary once and write every value to it.
  • Approval moves up a level. Nobody reads 800 individual emails, so review happens at the template and process level, with a spot-check across tiers. That is the same standard this library already applies to AI-assisted copy.
  • Individual bodies do not rescue a weak offer. A thousand bespoke emails to a bad list is still a bad campaign, just a more expensive one.

The direct route: each contact's own copy on the step itself

Since 2026-11-08 the MCP can store a contact's own subject and body on a sequence step, with no custom field in between. Three tools do it:

  • At enrollment: apollo_emailer_campaigns_add_contact_ids takes step_content_overrides, one entry per contact, each naming the steps (emailer_step_id, from apollo_emailer_campaigns_show) and the subject and body_html that contact gets instead of the shared template. A step you leave out stays on the template. On a manual email step it pre-fills the rep's draft instead of sending.
  • Read it back: apollo_emailer_campaigns_step_content returns what is stored per contact and step. It is free and read-only. An empty step_contact_contents means everyone gets the template (that is what it returned on our live sequence on 2026-11-08).
  • Change it after enrollment: apollo_emailer_campaigns_update_step_content sets or clears one contact's content on the steps you name and leaves the others alone. It cannot edit a step already sent to that contact, and it reports that step under not_written.

Verified live 2026-11-08 on an inactive test sequence: an enrollment with an override on step 1 reported it under step_content_overrides_result.written, step_content read it back with source: "agent", an update set step 2 and cleared step 1 in one call, and a second enrollment of the same contact was skipped with not_written (the reason given was contacts_active_in_other_campaigns, although it was the same sequence). No credits.

Five rules, four of them from Apollo's own descriptions:

  • Overrides only apply to contacts the call actually enrolls. Anyone already in the sequence is skipped and keeps what they had, so re-sending an enrollment does not update copy. Use update_step_content for that.
  • An enrollment replaces all of that contact's prior overrides for the sequence, and an update overwrites a step's stored copy. Neither can be undone, so read the current content back first if any of it is worth keeping.
  • Write the resolved text. Merge tags are not substituted inside override content, so {{first_name}} would arrive as typed. Put the real name in.
  • Keep it plain text. The field is body_html, but the cold-email rule does not change: wrap lines in <div> exactly as the shared template does, no images, links, or styling.
  • Subjects on steps 2 and 3 still thread. If you set a subject there, set the same one as step 1 or leave the step on the template, or you break the thread.

Which route to use. The custom-field merge keeps one shared template with a single hole in it, so the greeting, CTA, and signature stay identical for everyone and only the middle varies. Step content replaces the whole subject and body per contact, which is more control and more to review. Either way, approval moves up to the template and process level with a spot-check, as above, and nothing is personalized until the list is verified. Read the stored content back before activating, every time.

The alternative: Apollo's own AI opener

emailer_touches accepts has_personalized_opener: true, which has Apollo generate a per-recipient opening line, plus generation_tone (Direct, Formal, Casual) and a required personalized_opener_fallback_option (skip, or use_generic with a generic_personalized_opener). It uses credits.

Use it when you want personalization without doing the research yourself. Use the custom-field route when you want control over exactly what each person reads, which for a high-value list is usually the better trade.

A third option is in the schema, and did not work when tested. apollo_sequences_create and update accept generation_options on a touch, with types ai_full_email, ai_follow_up_email, and ai_last_pitch_email, meant to have Apollo write the whole body, plus template_type: "prompt_template" for a reusable prompt. Tested 2026-09-14: a create with ai_full_email and a matching {{ai_email_body}} merge variable in the body was accepted, but the saved touch came back with generation_options: [] and has_ai_variables: false on both the create response and an independent apollo_emailer_campaigns_show. The option was silently dropped, nothing was generated, and no ai_credit was spent. It may need an account feature this test account lacks. Always read the touch back after setting it, because a create that succeeds tells you nothing about whether the AI option survived.

Apollo's agent can draft the whole sequence. Offer it.

apollo_agent_draft_sequence_copy is Apollo's own drafting, review, and optimisation of sequence copy, grounded in the Context Center. It overlaps this skill directly, so say the option out loud rather than writing by hand as if it did not exist: an operator with a filled Context Center who wants a first draft fast may prefer it, and that is their call. It is reachable on the ordinary connector since 2026-11-08, and so is apollo_agent_optimize_sequences, which diagnoses a live sequence and rewrites its copy once approved. Treat an optimisation as an experiment, not a fix: run it through experiment-design so one change is measured at a time.

Whatever it produces is a draft, not an approved sequence. It goes through sequence-reviewer like any other copy, the word bands and the step 3 rule still apply, and a human still approves before activation. Apollo's copy knows the product; it does not know the deliverability history of the mailboxes it will send from. Stance: operator-context.

Editing a live sequence: the diff is destructive

Kill switch, verified end to end 2026-08-31, on a live sequence with contacts enrolled. apollo sequences abort --id <id> flips a running sequence from active: true to active: false immediately, and returns the campaign object with a contact_statuses breakdown so you can see what was in flight. It does not archive, so the sequence and its enrollment survive: reactivating with sequences update --steps-file <file> --active resumes it, and the remaining steps continue on schedule.

On a sequence that is already inactive it refuses cleanly with 422 The sequence is already inactive. rather than erroring obscurely or silently succeeding, so it is safe to call speculatively when you are unsure of the state.

Prove it before you need it. Run abort against the real sequence, confirm active flips, then reactivate. Two minutes, and it turns the kill switch from something you believe in into something you have used. During an actual incident is the wrong time to discover the command needs a flag you do not have.

MCP can stop a sequence too, verified 2026-09-14. apollo_sequences_update with active: false flips a running sequence to status_reason: manual_pause, as long as every step, touch, and template is echoed back with its id. The diff rules below are exactly why that is dangerous, so read the sequence with apollo_emailer_campaigns_show first, and prefer abort in a real incident.

apollo_sequences_update uses declarative diff semantics, and this is the single easiest way to destroy a sequence by accident. The emailer_steps array you send is the full intended state after the call, not a patch:

  • A step with an id that matches → updated in place.
  • A step without an id → created.
  • An existing step whose id you omit → deleted.

The same applies per touch inside each step. So "change the subject line on step 2" means sending every step with its existing id, not just step 2. Send step 2 alone and you have deleted steps 1 and 3.

Three more traps in the same call:

  • position is required on every step and must reflow as a complete 1..N.
  • active is required, so mirror the current value unless you intend to change it. Passing true starts real sends.
  • label_names is a full replacement, not an append. Read the current labels first (the search response returns label_ids, so resolve them via apollo_labels_index) and send the complete intended set. Passing only the new label erases the others.

Always fetch current state first (apollo_emailer_campaigns_search), show a before-and-after of what will be added, changed, removed, or reordered, and get a yes. If the sequence is active with contacts enrolled, say plainly that step changes can affect people mid-sequence.

Optional: multichannel

Email-only is a complete campaign. If you want to add LinkedIn, call, or manual steps to this sequence and work the resulting task queue, see ../apollo-multichannel. It is opt-in and does not change any of the rules above.

Common mistakes

  • Selling the outcome and the mechanism in one email. Pick one.
  • A new pitch in step 3. It is for routing or a graceful exit.
  • Absolute delays. Day 7 is wait_time: 4 after a Day 3 step, not 7.
  • Activating without a human. Never.
  • A "personalized" opener that is generic ("love what you're doing at {{company}}"). That is theater, and it reads like it.
  • Dumping the whole email into one <div>. It sends as a wall of text. Break it with <br><br> (see step 5).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.