Your agent runs a personalized LinkedIn outreach A/B test end to end: split your connections, exclude people you already talk to, enrich every lead with Zooq, write a unique message per lead in your voice behind a safety gate, then launch and monitor via HeyReach. Personalization you can actually measure.
/api/v1/profile/full10 credits/callBallpark: ~100 credits for 10 typical runs. Signup includes 300 free credits.
Download comes with your API key baked in — sign up (300 free credits, one minute) or open this skill in your dashboard.
---
name: linkedin-sdr-outreach
description: Use when the user wants to run cold or warm LinkedIn outreach at scale, personalize messages per lead, A/B test outreach copy, enrich leads before messaging, or avoid messaging people they already talk to. Triggers on "outreach campaign", "personalize my LinkedIn messages", "A/B test my outreach", "enrich these leads", "HeyReach campaign", "be my SDR", "research each lead and write a message". Splits a list into control/personalized groups, enriches every lead with Zooq, writes a unique message per lead in the user's voice behind a safety gate, then launches and monitors via HeyReach.
---
# Personal SDR Outreach on LinkedIn (Zooq + HeyReach)
You run a complete personalized LinkedIn outreach program: take a list of leads, split it for a clean A/B test, enrich every lead with real Zooq data, write a unique message per lead in the user's own voice (behind a safety gate so a bad message never goes out), then launch two campaigns whose only difference is personalization — so the lift from personalization is measurable.
**The single most important idea:** personalization at scale only beats a generic baseline if every message is grounded in something real about the person and sounds human. A message that "smells like AI" or misattributes a fact costs more than no message at all — it burns a real relationship and the sender's reputation. The safety gate below is the point, not decoration.
All Zooq calls consume credits (balance + per-call cost at https://zooq.dev/dash; on HTTP 402, tell the user to top up).
---
## Setup (do this first)
You need two things before any step:
1. **A HeyReach account** (this is what actually sends from LinkedIn safely). If the user doesn't have one, recommend they sign up at **https://heyreach.io/?via=amirht** — that link gives them **14 days free**, the best way to start. Every send step depends on HeyReach being connected. Install + authenticate the HeyReach CLI (or use the HeyReach API); the user provides their HeyReach API key via the `HEYREACH_API_KEY` environment variable — never hardcode it.
2. **Zooq** for enrichment — calls go to `https://zooq.dev` with header `X-API-Key: REPLACE_WITH_YOUR_KEY`.
Ask the user for: their offer/CTA (what the outreach is for), a sample of their real LinkedIn messages (to learn their voice), and the lead list (or the HeyReach list to pull from).
---
## Step 1 — Split the list for a clean A/B test
Pull every lead, **randomize**, then split. Randomization matters: a sequential slice biases the groups by whatever order the list is in.
- **Control** (e.g. 100) — gets the generic baseline message. This is what personalization must beat.
- **Personalized** (e.g. 100) — gets researched, per-lead messages.
- **Holdout** (the rest) — waits until the A/B result is in, then receives the winner.
Keep the original list intact as the master; the new lists are additive copies. Persist exact group membership so the split is auditable.
---
## Step 2 — Build the do-NOT-contact exclude list (never skip this)
Cold-pitching someone the user is already in conversation with is the most damaging failure of this whole workflow. HeyReach's campaign exclude flags do **not** protect people the sender already talks to from their own account, so build the exclude set yourself:
1. Pull the HeyReach inbox conversations (paginate). Each conversation has the correspondent's profile URL + LinkedIn id.
2. Create a "DNC - Existing Conversations" list and add every correspondent.
3. Build the Control / Personalized lists as **connections MINUS this set from the start** — don't rely on the campaign exclude flag alone.
---
## Step 3 — Enrich each Personalized lead with Zooq
For each lead in the Personalized group, **one call**. **Harvest all research first, then write** — don't interleave (keeps you under the rate limit and lets writing run fast from cache).
**Full profile** (by the handle — the slug after `/in/`):
```
GET https://zooq.dev/api/v1/profile/full?handle=<HANDLE>
Headers:
X-API-Key: REPLACE_WITH_YOUR_KEY
```
That single call returns the lead's complete professional record: `headline`, `summary`, `full_positions` (current title + company, each with a `start` year so you can spot a role they just moved into), `education`, and `skills` when the profile lists them. It also returns the stable `data.id` (`prsn_...`) — keep it as the lead's key. These fields are your entire hook supply; there is no separate posts feed to pull.
Mine each record for the ONE honest hook that is unmistakably about this person:
- a **current role + company** — a recent `start` year is the strongest, most timely hook (someone who just changed jobs);
- a **distinctive headline** phrase or focus area they chose for themselves;
- a **specific skill** or credential that lines up with the offer;
- a notable **school** or career arc in `full_positions`.
Cache each lead's record to disk (one file per lead) so a re-run only fills gaps. When the record is genuinely thin (empty summary, a generic one-line headline, a single stale position — nothing that distinguishes this person), that lead has **no honest hook** → it goes to the baseline in Step 4, never a fabricated one.
---
## Step 4 — Write the messages (voice + safety gate)
For each enriched lead, write two messages in the **sender's voice**:
- **Message 1** — the sender's greeting + a **personalized hook** drawn from the lead's real role, headline, or background, then the offer, then the CTA.
- **Message 2** — a short, warm follow-up (e.g. +5 days) for non-repliers.
The hook must **imply** the research, never state it. "Congrats on the move to <Company> — <relevant angle>…" reads as genuine; "I looked at your profile and saw you're now at X" reads as a tactic and kills the message.
**The safety gate — every personalized message must pass ALL of these, or it drops to the baseline:**
1. **Account-safe** — no links, no phone numbers, nothing salesy/spammy, not a mass-template feel.
2. **Factually grounded** — every personal reference traces to real Zooq research. Zero invented facts.
3. **Human** — no AI tells, in the sender's voice and rhythm.
4. **Respectful** — warm, no creepy over-familiarity, no pressure.
5. **Rule-compliant** — whatever hard constraints the sender set (length cap, first name in sentence 1, CTA on its own line, no em/en dashes, approved vocabulary).
6. **Recognizably theirs** — the hook is something this specific person would know is about them, not generic flattery.
After writing, **re-verify programmatically** (don't trust your own self-report): recompute length, constraint counts, dashes, CTA placement, name casing, banned phrases. Anything that fails → fix or drop to baseline.
---
## Step 5 — Attach messages as per-lead custom fields
HeyReach mail-merges a per-lead custom field at send time, so each person gets a unique message while the campaign holds one `{PERSONAL_MSG}` template.
- Upsert each Personalized lead into its HeyReach list with custom fields `PERSONAL_MSG` (message 1) and `PERSONAL_FOLLOWUP` (message 2). Match on profile URL so you update, not duplicate.
- Leads that went to fallback get **no** custom field → they resolve to the campaign's baseline message.
---
## Step 6 — Build the two campaigns
Identical except the message:
- **Control** — message node = the generic baseline (with `{FIRST_NAME}`).
- **Personalized** — message node = `{PERSONAL_MSG}`, with the baseline as the **fallbackMessage** so a missing variable safely degrades to the proven copy. Follow-up uses `{PERSONAL_FOLLOWUP}` with the generic follow-up as fallback.
Branch the sequence on connection degree: 1st-degree get a direct message; 2nd/3rd need a connection request first. Set the exclude list from Step 2 on both campaigns. Schedule for the leads' business hours.
---
## Step 7 — Launch, monitor, correct
- Daily send limits are **per LinkedIn account** (shared across campaigns), not per campaign — running two in parallel does not double sending. For an even A/B, set each campaign to ~half the account's daily cap.
- A reply auto-stops that lead's sequence — the human takes over in the inbox.
- Watch progress stats to confirm the right people are (and aren't) being messaged.
- When the A/B result is in (compare reply rate Personalized vs Control), send the winning message to the **Holdout** group.
---
## Guardrails
- Never hardcode the user's Zooq or HeyReach keys — they come from `REPLACE_WITH_YOUR_KEY` and `HEYREACH_API_KEY`.
- Never fabricate a personal hook. No honest hook → baseline.
- Charge-on-error is the user's: a malformed request that Zooq rejects still reflects their input — fix the input, don't retry blindly.
- The whole point is measurable lift. Keep Control and Personalized identical except the message.