Turn a SERP into a personalized link-building outreach campaign
From one seed keyword to a scoped outreach campaign of 10–25 personalized emails — SERP scraping, Apollo contact discovery, LLM drafts, bulk approval, and Instantly send in under 15 minutes.
One keyword → top SERP results → filtered → Apollo contact discovery → personalized outreach drafts → bulk approval → send. Sub-15 minutes from idea to outbound queue.
| Outcome | A scoped outreach campaign of 10–25 drafted emails to high-relevance partners |
| Time | ~10 min for the agent to run; human-review time depends on draft count |
| Cost | ~50 credits for a 25-draft campaign end-to-end (campaign + per-domain Apollo + per-draft LLM + send) |
| Prereqs | Apollo + Instantly connected, your content asset URL, the seed keyword |
What it does
link_building.serp_outreach.create(seed: keyword, ourContentUrl: ...)
↓ runs research.serp internally
↓ filters: excludeRootPages, DR range, prior contact, account status
↓ link_building.crm.contact.discover (Apollo) per surviving domain
↓ drafts personalized email per contact citing the SERP context
↓ pauses at WAITING_APPROVAL with all drafts surfaced
↓
human bulk-approves
↓ link_building.campaign.send_email per draft (via Instantly)
↓ creates EMAIL_SENT events
↓ relationship status → CONTACTED_BY_USStep 1 — Pick a winnable keyword
Use research.serp_gap to check winnability first:
curl -X POST .../v1/research/serp-gap -d '{
"query": "best project management software for engineering teams",
"ourDomain": "acme.com"
}'If the verdict is DOMINATED, pick another keyword. If WINNABLE_WITH_EFFORT or EASY_WIN, proceed.
Step 2 — Start the campaign
curl -X POST https://api.citationbench.com/v1/link-building/serp-outreach \
-H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"seed": {
"keyword": "best project management software for engineering teams",
"country": "us"
},
"filters": {
"topN": 50,
"excludeRootPages": true,
"excludeSubdomains": ["amazon.com", "youtube.com", "reddit.com"],
"minDomainRating": 30,
"maxDomainRating": 85,
"excludeAlreadyContactedWithinDays": 180,
"requireContactDiscovery": true
},
"outreach": {
"ourContentUrl": "https://acme.com/blog/engineering-team-capacity-tracking",
"targetingAngle": "guest_post",
"emailTemplate": "tpl_friendly-introduction",
"personalize": true,
"approval": { "required": true }
}
}'Returns a campaignId + invocationId. The agent runs ~5–10 minutes.
Step 3 — Review drafts
curl -G .../v1/link-building/serp-outreach/scmp_***/draftsRender the drafts in your portal (or in MCP via Claude):
> Show me the drafts for that campaign, one by one. I'll approve or reject each.Step 4 — Bulk approve
# Approve all drafts that survived the filter
curl -X POST .../v1/link-building/serp-outreach/scmp_***/approve -d '{
"draftIds": "all",
"scheduleAt": "2026-05-25T09:00:00-04:00"
}'
# Or approve a subset
curl -X POST .../v1/link-building/serp-outreach/scmp_***/approve -d '{
"draftIds": ["draft_A", "draft_B", "draft_D"],
"editsPerDraft": {
"draft_A": { "subject": "Quick thought on your PM roundup" }
}
}'
# Reject the rest and mark them so the agent doesn't reconsider
curl -X POST .../v1/link-building/serp-outreach/scmp_***/reject -d '{
"draftIds": ["draft_C"],
"reason": "Off-target",
"markRelationship": "NOT_INTERESTED"
}'Step 5 — Watch the campaign progress
curl .../v1/link-building/campaign/scmp_***/metricsTrack open / reply / placement rates over time.
One-shot script
#!/usr/bin/env bash
set -euo pipefail
KEY="${CITATIONBENCH_API_KEY:?}"
WS="${WORKSPACE_ID:?}"
BASE="https://api.citationbench.com/v1"
KEYWORD="${1:?usage: $0 <keyword> <our-content-url>}"
CONTENT="${2:?usage: $0 <keyword> <our-content-url>}"
# 1. Check winnability
VERDICT=$(curl -sf -X POST $BASE/research/serp-gap \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d "{\"query\":\"$KEYWORD\",\"ourDomain\":\"acme.com\"}" \
| jq -r '.result.winnability.verdict' 2>/dev/null || echo "unknown")
echo "Winnability verdict: $VERDICT"
# 2. Start the campaign
CAMPAIGN=$(curl -sf -X POST $BASE/link-building/serp-outreach \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d "{
\"seed\": { \"keyword\": \"$KEYWORD\" },
\"outreach\": { \"ourContentUrl\": \"$CONTENT\" }
}" | jq -r '.campaignId')
echo "Campaign $CAMPAIGN started; drafts will park at WAITING_APPROVAL."Gotchas
- Same keyword twice: the
excludeAlreadyContactedWithinDaysfilter protects you from re-pitching the same contacts. Don't disable it. - Apollo contact gaps: not every domain has discoverable contacts. The filter drops these by default (
requireContactDiscovery: true). - Instantly sender reputation: high-volume outreach can hurt deliverability. Pace via
scheduleAtor per-day caps. - Template tone matters: friendly templates outperform aggressive ones in our data by 2–3x reply rate. Default to friendly.
Related
- API: Link Building · serp outreach
- API: Link Building · campaign management
- API: Link Building · CRM
- API: Research · serp gap analysis
- Playbook: Auto-respond to link requests (the inverse)
- Playbook: From competitor URL to content plan
Track ChatGPT citations
Daily share-of-voice snapshot across 5 LLMs and Google AI Overviews for your brand and category queries, with alerts on drops and an optional skill to reclaim lost mentions.
Auto-respond to link requests
Set up an agent that ingests inbound link emails, classifies them, drafts replies, sends safe cases autonomously, and escalates only payment requests and high-value partners.