# Linkedin Comment To Outreach Gsheets

> Use when the user provides a LinkedIn post URL and wants to run the full pipeline from commenters to a live cold outreach campaign on Instantly, storing leads in Google Sheets instead of Airtable. Triggers on phrases like "linkedin comment to outreach gsheets", "scrape and email with sheets", "linkedin pipeline google sheets", or any request combining LinkedIn post scraping with cold email outrea…

- **Type:** Skill
- **Install:** `agentstack add skill-felipesalinasr-fsr-stack-linkedin-comment-to-outreach-gsheets`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [felipesalinasr](https://agentstack.voostack.com/s/felipesalinasr)
- **Installs:** 0
- **Category:** [Web & Browser](https://agentstack.voostack.com/c/web-and-browser)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [felipesalinasr](https://github.com/felipesalinasr)
- **Source:** https://github.com/felipesalinasr/fsr-stack/tree/main/skills/linkedin-comment-to-outreach-gsheets

## Install

```sh
agentstack add skill-felipesalinasr-fsr-stack-linkedin-comment-to-outreach-gsheets
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# LinkedIn Comment to Outreach Pipeline (Google Sheets)

## Overview

End-to-end orchestrator: LinkedIn post URL in, paused Instantly cold email campaign out. Leads are stored in Google Sheets (not Airtable), enriched via Apollo + Hunter.io fallback, sequenced, and loaded into Instantly. Every external tool call goes through Composio first when available. Every phase maximizes parallelism to finish faster without cutting corners.

One key difference from the Airtable version: Google Sheets can write hundreds of rows in a single API call, so Phase 2 collapses from "4 parallel batch-loading agents" to one bulk write. That time savings compounds.

Another difference: when total enrichment (Apollo + Hunter.io) returns fewer than 20 leads with verified emails, the sequence-writing phase runs on autopilot -- no interactive review, just write all 3 emails and move straight to Instantly. For 20+ leads, the user co-writes each email as usual.

**Hunter.io fallback:** After Apollo enrichment, contacts that matched a person but have no email are sent to Hunter.io's Email Finder in parallel (8 concurrent threads). This typically recovers 10-20% of the missing emails, boosting total email coverage by 5-15 percentage points. Only contacts with company info are searched -- Hunter requires a company or domain to work.

## When to use

- User provides a LinkedIn post URL and wants to reach out to its commenters via Google Sheets
- User asks to "run the linkedin pipeline with sheets", "scrape and email", or "linkedin comment to outreach gsheets"
- User wants the complete path from a LinkedIn post to a live Instantly campaign, stored in Google Sheets
- User needs commenters scraped, stored in Sheets, enriched with Apollo, sequenced, and loaded into Instantly in one flow

## When NOT to use

- Targeting people who *reacted* to a post (use `linkedin-reaction-to-outreach` instead)
- User specifically wants Airtable (use `linkedin-comment-to-outreach` instead)
- Just scraping commenters with no outreach (use `linkedin-comment-scraper` directly)
- Just enriching an existing list (use `apollo-enrichment` directly)
- Just writing a cold email sequence without a lead source (use `cold-email-sequence` directly)
- Just creating an Instantly campaign from leads you already have (use `instantly-campaign` directly)

## Prerequisites

- **Composio toolkits connected:** `googlesheets`, `apollo`, `hunter`, `instantly`
- **Apify MCP server:** Connected with `mcp__apify__*` tools available (Composio fallback: `APIFY_RUN_ACTOR_SYNC`)
- **User input needed for Phase 4 (if 20+ leads):** Social proof, product description, CTA preferences, sender name

Before starting, verify each Composio toolkit is connected. If any is missing, use `composio link  --no-wait` and present the redirect URL to the user. Do all missing connections in parallel -- don't chain them sequentially.

## Core Principles

### 1. Composio First

For every external tool call, try the Composio tool slug first. Fall back to MCP or direct API only if Composio fails or the toolkit isn't connected. This applies universally:

| Service | Composio slug (primary) | Fallback |
|---------|------------------------|----------|
| Apify | `APIFY_RUN_ACTOR_SYNC` | `mcp__apify__call-actor` |
| Google Sheets | `GOOGLESHEETS_*` (see slug table below) | None -- Sheets IS the chosen platform |
| Apollo | `APOLLO_PEOPLE_ENRICHMENT` (parallel single calls) | `APOLLO_BULK_PEOPLE_ENRICHMENT` (less reliable) |
| Hunter.io | `HUNTER_EMAIL_FINDER` (parallel single calls) | None -- Hunter IS the fallback |
| Instantly | `INSTANTLY_*` (see slug table below) | Direct REST API via curl |

### 2. Maximum Parallelism

Within every phase, run independent operations concurrently. Between phases, pre-fetch data for the next phase whenever possible. Specific opportunities are called out in each phase below.

### 3. Auto-Mode for Small Lists

If Apollo enrichment returns fewer than 20 leads with verified emails, skip the interactive email-writing review. Write all 3 emails using the James Shields framework and the context gathered in Phase 1, then proceed directly to Instantly. The user reviews the final campaign in Instantly's dashboard instead of approving each email in chat.

## The Pipeline

```
LinkedIn Post URL
       |
       v
[Phase 1: SCRAPE]           -- Apify via MCP or Composio, deduplicate
       |
       v
[Phase 2: STORE]            -- Create Google Sheet, bulk write all rows
       |
       v
[Phase 3: ENRICH (Apollo)]  -- Apollo parallel single enrichment, batch-update Sheet
       |                       (pre-fetch Instantly accounts here)
       v
[Phase 3.5: ENRICH (Hunter)]-- Hunter.io fallback for contacts Apollo missed emails on
       |                       (parallel single calls, 8 threads, update Sheet)
       v
[Phase 4: WRITE]            -- Co-write 3 emails (or auto-write if = 50` to avoid low-confidence guesses.

Name splitting: same rule as Apollo -- split `fullName` on the FIRST space only.

**Why parallel single calls:** Same rationale as Apollo. Hunter has no reliable bulk endpoint via Composio. Individual calls at 8 concurrent threads give the best throughput and visibility into per-person results.

Expected recovery rates:
- 10-20% of Apollo no-email contacts will get an email from Hunter
- Overall pipeline email coverage typically jumps 5-15 percentage points
- LATAM/emerging market recovery rates may be lower (5-10%)

Collect results into `./output/hunter_results.json`:
```json
{
  "idx": 15,
  "row": 17,
  "fullName": "Jane Smith",
  "email": "jane@company.com",
  "score": 85,
  "source": "hunter",
  "profileUrl": "https://www.linkedin.com/in/janesmith"
}
```

### Step 3: Merge results and update Sheet + Apollo

**3a -- Merge into apollo_contacts.json:**
For each Hunter result with an email, update the corresponding record in `./output/apollo_contacts.json` to add the email (mark `email_source: "hunter"` to distinguish from Apollo-found emails).

**3b -- Batch-update Google Sheet:**
Same pattern as Phase 3 Step 3 C1. For each newly found email, update:
- `J` (Lead Status) = "Enriched (Hunter)"
- `K` (Email) = Hunter email

Use `GOOGLESHEETS_UPDATE_VALUES_BATCH` with **camelCase** `valueInputOption`.

**3c -- Create Apollo contacts for new emails:**
Add newly found contacts to Apollo using `APOLLO_CREATE_BULK_CONTACTS` (max 100 per batch, no `label_names` at root level -- put inside each contact object).

Run 3b and 3c in parallel.

### Step 4: Final count

Re-count total emails (Apollo + Hunter). This combined count determines whether Phase 4 runs in auto-mode or interactive mode.

**Checkpoint:** "Hunter.io recovered X additional emails. Total: Y out of Z people now have emails (W% combined match rate). Y contacts ready for outreach."

---

## Phase 4: Write Email Sequence

### Auto-mode (fewer than 20 leads with emails)

Small lists don't justify a multi-round interactive review. Write all 3 emails yourself using the James Shields framework:

1. Gather context from earlier phases: the trigger (the LinkedIn post), the author, the audience profile (from headlines/companies), and any product/offer info from the sales context.
2. If product, social proof, and offer are not in the sales context, ask the user ONE consolidated question with all missing items.
3. Write all 3 emails (Day 0 opener, Day 3 follow-up, Day 7 breakup) following the rules in `cold-email-sequence`.
4. Save to `{campaign-name}-sequence.md`.
5. Show the user the complete sequence and say: "Wrote 3 emails for your X leads. Loading into Instantly now. You can tweak the copy in the Instantly dashboard before activating."

### Interactive mode (20+ leads with emails)

Follow the `cold-email-sequence` skill procedure exactly: gather inputs, draft Email 1, get approval, draft Email 2, get approval, draft Email 3, get approval, save.

**James Shields rules (both modes):**
- Personalize subject (not body), lowercase, 2-4 words
- 3 sentences + PS per email
- Low-friction CTA ("Reply 'I'm in'"), never a booking link
- No em dashes, no exclamation points, no formatting
- NEW social proof for each email (never repeat)
- Follow-up subjects: empty string (same thread)

**Checkpoint (interactive only):** "Sequence locked. Ready to load into Instantly."

---

## Phase 5: Launch on Instantly

### Step 1: Resolve credentials

Try Composio first:
1. Is the `instantly` Composio toolkit connected? (If you already ran `INSTANTLY_LIST_ACCOUNTS` in Phase 3, you know the answer.)
2. If not connected, use `composio link instantly --no-wait` and present the redirect URL.
3. If Composio unavailable, check `INSTANTLY_API_KEY` env var.
4. Last resort: ask for the API key in chat (ephemeral, session-only, never written to disk).

### Step 2: Create campaign

Sanitize ALL email bodies first -- replace every `&` character with "and" (Instantly silently drops bodies containing `&`).

**CRITICAL Composio/Instantly field notes:**
- Do NOT include `delay_unit` in sequence steps -- the Composio schema doesn't support it. The `delay` field is already in days.
- Timezone: pick one appropriate for the audience. Not all IANA timezone strings work with Composio's Instantly integration. Known working: `America/New_York`, `America/Chicago`, `America/Denver`, `America/Los_Angeles`, `America/Bogota`, `America/Sao_Paulo`, `Europe/London`, `Europe/Berlin`. Known rejected: `America/Vancouver`. When in doubt, use the major city timezone closest to the audience.
- Follow-up email subjects: use empty string `""` to thread under the original subject.

```
composio execute INSTANTLY_CREATE_CAMPAIGN -d '{
  "name": "LinkedIn - {Author} {Topic}",
  "campaign_schedule": {
    "schedules": [{
      "name": "Weekday Schedule",
      "timing": { "from": "08:00", "to": "17:00" },
      "days": { "0": true, "1": true, "2": true, "3": true, "4": true, "5": false, "6": false },
      "timezone": "America/New_York"
    }]
  },
  "sequences": [{
    "steps": [
      { "type": "email", "delay": 0,
        "variants": [{ "subject": "subject line", "body": "Email 1 body" }] },
      { "type": "email", "delay": 3,
        "variants": [{ "subject": "", "body": "Email 2 body" }] },
      { "type": "email", "delay": 4,
        "variants": [{ "subject": "", "body": "Email 3 body" }] }
    ]
  }],
  "email_list": ["sender@domain.com"],
  "stop_on_reply": true,
  "text_only": true,
  "link_tracking": false,
  "open_tracking": true,
  "daily_limit": 50,
  "daily_max_leads": 50,
  "email_gap": 5,
  "random_wait_max": 10
}'
```

**Delay math:** Delays are RELATIVE to the previous step, not absolute from Day 0. So for Day 0 / Day 3 / Day 7: step 1 delay=0, step 2 delay=3, step 3 delay=4 (because 3+4=7).

Save the returned campaign `id`. Verify all step bodies are non-empty (ampersand check).

### Step 2.5: Verify the campaign was created correctly

```
composio execute INSTANTLY_GET_CAMPAIGN -d '{
  "id": "{CAMPAIGN_ID}"
}'
```

**CRITICAL:** This endpoint uses `id` as the parameter name, NOT `campaign_id`. Check that all 3 email step bodies are populated and the schedule/timezone look correct.

### Parallel block D (run together):

**D1 -- Bulk load leads:**
```
composio execute INSTANTLY_ADD_LEADS_BULK -d '{
  "campaign_id": "{CAMPAIGN_ID}",
  "leads": [
    { "email": "j@co.com", "first_name": "Jane", "last_name": "Smith", "company_name": "Co" },
    ...
  ]
}'
```
Max 1000 leads per call. Check `leads_uploaded` and `invalid_email_count`.

**D2 -- Attach all sending accounts:**
If the campaign was created with only one account, patch to include all:
```
composio execute INSTANTLY_UPDATE_CAMPAIGN -d '{
  "campaign_id": "{CAMPAIGN_ID}",
  "email_list": ["email1@...", "email2@...", "email3@..."]
}'
```

### Step 3: Leave paused

NEVER auto-activate. Campaign starts paused.

**Final report to user:**
- Campaign name and status (paused)
- Number of leads loaded
- Sending accounts attached
- Schedule: Mon-Fri, 8:00-17:00 in the audience's timezone
- Google Sheet URL for reference
- "Review in Instantly dashboard. Say 'go' when ready to launch."

To activate when user approves:
```
composio execute INSTANTLY_ACTIVATE_CAMPAIGN -d '{ "campaign_id": "{CAMPAIGN_ID}" }'
```

---

## Composio Tool Slug Reference

| Service | Action | Composio slug | Key gotchas |
|---------|--------|---------------|-------------|
| Apify | Get actor info | `APIFY_GET_ACTOR` | |
| Apify | Run actor (sync) | `APIFY_RUN_ACTOR_SYNC` | Input field is `posts` not `urls` |
| Sheets | Create spreadsheet | `GOOGLESHEETS_CREATE_GOOGLE_SHEET1` | |
| Sheets | Get tab names | `GOOGLESHEETS_GET_SHEET_NAMES` | Always call after create -- tab name is locale-dependent |
| Sheets | Write/update values | `GOOGLESHEETS_VALUES_UPDATE` | Uses **snake_case**: `value_input_option` |
| Sheets | Batch update | `GOOGLESHEETS_UPDATE_VALUES_BATCH` | Uses **camelCase**: `valueInputOption` |
| Sheets | Append rows | `GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND` | |
| Sheets | Read values | `GOOGLESHEETS_VALUES_GET` | |
| Apollo | Single enrichment | `APOLLO_PEOPLE_ENRICHMENT` | **Preferred.** Uses `organization_name`. No `reveal_phone_number`. |
| Apollo | Bulk enrichment | `APOLLO_BULK_PEOPLE_ENRICHMENT` | Unreliable in parallel -- use single instead. Uses `company_name`. |
| Apollo | Bulk create contacts | `APOLLO_CREATE_BULK_CONTACTS` | |
| Apollo | Check credits | `APOLLO_VIEW_API_USAGE_STATS` | |
| Hunter | Email finder | `HUNTER_EMAIL_FINDER` | Uses `company` (not `domain`) when only name known. `max_duration` 3-20s. Score >= 50 to accept. |
| Hunter | Check credits | `HUNTER_ACCOUNT_INFORMATION` | `searches.available`/`used` for quota. |
| Instantly | List accounts | `INSTANTLY_LIST_ACCOUNTS` | |
| Instantly | Get campaign | `INSTANTLY_GET_CAMPAIGN` | Uses `id` (NOT `campaign_id`) |
| Instantly | Create campaign | `INSTANTLY_CREATE_CAMPAIGN` | No `delay_unit` in steps. Timezone must be a major city. |
| Instantly | Update campaign | `INSTANTLY_UPDATE_CAMPAIGN` | |
| Instantly | Bulk add leads | `INSTANTLY_ADD_LEADS_BULK` | |
| Instantly | Activate campaign | `INSTANTLY_ACTIVATE_CAMPAIGN` | |

---

## Timing Expectations

| Phase | Duration | Notes |
|-------|----------|-------|
| 1. Scrape | 2-5 min | MCP Apify handles async polling automatically |
| 2. Store | ~30 sec | One bulk write -- include tab name discovery |
| 3. Enrich (Apollo) | 5-15 min | 8 concurrent single Apollo calls. Sheet batch-update adds ~30s. |
| 3.5. Enrich (Hunter) | 3-8 min | 8 concurrent Hunter calls on Apollo no-email contacts. Typically 10-20% recovery. |
| 4. Write | 1-2 min (auto) / 10-30 min (interactive) | Auto-mode skips interactive review for small lists |
| 5. Launch | 1-3 min | Instantly accounts pre-fetched during Phase 3 |
| **Total** | **~13-30 min (auto) / ~23-60 min (interactive)** | |

---

## Common Mistakes

| Phase | Failure mode | Fix |
|-------|--------------|-----|
| 1 | Zero results: used `urls` instead of `posts` | Apify actor field is `posts`. Always check actor input schema. |
| 1 | Only 20 results returned | Actor defaults `maxItems` to 20. Always override to 1000. |
| 1 | Field names don't match expected | Actor returns `name`/`position`/`linkedinUrl`, not `fullName`/`headline`/`profileUrl`. Map them. |
| 1 | Invalid LinkedIn URL | Validate format, resolve redirects via WebFetch. |
| 2 | "Range not found" error | Tab name is locale-dependent ("Hoja 1", "Feuille 1", etc.). Discover with `GOOGLESHEETS_GET_SHEET_NAMES`. |
| 2 | `value_input_option` error | `GOOGLESHEETS_VALUES_UPDATE` uses **snake_case**. `GOOGLESHEETS_UPDATE_VALUES_BATCH` uses **camelCase**. They're inconsistent. |
| 2 | `values` rejected | Must be a native JSON array, not a stringified array. |
| 2 | 403 on sheet creation | Google Sheets Composio scopes may be insufficient. Re-link with `composio link googlesheets --no-wait`. |
| 2 | Values array not rectangular | Pad empty cells with `""`. Every row must have 15 columns. |
| 3 | Bulk Apollo returns 0 matches

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [felipesalinasr](https://github.com/felipesalinasr)
- **Source:** [felipesalinasr/fsr-stack](https://github.com/felipesalinasr/fsr-stack)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-felipesalinasr-fsr-stack-linkedin-comment-to-outreach-gsheets
- Seller: https://agentstack.voostack.com/s/felipesalinasr
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
