Agent Skills: Apollo Core Workflow B: Email Sequences & Outreach

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/apollo-core-workflow-b

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/apollo-pack/skills/apollo-core-workflow-b

Skill Files

Browse the full folder contents for apollo-core-workflow-b.

Download Skill

Loading file tree…

plugins/saas-packs/apollo-pack/skills/apollo-core-workflow-b/SKILL.md

Skill Metadata

Name
apollo-core-workflow-b
Description
|

Apollo Core Workflow B: Email Sequences & Outreach

Overview

Build Apollo.io email sequencing and outreach automation via the REST API. Sequences in Apollo are called "emailer_campaigns" in the API. This covers listing, searching, adding contacts, tracking engagement, and managing sequence lifecycle. All endpoints require a master API key.

Prerequisites

  • Completed apollo-core-workflow-a (lead search)
  • Apollo account with Sequences feature enabled
  • Connected email account in Apollo (Settings > Channels > Email)
  • Master API key (not standard)

Instructions

Step 1: Search for Existing Sequences

// src/workflows/sequences.ts
import axios from 'axios';

const client = axios.create({
  baseURL: 'https://api.apollo.io/api/v1',
  headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },
});

export async function searchSequences(query?: string) {
  const { data } = await client.post('/emailer_campaigns/search', {
    q_name: query,       // optional name filter
    page: 1,
    per_page: 25,
  });

  return data.emailer_campaigns.map((seq: any) => ({
    id: seq.id,
    name: seq.name,
    active: seq.active,
    numSteps: seq.num_steps ?? seq.emailer_steps?.length ?? 0,
    stats: {
      totalContacts: seq.unique_scheduled ?? 0,
      delivered: seq.unique_delivered ?? 0,
      opened: seq.unique_opened ?? 0,
      replied: seq.unique_replied ?? 0,
      bounced: seq.unique_bounced ?? 0,
    },
    createdAt: seq.created_at,
  }));
}

Step 2: Get Email Accounts for Sending

Before adding contacts to a sequence, you need the email account ID that will send the messages.

export async function getEmailAccounts() {
  const { data } = await client.get('/email_accounts');

  return data.email_accounts.map((acct: any) => ({
    id: acct.id,
    email: acct.email,
    sendingEnabled: acct.active,
    provider: acct.type,  // "gmail", "outlook", "smtp"
    dailySendLimit: acct.daily_email_limit,
  }));
}

Step 3: Add Contacts to a Sequence

The add_contact_ids endpoint enrolls contacts into an existing sequence. You must specify which email account sends the messages.

export async function addContactsToSequence(
  sequenceId: string,
  contactIds: string[],
  emailAccountId: string,
) {
  const { data } = await client.post(
    `/emailer_campaigns/${sequenceId}/add_contact_ids`,
    {
      contact_ids: contactIds,
      emailer_campaign_id: sequenceId,
      send_email_from_email_account_id: emailAccountId,
      sequence_active_in_other_campaigns: false,  // skip if already in another sequence
    },
  );

  return {
    added: data.contacts?.length ?? 0,
    alreadyInCampaign: data.contacts_already_in_campaign ?? 0,
    errors: data.not_added_contact_ids ?? [],
  };
}

Step 4: Update Contact Status in a Sequence

// Mark contacts as finished or remove them from a sequence
export async function removeContactsFromSequence(
  sequenceId: string,
  contactIds: string[],
  action: 'finished' | 'removed' = 'finished',
) {
  const { data } = await client.post('/emailer_campaigns/remove_or_stop_contact_ids', {
    emailer_campaign_id: sequenceId,
    contact_ids: contactIds,
    // "finished" marks contact as completed; "removed" fully removes
  });

  return {
    updated: data.contacts?.length ?? 0,
  };
}

Step 5: Create and Manage Contacts for Sequences

Contacts must exist in your Apollo CRM before adding to sequences. Use the Contacts API to create them.

// Create a contact in your Apollo CRM
export async function createContact(params: {
  firstName: string;
  lastName: string;
  email: string;
  title?: string;
  organizationName?: string;
  websiteUrl?: string;
}) {
  const { data } = await client.post('/contacts', {
    first_name: params.firstName,
    last_name: params.lastName,
    email: params.email,
    title: params.title,
    organization_name: params.organizationName,
    website_url: params.websiteUrl,
  });

  return {
    id: data.contact.id,
    email: data.contact.email,
    name: `${data.contact.first_name} ${data.contact.last_name}`,
  };
}

// Search your CRM contacts (not the Apollo database)
export async function searchCrmContacts(query: string) {
  const { data } = await client.post('/contacts/search', {
    q_keywords: query,
    page: 1,
    per_page: 25,
  });

  return data.contacts.map((c: any) => ({
    id: c.id,
    name: c.name,
    email: c.email,
    title: c.title,
    company: c.organization_name,
  }));
}

Step 6: Full Outreach Pipeline

async function launchOutreach(
  sequenceId: string,
  leads: Array<{ firstName: string; lastName: string; email: string; title?: string; company?: string }>,
) {
  // 1. Get a sending email account
  const accounts = await getEmailAccounts();
  const sender = accounts.find((a: any) => a.sendingEnabled);
  if (!sender) throw new Error('No active email account found');

  // 2. Create contacts in Apollo CRM (or find existing)
  const contactIds: string[] = [];
  for (const lead of leads) {
    try {
      const contact = await createContact({
        firstName: lead.firstName,
        lastName: lead.lastName,
        email: lead.email,
        title: lead.title,
        organizationName: lead.company,
      });
      contactIds.push(contact.id);
    } catch (err: any) {
      // Contact may already exist — search for them
      const existing = await searchCrmContacts(lead.email);
      if (existing.length > 0) contactIds.push(existing[0].id);
    }
  }

  // 3. Add contacts to the sequence
  const result = await addContactsToSequence(sequenceId, contactIds, sender.id);
  console.log(`Added ${result.added} contacts, ${result.alreadyInCampaign} already enrolled`);

  return result;
}

Output

  • Sequence search via POST /emailer_campaigns/search
  • Email account listing via GET /email_accounts
  • Contact enrollment via POST /emailer_campaigns/{id}/add_contact_ids
  • Contact removal via POST /emailer_campaigns/remove_or_stop_contact_ids
  • Contact creation via POST /contacts and search via POST /contacts/search
  • Full outreach pipeline: create contacts, find sender, enroll in sequence

Error Handling

| Error | Cause | Solution | |-------|-------|----------| | 403 Forbidden | Standard API key used | Sequence endpoints require a master API key | | No email accounts | Inbox not connected | Connect email at Settings > Channels > Email in Apollo UI | | Contact already enrolled | Duplicate enrollment | Check contacts_already_in_campaign in response | | Contact not found | ID does not exist in CRM | Create via POST /contacts first |

Resources

Next Steps

Proceed to apollo-common-errors for error handling patterns.