Automate Influencer Outreach: From Data Collection to Email Pipeline
Most influencer outreach is painfully manual: scroll through Instagram, copy profile stats into a spreadsheet, find email addresses one by one, write generic messages. A 50-creator campaign takes a week of grunt work before a single email is sent.
You can automate 80% of this pipeline. Here's the complete workflow — from discovering creators to sending personalized outreach — using SociaVault's API and a few scripts.
The Pipeline
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Discover │ → │ Collect │ → │ Score │ → │ Extract │ → │ Send │
│ Creators │ │ Data │ │ & Filter │ │ Contact │ │ Outreach │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
Search Profile Fit score Bio email Personalized
hashtags + posts based on parsing template with
+ keywords enrichment your criteria real metrics
Step 1: Discover Creators
Find potential influencers by searching hashtags, keywords, or exploring a competitor's followers:
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE = 'https://api.sociavault.com/v1/scrape';
const headers = { 'X-API-Key': API_KEY };
// Strategy 1: Search TikTok by keyword
async function searchTikTok(keyword) {
const res = await fetch(
`${BASE}/tiktok/search/users?query=${encodeURIComponent(keyword)}`,
{ headers }
);
const data = (await res.json()).data || [];
return data.map(u => ({
handle: u.uniqueId || u.unique_id,
platform: 'tiktok',
source: `search:${keyword}`
}));
}
// Strategy 2: Search Instagram hashtag posts, extract authors
async function searchInstagramHashtag(hashtag) {
const res = await fetch(
`${BASE}/instagram/hashtag?hashtag=${encodeURIComponent(hashtag)}`,
{ headers }
);
const posts = (await res.json()).data || [];
// Extract unique usernames from posts
const handles = [...new Set(
posts.map(p => p.owner?.username || p.user?.username).filter(Boolean)
)];
return handles.map(h => ({
handle: h,
platform: 'instagram',
source: `hashtag:${hashtag}`
}));
}
// Strategy 3: Check who a competitor follows
async function getCompetitorFollowing(handle) {
const res = await fetch(
`${BASE}/instagram/following?handle=${encodeURIComponent(handle)}`,
{ headers }
);
const users = (await res.json()).data || [];
return users.map(u => ({
handle: u.username,
platform: 'instagram',
source: `following:${handle}`
}));
}
// Run all discovery strategies
async function discoverCreators() {
const results = [];
// Customize these for your niche
const tiktokKeywords = ['fitness coach', 'meal prep', 'workout routine'];
const instagramHashtags = ['fitnessmotivation', 'healthylifestyle'];
const competitorHandles = ['competitor1', 'competitor2'];
for (const kw of tiktokKeywords) {
results.push(...await searchTikTok(kw));
await new Promise(r => setTimeout(r, 1000));
}
for (const tag of instagramHashtags) {
results.push(...await searchInstagramHashtag(tag));
await new Promise(r => setTimeout(r, 1000));
}
for (const handle of competitorHandles) {
results.push(...await getCompetitorFollowing(handle));
await new Promise(r => setTimeout(r, 1000));
}
// Deduplicate by handle + platform
const seen = new Set();
return results.filter(r => {
const key = `${r.platform}:${r.handle}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
Step 2: Enrich with Profile Data
Now pull full profile data for each discovered creator:
async function enrichProfile(handle, platform) {
const endpoints = {
instagram: 'instagram/profile',
tiktok: 'tiktok/profile',
twitter: 'twitter/profile',
};
const res = await fetch(
`${BASE}/${endpoints[platform]}?handle=${encodeURIComponent(handle)}`,
{ headers }
);
const data = (await res.json()).data;
if (!data) return null;
if (platform === 'instagram') {
return {
handle: data.username,
platform: 'instagram',
name: data.full_name,
followers: data.follower_count,
following: data.following_count,
posts: data.media_count,
bio: data.biography || '',
verified: data.is_verified,
profileUrl: `https://instagram.com/${data.username}`,
isPrivate: data.is_private,
externalUrl: data.external_url
};
}
if (platform === 'tiktok') {
return {
handle: data.user?.uniqueId,
platform: 'tiktok',
name: data.user?.nickname,
followers: data.stats?.followerCount,
following: data.stats?.followingCount,
posts: data.stats?.videoCount,
bio: data.user?.signature || '',
verified: data.user?.verified,
profileUrl: `https://tiktok.com/@${data.user?.uniqueId}`,
isPrivate: false,
externalUrl: data.user?.bioLink?.link
};
}
return null;
}
async function enrichAll(creators) {
const enriched = [];
for (const creator of creators) {
const profile = await enrichProfile(creator.handle, creator.platform);
if (profile) {
enriched.push({ ...profile, source: creator.source });
}
await new Promise(r => setTimeout(r, 800));
}
return enriched;
}
Step 3: Score and Filter
Not every creator is a good fit. Score them based on your campaign criteria:
function scoreCreator(profile, criteria = {}) {
const {
minFollowers = 5000,
maxFollowers = 500000,
minPosts = 20,
preferVerified = false,
mustNotBePrivate = true
} = criteria;
let score = 0;
const reasons = [];
// Follower range (0-30 points)
if (profile.followers >= minFollowers && profile.followers <= maxFollowers) {
score += 30;
reasons.push('follower count in range');
} else if (profile.followers < minFollowers) {
reasons.push('too few followers');
return { score: 0, reasons, pass: false };
} else {
score += 15; // Over max but not disqualifying
reasons.push('above target range');
}
// Follower/following ratio (0-20 points)
const ratio = profile.followers / Math.max(profile.following, 1);
if (ratio > 10) {
score += 20;
reasons.push('excellent f/f ratio');
} else if (ratio > 3) {
score += 15;
reasons.push('good f/f ratio');
} else if (ratio > 1) {
score += 5;
reasons.push('average f/f ratio');
} else {
reasons.push('poor f/f ratio');
}
// Content volume (0-15 points)
if (profile.posts >= minPosts) {
score += 15;
reasons.push('active poster');
} else {
reasons.push('low post count');
}
// Bio quality (0-15 points)
if (profile.bio.length > 20) {
score += 10;
reasons.push('has bio');
}
if (profile.externalUrl) {
score += 5;
reasons.push('has website link');
}
// Verification bonus (0-10 points)
if (profile.verified) {
score += preferVerified ? 10 : 5;
reasons.push('verified');
}
// Privacy check
if (mustNotBePrivate && profile.isPrivate) {
return { score: 0, reasons: ['private account'], pass: false };
}
// Contact info in bio (0-10 points)
const emailRegex = /[\w.+-]+@[\w-]+\.[\w.-]+/;
if (emailRegex.test(profile.bio)) {
score += 10;
reasons.push('email in bio');
}
return {
score,
reasons,
pass: score >= 40 // Minimum qualifying score
};
}
function filterAndRank(profiles, criteria) {
return profiles
.map(profile => {
const result = scoreCreator(profile, criteria);
return { ...profile, ...result };
})
.filter(p => p.pass)
.sort((a, b) => b.score - a.score);
}
Step 4: Extract Contact Information
Pull email addresses from bios and external links:
function extractEmail(bio) {
const emailRegex = /[\w.+-]+@[\w-]+\.[\w.-]+/;
const match = bio.match(emailRegex);
return match ? match[0] : null;
}
function extractContactInfo(profile) {
const email = extractEmail(profile.bio);
// Common business email patterns in bios
const businessIndicators = [
'collab', 'partner', 'business', 'inquir', 'sponsor',
'brand deal', 'dm for', 'email', 'contact'
];
const isOpenToCollabs = businessIndicators.some(
indicator => profile.bio.toLowerCase().includes(indicator)
);
return {
...profile,
email,
isOpenToCollabs,
contactMethod: email ? 'email' : (isOpenToCollabs ? 'dm' : 'none'),
websiteFromBio: profile.externalUrl
};
}
Step 5: Generate Personalized Outreach
Create email templates that reference real data points — because "loved your content" doesn't convince anyone:
function generateOutreach(profile, campaignContext) {
const {
brandName = 'Your Brand',
campaignGoal = 'product collaboration',
compensation = 'paid partnership'
} = campaignContext;
const followerTier = profile.followers > 100000 ? 'large'
: profile.followers > 10000 ? 'mid' : 'micro';
const templates = {
micro: {
subject: `${brandName} x @${profile.handle} — Collaboration Idea`,
body: `Hi ${profile.name || profile.handle},
I found your ${profile.platform} profile while researching ${profile.source.includes('hashtag') ? 'top creators in your niche' : 'creators in our space'}. With ${profile.followers.toLocaleString()} followers and ${profile.posts} posts, you're building something impressive.
${profile.verified ? "Your verified status caught our eye — " : ""}We're a ${brandName} and we'd love to explore a ${campaignGoal} with you.
Quick details:
• Type: ${compensation}
• Platform: ${profile.platform}
• Timeline: Flexible
Would you be open to a quick chat this week?
Best,
[Your Name]
${brandName}`
},
mid: {
subject: `Collaboration Opportunity — ${brandName}`,
body: `Hi ${profile.name || profile.handle},
We've been following your growth on ${profile.platform} — ${profile.followers.toLocaleString()} followers with a genuine community is exactly what we look for in partners.
${brandName} is looking for creators for a ${campaignGoal}, and your content style aligns with our brand.
What we're offering:
• ${compensation}
• Creative freedom with brand guidelines
• Long-term partnership potential
Interested? I'd love to share more details.
Best,
[Your Name]
${brandName}`
},
large: {
subject: `${brandName} Partnership Proposal for @${profile.handle}`,
body: `Hi ${profile.name || profile.handle},
I'll keep this short — we'd like to work with you.
${brandName} is running a ${campaignGoal} campaign and your ${profile.followers.toLocaleString()}-follower ${profile.platform} audience is a perfect fit.
We'd love to schedule a call with you or your management team to discuss terms.
Available this week?
Best,
[Your Name]
${brandName}`
}
};
return templates[followerTier];
}
Step 6: Full Pipeline
Put it all together:
import fs from 'fs';
async function runOutreachPipeline(campaignConfig) {
console.log('Step 1: Discovering creators...');
const discovered = await discoverCreators();
console.log(` Found ${discovered.length} unique creators`);
console.log('Step 2: Enriching profiles...');
const enriched = await enrichAll(discovered);
console.log(` Enriched ${enriched.length} profiles`);
console.log('Step 3: Scoring and filtering...');
const qualified = filterAndRank(enriched, campaignConfig.criteria);
console.log(` ${qualified.length} creators qualified`);
console.log('Step 4: Extracting contact info...');
const withContact = qualified.map(extractContactInfo);
const contactable = withContact.filter(c => c.email || c.isOpenToCollabs);
console.log(` ${contactable.length} have contact info`);
console.log('Step 5: Generating outreach...');
const outreach = contactable.map(creator => ({
...creator,
outreach: generateOutreach(creator, campaignConfig)
}));
// Export to CSV for review before sending
const csv = [
'Handle,Platform,Followers,Score,Email,Contact Method,Subject,Profile URL',
...outreach.map(c => [
c.handle,
c.platform,
c.followers,
c.score,
c.email || '',
c.contactMethod,
`"${c.outreach.subject}"`,
c.profileUrl
].join(','))
].join('\n');
fs.writeFileSync('outreach-list.csv', csv);
console.log(`\nExported ${outreach.length} creators to outreach-list.csv`);
// Also save full data as JSON
fs.writeFileSync('outreach-full.json', JSON.stringify(outreach, null, 2));
// Summary
console.log('\n--- Pipeline Summary ---');
console.log(`Discovered: ${discovered.length}`);
console.log(`Enriched: ${enriched.length}`);
console.log(`Qualified: ${qualified.length}`);
console.log(`Contactable: ${contactable.length}`);
console.log(`Ready for outreach: ${outreach.length}`);
return outreach;
}
// Run it
runOutreachPipeline({
criteria: {
minFollowers: 5000,
maxFollowers: 200000,
minPosts: 30,
preferVerified: false,
mustNotBePrivate: true
},
brandName: 'FitGear',
campaignGoal: 'product review',
compensation: 'free product + $200-500'
});
Python version of the full pipeline:
import os
import csv
import json
import time
import re
import requests
API_KEY = os.environ["SOCIAVAULT_API_KEY"]
BASE = "https://api.sociavault.com/v1/scrape"
HEADERS = {"X-API-Key": API_KEY}
def search_tiktok(keyword):
r = requests.get(f"{BASE}/tiktok/search/users", headers=HEADERS, params={"query": keyword})
users = r.json().get("data", [])
return [{"handle": u.get("uniqueId") or u.get("unique_id"), "platform": "tiktok", "source": f"search:{keyword}"} for u in users]
def enrich_instagram(handle):
r = requests.get(f"{BASE}/instagram/profile", headers=HEADERS, params={"handle": handle})
d = r.json().get("data")
if not d:
return None
return {
"handle": d.get("username"),
"platform": "instagram",
"name": d.get("full_name"),
"followers": d.get("follower_count", 0),
"following": d.get("following_count", 0),
"posts": d.get("media_count", 0),
"bio": d.get("biography", ""),
"verified": d.get("is_verified", False),
"url": f"https://instagram.com/{d.get('username')}",
"external_url": d.get("external_url"),
}
def score_creator(profile, min_followers=5000, max_followers=500000):
score = 0
if profile["followers"] < min_followers:
return 0
if min_followers <= profile["followers"] <= max_followers:
score += 30
ratio = profile["followers"] / max(profile["following"], 1)
if ratio > 10:
score += 20
elif ratio > 3:
score += 15
if profile["posts"] >= 20:
score += 15
if len(profile["bio"]) > 20:
score += 10
email_match = re.search(r"[\w.+-]+@[\w-]+\.[\w.-]+", profile["bio"])
if email_match:
score += 10
return score
def extract_email(bio):
match = re.search(r"[\w.+-]+@[\w-]+\.[\w.-]+", bio)
return match.group(0) if match else None
def run_pipeline():
# Discover
creators = search_tiktok("fitness coach")
print(f"Discovered {len(creators)} creators")
# Enrich (example: enrich as Instagram profiles)
enriched = []
for c in creators[:20]:
profile = enrich_instagram(c["handle"])
if profile:
enriched.append(profile)
time.sleep(0.8)
print(f"Enriched {len(enriched)} profiles")
# Score & filter
scored = [(p, score_creator(p)) for p in enriched]
qualified = [(p, s) for p, s in scored if s >= 40]
qualified.sort(key=lambda x: x[1], reverse=True)
print(f"Qualified: {len(qualified)}")
# Export
with open("outreach.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Handle", "Platform", "Followers", "Score", "Email", "URL"])
for p, s in qualified:
writer.writerow([p["handle"], p["platform"], p["followers"], s, extract_email(p["bio"]) or "", p["url"]])
print(f"Exported {len(qualified)} creators to outreach.csv")
run_pipeline()
Best Practices for Influencer Outreach
Do:
- Personalize every message. Reference specific metrics or content.
- Be upfront about compensation. Don't waste creators' time.
- Follow up once. Then move on.
- Track responses. A 10-15% reply rate is normal for cold outreach.
- Respect "no." It's part of the process.
Don't:
- Send identical mass emails
- Promise "exposure" as payment
- Automate the actual sending without review
- Contact creators who explicitly say "no collabs"
- Spam DMs on multiple platforms simultaneously
Pipeline Cost Estimate
| Step | API Calls | Credits |
|---|---|---|
| Discovery (5 keywords) | 5 | 5 |
| Enrichment (100 profiles) | 100 | 100 |
| Total per campaign | 105 | ~105 |
That's roughly $1-2 per campaign run for a list of 100 qualified, scored, contact-enriched creators. Compare that to influencer discovery platforms charging $200-500/month.
Get Started
Sign up free and build your first outreach pipeline today.
Related Reading
Found this helpful?
Share it with others who might benefit
Ready to Try SociaVault?
Start extracting social media data with our powerful API. No credit card required.