CitationBenchTalk to Sales
Playbooks

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.

OutcomeA 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)
PrereqsApollo + 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_US

Step 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_***/drafts

Render 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_***/metrics

Track 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 excludeAlreadyContactedWithinDays filter 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 scheduleAt or per-day caps.
  • Template tone matters: friendly templates outperform aggressive ones in our data by 2–3x reply rate. Default to friendly.

On this page