AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Linkedin Reaction To Outreach

skill-felipesalinasr-fsr-stack-linkedin-reaction-to-outreach · by felipesalinasr

Use when the user wants to turn LinkedIn post reactors (likes, celebrates, loves) into a paused Instantly cold email campaign, mentions a "reactions pipeline", asks to "scrape reactions and email them", or pastes a LinkedIn post URL with intent to reach the people who reacted. Orchestrates five sub-skills (reaction scraping, Airtable loading, Apollo enrichment, sequence writing, Instantly setup)…

No reviews yet
0 installs
39 views
0.0% view→install

Install

$ agentstack add skill-felipesalinasr-fsr-stack-linkedin-reaction-to-outreach

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-felipesalinasr-fsr-stack-linkedin-reaction-to-outreach)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Linkedin Reaction To Outreach? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

LinkedIn Reaction-to-Outreach Pipeline

Overview

Convert every reactor on a LinkedIn post into a paused, ready-to-launch Instantly cold email campaign by chaining five sub-skills in a single deterministic pipeline. Reactor profiles arrive richer than commenter profiles, so this orchestrator favors volume plays where company, title, and location data are already in the scrape.

When to use

  • A LinkedIn post URL is provided with intent to reach the people who reacted (liked, celebrated, loved, etc.)
  • The user asks for a "reactions pipeline", "outreach from reactions", or "scrape reactions and email them"
  • Volume matters more than per-lead intensity (reactors are 5-10x larger than commenters)
  • The user wants full LinkedIn profile data (experience, education, skills) loaded into Airtable

When NOT to use

  • Use linkedin-comment-to-outreach when the target audience is commenters on a post (smaller, higher-intent list)
  • Use linkedin-reaction-scraper directly when the user only wants the raw reactor data and no downstream pipeline
  • Use instantly-campaign directly when leads already exist in Apollo or Airtable and only the campaign step is needed
  • Skip this skill for one-off lead lookups, ABM research, or anything not anchored to a LinkedIn post URL

The Pipeline

LinkedIn Post URL
       |
       v
[1. linkedin-reaction-scraper]  -- Apify scrape, full profiles, deduplicate
       |
       v
[2. airtable-lead-loader]      -- Create table, batch load records
       |
       v
[3. apollo-enrichment]         -- Bulk email lookup, update Airtable, create contacts
       |
       v
[4. cold-email-sequence]       -- Co-write 3 emails with user (interactive)
       |
       v
[5. instantly-campaign]        -- Create campaign, load leads, configure accounts
       |
       v
Campaign ready for review (paused)

Why Reactions vs Comments Pipeline

| Dimension | Comments Pipeline | Reactions Pipeline (this one) | |-----------|------------------|-------------------------------| | Volume | Lower (commenting takes effort) | Higher (liking is easy, 5-10x more people) | | Profile data from scrape | Basic: name, headline, profileUrl | Rich: full experience, education, skills, certs, location | | Apollo dependency | High -- need Apollo for company, title, location | Lower -- company, title, location already in scrape data | | Lead quality signal | Strong (took time to write) | Moderate (one-click action) | | Best for | Smaller, high-intent lists | Broader audience capture, volume plays |


Prerequisites

  • MCP tools: Apify, Airtable, Apollo.io
  • API key: Instantly API key (user provides or stored)
  • Airtable base: Existing base ID (user provides or we find it)
  • User input needed for Step 4: Social proof, product description, CTA preferences

The Workflow

Phase 1: Scrape Reactions (Skill: linkedin-reaction-scraper)

Input: LinkedIn post URL from user Output: ./output/linkedin_reactors.json

  1. Validate the LinkedIn post URL (any format accepted)
  2. Strip UTM parameters
  3. Test scrape with maxItems: 20, profileScraperMode: "main"
  4. Verify rich data fields populated: actor.name, actor.headline, actor.currentPosition, actor.location.parsed, actor.experience
  5. Full scrape with maxItems: 500 (increase for viral posts)
  6. Process results:
  • Remove null profiles (deleted/deactivated accounts)
  • Deduplicate by actor.linkedinUrl
  • Extract company from headline if currentPosition is null
  • Parse reaction type breakdown
  1. Save to ./output/linkedin_reactors.json

Key differences from comment scraper:

  • Actor: harvestapi/linkedin-post-reactions (not linkedin-post-comments)
  • profileScraperMode: "main" gives full profiles -- ALWAYS use this
  • Default maxItems is 10 (even lower than comment scraper's 20) -- ALWAYS override
  • Data nested under actor key (not flat like comment scraper)
  • reactionType field available for filtering (LIKE, PRAISE, EMPATHY, etc.)

Checkpoint: Report to user:

  • "Scraped X unique reactors from [Author]'s post"
  • Reaction breakdown (X likes, Y celebrates, Z loves, etc.)
  • "Full profiles captured: experience, education, skills, location"
  • "Proceeding to Airtable."

Phase 2: Store (Skill: airtable-lead-loader)

Input: ./output/linkedin_reactors.json, Airtable Base ID Output: Airtable table populated, record IDs saved

  1. List Airtable bases, confirm with user
  2. Create table: "LinkedIn Reactors - [Author Name]"
  3. Expanded 22-field schema -- captures ALL rich data from the reactions scraper:

| # | Field | Airtable Type | Source | Notes | |---|-------|---------------|--------|-------| | 1 | Full Name | Single line text | actor.name | | | 2 | First Name | Single line text | actor.firstName | | | 3 | Last Name | Single line text | actor.lastName | | | 4 | Headline | Single line text | actor.headline | | | 5 | LinkedIn URL | URL | actor.linkedinUrl | Canonical /in/slug URL | | 6 | Company | Single line text | actor.currentPosition[0].companyName | Fallback: parse from headline | | 7 | Company LinkedIn URL | URL | actor.currentPosition[0].companyLinkedinUrl | May be null | | 8 | Job Title | Single line text | actor.currentPosition[0].position | Fallback: first segment of headline | | 9 | City | Single line text | actor.location.parsed.city | | | 10 | State | Single line text | actor.location.parsed.state | | | 11 | Country | Single line text | actor.location.parsed.country | | | 12 | About | Long text | actor.about | Full LinkedIn bio | | 13 | Experience | Long text | actor.experience | Formatted (see below) | | 14 | Education | Long text | actor.education | Formatted (see below) | | 15 | Top Skills | Single line text | actor.topSkills or actor.skills | Comma-separated list | | 16 | Connections | Number | actor.connectionsCount | | | 17 | Followers | Number | actor.followerCount | | | 18 | Reaction Type | Single line text | reactionType | LIKE, PRAISE, EMPATHY, APPRECIATION, INTEREST, ENTERTAINMENT | | 19 | Email | Email | (empty) | Filled by Apollo in Phase 3 | | 20 | Apollo Match | Checkbox | (false) | Set by Phase 3 | | 21 | Source | Single line text | Auto | "LinkedIn Reactions - [Post URL slug]" | | 22 | Profile JSON | Long text | Full actor object | Raw JSON backup for data not in other fields |

Formatting Complex Fields

Experience -- flatten the array into readable text:

Senior Accountant @ Deloitte (Jan 2020 - Present, 4 yrs 3 mos)
Staff Accountant @ PwC (Jun 2017 - Dec 2019, 2 yrs 7 mos)

Pattern: {position} @ {companyName} ({startDate.text} - {endDate.text}, {duration}) One line per role. Most recent first (array is already sorted).

Education -- same approach:

Master of Science, Accounting @ Colorado State University (2019 - 2022)
Bachelor's, Finance @ Monroe University (2008 - 2013)

Pattern: {degree} @ {schoolName} ({period})

Top Skills -- comma-separated string:

Tax Planning, Auditing, Financial Reporting

Source: actor.skills[].name array. If actor.topSkills exists, use that instead.

Profile JSON -- store JSON.stringify(actor) as a backup. This preserves certifications, volunteering, recommendations, publications, and any other fields not mapped to dedicated columns. Useful for future analysis without re-scraping.

Field Mapping Code

Walk every reactor item, pull actor plus the first currentPosition and location.parsed, format experience and education arrays into newline-joined strings, fall back to parsing the headline for company when currentPosition is empty, then emit the 22-field record.

See the airtable-lead-loader skill for the full mapping logic and reference implementation.

  1. Batch load via parallel agents (4 agents, 10 records per create_record call)
  2. Verify load count matches scrape count

Checkpoint: Report:

  • "Loaded X records into Airtable with full profile data"
  • "Y have company data, Z have location, W have bios"
  • "Experience and education captured for all profiles"
  • "Starting Apollo enrichment for emails."

Phase 3: Enrich (Skill: apollo-enrichment)

Input: Airtable Base ID, Table ID Output: Airtable updated with emails, Apollo contacts created

  1. Pull all records from Airtable
  2. Batch into groups of 10 for Apollo apollo_people_bulk_match
  3. Run enrichment via parallel agents (4 agents)
  4. Update Airtable records with:
  • Email (primary purpose of this step)
  • Company (Apollo override if scrape data was parsed from headline)
  • Title (Apollo override if more specific)
  • City, State, Country (Apollo override if scrape had null location)
  1. Set Apollo Match = true for matched records
  2. Create Apollo contacts under label: "LinkedIn Reactions - [Author] Post"
  3. Re-fetch records with emails, save to ./output/apollo_contacts.json

Note: Since reactions scraper already provides company/title/location, Apollo enrichment here is primarily for EMAIL addresses. Match rates may be slightly different than comment scraper since we have richer input data for matching.

Warning: Apollo has a stale cache bug. After bulk match, always re-fetch from Airtable to get the latest data rather than using the match response directly.

Checkpoint: Report: "Found emails for X out of Y people (Z% match rate). X contacts ready for outreach."


Phase 4: Write (Skill: cold-email-sequence)

Input: User context (product, social proof, trigger), contact count Output: {campaign-name}-sequence.md

This is the interactive step. Work with the user one email at a time.

  1. Gather inputs from user:
  • Trigger: What the LinkedIn post was about (the hook)
  • Product/offer: What we're selling
  • Social proof: Results, case studies, numbers
  • CTA: What's the ask (usually "I'm in" reply)
  • Sender name: Who it's from
  1. Draft Email 1 (Day 0 -- Opener)
  • Present to user, refine, lock
  1. Draft Email 2 (Day 3 -- Follow-up)
  • Present to user, refine, lock
  1. Draft Email 3 (Day 7 -- Breakup)
  • Present to user, refine, lock
  1. Save complete sequence to {campaign-name}-sequence.md

James Shields Framework Rules:

  • Personalize SUBJECT (reference the post), not the body
  • 3 sentences max + PS line
  • Low-friction CTA ("just reply 'I'm in'")
  • Irresistible offer in PS
  • No em dashes (use commas or periods)
  • No exclamation points
  • No bold/italic/formatting
  • No "Hi [Name]" opener -- jump straight in
  • NEW social proof per email (never repeat across emails)
  • Subject line references the specific post/author they reacted to

Checkpoint: "Sequence locked. Ready to load into Instantly."


Phase 5: Launch (Skill: instantly-campaign)

Input: Sequence file, ./output/apollo_contacts.json, Instantly API key Output: Paused Instantly campaign with all leads loaded

  1. Get/confirm Instantly API key from the INSTANTLY_API_KEY environment variable (see ../SETUP.md for how to configure). Never hardcode the key in this file.
  2. List connected email accounts via GET /api/v2/accounts
  3. Create campaign:
  • Name: "[Author] Post Reactions - [Product] Outreach"
  • Timezone: America/Vancouver (Pacific w/ DST). Instantly rejects America/Los_Angeles; Vancouver is the safe substitute. See the instantly-campaign skill for the full timezone workaround table.
  • Schedule: Mon-Fri, 8:00 AM - 5:00 PM
  • Daily limit: 25 per account
  1. Add 3 email steps with correct delay logic:
  • Email 1: Day 0 (delay: 0)
  • Email 2: Day 3 (delay: 3 -- relative to Email 1)
  • Email 3: Day 7 (delay: 4 -- relative to Email 2, NOT 7)
  1. CRITICAL: Sanitize all email bodies -- replace every & with + or "and"
  • The Instantly API silently drops the entire body if it contains &
  • No error returned -- body just becomes empty string
  • Test by reading back campaign after creation to verify bodies are non-empty
  1. Bulk load leads (up to 1000 per call)
  • Map from ./output/apollo_contacts.json:
  • email → email
  • firstName → first_name
  • lastName → last_name
  • companyName → company_name
  1. Attach all sending accounts to the campaign
  2. NEVER auto-activate. Leave campaign status = 0 (paused)

Final report to user:

  • Campaign name and ID
  • Status: PAUSED
  • Number of leads loaded
  • Sending accounts attached (list them)
  • Schedule: Mon-Fri, 8-5 Pacific
  • "Review in your Instantly dashboard. Say 'activate' when ready to launch."

Common Mistakes

| Phase | Common Error | Recovery | |-------|-------------|----------| | 1. Scrape | Only 10 results returned | Default maxItems is 10. Always set 500+. | | 1. Scrape | Post returns 0 results | Post may be private/deleted. Try urn:li:activity:ID format. | | 1. Scrape | profileScraperMode was "short" | Re-run with "main". Short mode misses experience/education. | | 2. Store | Airtable batch limit exceeded | Split into chunks of 10 records per create_record call. | | 2. Store | Token limit on list_records | Save to file, parse with Python. | | 3. Enrich | Low match rate ( Apify: harvestapi/linkedin-post-reactions (profileScraperMode: "main") -> ./output/linkedinreactors.json (all reactors with full profiles) -> Airtable: new table "LinkedIn Reactors - [Author]" (22 fields) (pre-filled: name, headline, company, title, city, state, country, about, experience, education, skills, connections, followers, reaction type, company LinkedIn URL, profile JSON backup) -> Apollo: bulk people match (batches of 10) -- primarily for EMAILS -> Airtable: updated with emails + Apollo Match flag -> Apollo: contacts created with label "LinkedIn Reactions - [Author] Post" -> ./output/apollocontacts.json (email-verified leads) -> Sequence file: {campaign-name}-sequence.md (3 emails, James Shields framework) -> Instantly: campaign + leads + accounts (API v2) -> PAUSED campaign ready for user review


---

## Output

At completion, the user has:

1. **Airtable table** -- all reactors with full profile data + email enrichment
2. **Apollo contacts** -- under named label, ready for CRM workflows
3. **Email sequence file** -- 3 locked emails in workspace
4. **Instantly campaign (PAUSED)** with:
   - 3 email steps loaded (bodies verified non-empty)
   - All leads with emails loaded
   - All sending accounts attached
   - Mon-Fri 8-5 Pacific schedule
   - Ready to activate on user command

---

## Quick Reference

| Phase | Sub-skill | Input | Output |
|-------|-----------|-------|--------|
| 1. Scrape | `linkedin-reaction-scraper` | LinkedIn post URL | `./output/linkedin_reactors.json` |
| 2. Store | `airtable-lead-loader` | Reactors JSON + Base ID | Airtable table populated (22 fields) |
| 3. Enrich | `apollo-enrichment` | Airtable Base ID + Table ID | Emails added, `./output/apollo_contacts.json` |
| 4. Write | `cold-email-sequence` | User context (offer, proof, CTA) | `{campaign-name}-sequence.md` |
| 5. Launch | `instantly-campaign` | Sequence file + Apollo contacts JSON | Paused Instantly campaign |

---

## Example

**Input:** `https://www.linkedin.com/posts/jane-doe_tax-strategy-activity-1234567890`

**Walkthrough:**

1. **Scrape** -- run `linkedin-reaction-scraper` with `maxItems: 500`, `profileScraperMode: "main"`. Result: 287 unique reactors saved to `./output/linkedin_reactors.json` with full experience, education, skills, and location.
2. **Store** -- create Airtable table `LinkedIn Reactors - Jane Doe` with 22 fields. Batch-load via 4 parallel agents. Result: 287 records, 268 with company data, 251 with location.
3. **Enrich** -- run Apollo bulk match in batches of 10. Result: 184 emails found (64% match rate), Apollo contacts created under label `LinkedIn Reactions - Jane Doe Post`, refreshed records exported to `./output/apollo_contacts.json`.
4. **Write** -- col

…

## 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.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.