feat: add email outreach CLIs for backlink building
Add 4 new zero-dependency CLI tools for email outreach: - hunter.js: Email finding/verification via Hunter.io (query param auth) - snov.js: Email finding + drip campaigns via Snov.io (OAuth2 auth) - lemlist.js: Cold email campaigns via Lemlist (Basic auth) - instantly.js: Cold email at scale via Instantly.ai (query param auth) Includes integration guides and registry/README updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f123804827
commit
aad399682c
10 changed files with 1404 additions and 0 deletions
|
|
@ -47,6 +47,10 @@ Quick reference for AI agents to discover tool capabilities and integration meth
|
|||
| postmark | Email | ✓ | - | [✓](clis/postmark.js) | ✓ | [postmark.md](integrations/postmark.md) |
|
||||
| brevo | Email/SMS | ✓ | - | [✓](clis/brevo.js) | ✓ | [brevo.md](integrations/brevo.md) |
|
||||
| activecampaign | Email/CRM | ✓ | - | [✓](clis/activecampaign.js) | ✓ | [activecampaign.md](integrations/activecampaign.md) |
|
||||
| hunter | Email Outreach | ✓ | - | [✓](clis/hunter.js) | - | [hunter.md](integrations/hunter.md) |
|
||||
| snov | Email Outreach | ✓ | - | [✓](clis/snov.js) | - | [snov.md](integrations/snov.md) |
|
||||
| lemlist | Email Outreach | ✓ | - | [✓](clis/lemlist.js) | - | [lemlist.md](integrations/lemlist.md) |
|
||||
| instantly | Email Outreach | ✓ | - | [✓](clis/instantly.js) | - | [instantly.md](integrations/instantly.md) |
|
||||
| google-ads | Ads | ✓ | ✓ | [✓](clis/google-ads.js) | ✓ | [google-ads.md](integrations/google-ads.md) |
|
||||
| meta-ads | Ads | ✓ | - | [✓](clis/meta-ads.js) | ✓ | [meta-ads.md](integrations/meta-ads.md) |
|
||||
| linkedin-ads | Ads | ✓ | - | [✓](clis/linkedin-ads.js) | - | [linkedin-ads.md](integrations/linkedin-ads.md) |
|
||||
|
|
@ -287,6 +291,19 @@ Webinar and virtual event platforms.
|
|||
|
||||
**Agent recommendation**: Demio for marketing-focused webinars. Livestorm for full event engagement.
|
||||
|
||||
### Email Outreach
|
||||
|
||||
Cold email outreach and email finding tools for link building and sales prospecting.
|
||||
|
||||
| Tool | Best For | Notes |
|
||||
|------|----------|-------|
|
||||
| **hunter** | Email finding, domain search | Largest email database |
|
||||
| **snov** | Email finding, drip campaigns | Built-in sequences |
|
||||
| **lemlist** | Cold email campaigns | Personalization features |
|
||||
| **instantly** | Cold email at scale | Email warmup built-in |
|
||||
|
||||
**Agent recommendation**: Hunter for finding emails. Lemlist or Instantly for sending cold email campaigns. Snov for combined finding + outreach.
|
||||
|
||||
### Commerce & CMS
|
||||
|
||||
E-commerce platforms and content management systems.
|
||||
|
|
@ -342,6 +359,10 @@ To use MCP tools, ensure the appropriate MCP server is configured in your enviro
|
|||
1. Read [customer-io.md](integrations/customer-io.md) for behavior-based automation
|
||||
2. Read [resend.md](integrations/resend.md) for transactional email
|
||||
|
||||
### Running email outreach for backlinks
|
||||
1. Read [hunter.md](integrations/hunter.md) for finding emails
|
||||
2. Read [lemlist.md](integrations/lemlist.md) or [instantly.md](integrations/instantly.md) for sending campaigns
|
||||
|
||||
### Running paid ads
|
||||
1. Read [google-ads.md](integrations/google-ads.md) for search campaigns
|
||||
2. Read [meta-ads.md](integrations/meta-ads.md) for social campaigns
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ Every CLI reads credentials from environment variables:
|
|||
| `tolt` | `TOLT_API_KEY` |
|
||||
| `trustpilot` | `TRUSTPILOT_API_KEY`, `TRUSTPILOT_API_SECRET`, `TRUSTPILOT_BUSINESS_UNIT_ID` |
|
||||
| `typeform` | `TYPEFORM_API_KEY` |
|
||||
| `hunter` | `HUNTER_API_KEY` |
|
||||
| `instantly` | `INSTANTLY_API_KEY` |
|
||||
| `lemlist` | `LEMLIST_API_KEY` |
|
||||
| `snov` | `SNOV_CLIENT_ID`, `SNOV_CLIENT_SECRET` |
|
||||
| `wistia` | `WISTIA_API_KEY` |
|
||||
| `zapier` | `ZAPIER_API_KEY` |
|
||||
|
||||
|
|
@ -140,10 +144,13 @@ DOMAINS=$(rewardful affiliates list | jq -r '.data[].email')
|
|||
| `google-ads.js` | Ads | [Google Ads](https://ads.google.com) |
|
||||
| `google-search-console.js` | SEO | [Google Search Console](https://search.google.com/search-console) |
|
||||
| `hotjar.js` | CRO | [Hotjar](https://hotjar.com) |
|
||||
| `hunter.js` | Email Outreach | [Hunter.io](https://hunter.io) |
|
||||
| `instantly.js` | Email Outreach | [Instantly.ai](https://instantly.ai) |
|
||||
| `intercom.js` | Messaging | [Intercom](https://intercom.com) |
|
||||
| `keywords-everywhere.js` | SEO | [Keywords Everywhere](https://keywordseverywhere.com) |
|
||||
| `kit.js` | Email | [Kit](https://kit.com) |
|
||||
| `klaviyo.js` | Email/SMS | [Klaviyo](https://klaviyo.com) |
|
||||
| `lemlist.js` | Email Outreach | [Lemlist](https://lemlist.com) |
|
||||
| `linkedin-ads.js` | Ads | [LinkedIn Ads](https://business.linkedin.com/marketing-solutions/ads) |
|
||||
| `livestorm.js` | Webinar | [Livestorm](https://livestorm.co) |
|
||||
| `mailchimp.js` | Email | [Mailchimp](https://mailchimp.com) |
|
||||
|
|
@ -162,6 +169,7 @@ DOMAINS=$(rewardful affiliates list | jq -r '.data[].email')
|
|||
| `segment.js` | Analytics | [Segment](https://segment.com) |
|
||||
| `semrush.js` | SEO | [SEMrush](https://semrush.com) |
|
||||
| `sendgrid.js` | Email | [SendGrid](https://sendgrid.com) |
|
||||
| `snov.js` | Email Outreach | [Snov.io](https://snov.io) |
|
||||
| `tiktok-ads.js` | Ads | [TikTok Ads](https://ads.tiktok.com) |
|
||||
| `tolt.js` | Referral | [Tolt](https://tolt.io) |
|
||||
| `trustpilot.js` | Reviews | [Trustpilot](https://trustpilot.com) |
|
||||
|
|
|
|||
249
tools/clis/hunter.js
Executable file
249
tools/clis/hunter.js
Executable file
|
|
@ -0,0 +1,249 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.HUNTER_API_KEY
|
||||
const BASE_URL = 'https://api.hunter.io/v2'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'HUNTER_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const separator = path.includes('?') ? '&' : '?'
|
||||
const url = `${BASE_URL}${path}${separator}api_key=${API_KEY}`
|
||||
if (args['dry-run']) {
|
||||
return { _dry_run: true, method, url: url.replace(API_KEY, '***'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined }
|
||||
}
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'domain':
|
||||
switch (sub) {
|
||||
case 'search': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
const params = new URLSearchParams({ domain })
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.type) params.set('type', args.type)
|
||||
result = await api('GET', `/domain-search?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'count': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
const params = new URLSearchParams({ domain })
|
||||
if (args.type) params.set('type', args.type)
|
||||
result = await api('GET', `/email-count?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown domain subcommand. Use: search, count' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'email':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const domain = args.domain
|
||||
const firstName = args['first-name']
|
||||
const lastName = args['last-name']
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
if (!firstName) { result = { error: '--first-name required' }; break }
|
||||
if (!lastName) { result = { error: '--last-name required' }; break }
|
||||
const params = new URLSearchParams({ domain, first_name: firstName, last_name: lastName })
|
||||
result = await api('GET', `/email-finder?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'verify': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const params = new URLSearchParams({ email })
|
||||
result = await api('GET', `/email-verifier?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown email subcommand. Use: find, verify' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'account':
|
||||
switch (sub) {
|
||||
case 'info':
|
||||
result = await api('GET', '/account')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown account subcommand. Use: info' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'leads':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.offset) params.set('offset', args.offset)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/leads${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/leads/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = { email }
|
||||
if (args['first-name']) body.first_name = args['first-name']
|
||||
if (args['last-name']) body.last_name = args['last-name']
|
||||
if (args.company) body.company = args.company
|
||||
result = await api('POST', '/leads', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/leads/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown leads subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.offset) params.set('offset', args.offset)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/campaigns${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${id}`)
|
||||
break
|
||||
}
|
||||
case 'start': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/campaigns/${id}/start`)
|
||||
break
|
||||
}
|
||||
case 'pause': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/campaigns/${id}/pause`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get, start, pause' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'leads-lists':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.offset) params.set('offset', args.offset)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/leads_lists${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/leads_lists/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown leads-lists subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
domain: {
|
||||
search: 'domain search --domain <domain> [--limit <n>] [--type personal|generic]',
|
||||
count: 'domain count --domain <domain> [--type personal|generic]',
|
||||
},
|
||||
email: {
|
||||
find: 'email find --domain <domain> --first-name <name> --last-name <name>',
|
||||
verify: 'email verify --email <email>',
|
||||
},
|
||||
account: 'account info',
|
||||
leads: {
|
||||
list: 'leads list [--limit <n>] [--offset <n>]',
|
||||
get: 'leads get --id <id>',
|
||||
create: 'leads create --email <email> [--first-name <name>] [--last-name <name>] [--company <company>]',
|
||||
delete: 'leads delete --id <id>',
|
||||
},
|
||||
campaigns: {
|
||||
list: 'campaigns list [--limit <n>] [--offset <n>]',
|
||||
get: 'campaigns get --id <id>',
|
||||
start: 'campaigns start --id <id>',
|
||||
pause: 'campaigns pause --id <id>',
|
||||
},
|
||||
'leads-lists': {
|
||||
list: 'leads-lists list [--limit <n>] [--offset <n>]',
|
||||
get: 'leads-lists get --id <id>',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
270
tools/clis/instantly.js
Executable file
270
tools/clis/instantly.js
Executable file
|
|
@ -0,0 +1,270 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.INSTANTLY_API_KEY
|
||||
const BASE_URL = 'https://api.instantly.ai/api/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'INSTANTLY_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const separator = path.includes('?') ? '&' : '?'
|
||||
const url = `${BASE_URL}${path}${separator}api_key=${API_KEY}`
|
||||
if (args['dry-run']) {
|
||||
const maskedUrl = url.replace(API_KEY, '***')
|
||||
const maskedBody = body ? JSON.parse(JSON.stringify(body)) : undefined
|
||||
if (maskedBody && maskedBody.api_key) maskedBody.api_key = '***'
|
||||
return { _dry_run: true, method, url: maskedUrl, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: maskedBody }
|
||||
}
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.skip) params.set('skip', args.skip)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/campaign/list${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams({ campaign_id: id })
|
||||
result = await api('GET', `/campaign/get?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'status': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams({ campaign_id: id })
|
||||
result = await api('GET', `/campaign/get/status?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'launch': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', '/campaign/launch', { api_key: API_KEY, campaign_id: id })
|
||||
break
|
||||
}
|
||||
case 'pause': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', '/campaign/pause', { api_key: API_KEY, campaign_id: id })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get, status, launch, pause' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'leads':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const campaignId = args['campaign-id']
|
||||
if (!campaignId) { result = { error: '--campaign-id required' }; break }
|
||||
const params = new URLSearchParams({ campaign_id: campaignId })
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.skip) params.set('skip', args.skip)
|
||||
result = await api('GET', `/lead/get?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'add': {
|
||||
const campaignId = args['campaign-id']
|
||||
const email = args.email
|
||||
if (!campaignId) { result = { error: '--campaign-id required' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const lead = { email }
|
||||
if (args['first-name']) lead.first_name = args['first-name']
|
||||
if (args['last-name']) lead.last_name = args['last-name']
|
||||
if (args.company) lead.company_name = args.company
|
||||
result = await api('POST', '/lead/add', { api_key: API_KEY, campaign_id: campaignId, leads: [lead] })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const campaignId = args['campaign-id']
|
||||
const email = args.email
|
||||
if (!campaignId) { result = { error: '--campaign-id required' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('POST', '/lead/delete', { api_key: API_KEY, campaign_id: campaignId, delete_list: [email] })
|
||||
break
|
||||
}
|
||||
case 'status': {
|
||||
const campaignId = args['campaign-id']
|
||||
const email = args.email
|
||||
if (!campaignId) { result = { error: '--campaign-id required' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const params = new URLSearchParams({ campaign_id: campaignId, email })
|
||||
result = await api('GET', `/lead/get/status?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown leads subcommand. Use: list, add, delete, status' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'accounts':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.skip) params.set('skip', args.skip)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/account/list${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'status': {
|
||||
const accountId = args['account-id']
|
||||
if (!accountId) { result = { error: '--account-id required' }; break }
|
||||
const params = new URLSearchParams({ email: accountId })
|
||||
result = await api('GET', `/account/get/status?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'warmup-status': {
|
||||
const accountId = args['account-id']
|
||||
if (!accountId) { result = { error: '--account-id required' }; break }
|
||||
const params = new URLSearchParams({ email: accountId })
|
||||
result = await api('GET', `/account/get/warmup?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown accounts subcommand. Use: list, status, warmup-status' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'analytics':
|
||||
switch (sub) {
|
||||
case 'campaign': {
|
||||
const campaignId = args['campaign-id']
|
||||
if (!campaignId) { result = { error: '--campaign-id required' }; break }
|
||||
const body = { api_key: API_KEY, campaign_id: campaignId }
|
||||
if (args['start-date']) body.start_date = args['start-date']
|
||||
if (args['end-date']) body.end_date = args['end-date']
|
||||
result = await api('POST', '/analytics/campaign/summary', body)
|
||||
break
|
||||
}
|
||||
case 'steps': {
|
||||
const campaignId = args['campaign-id']
|
||||
if (!campaignId) { result = { error: '--campaign-id required' }; break }
|
||||
const body = { api_key: API_KEY, campaign_id: campaignId }
|
||||
if (args['start-date']) body.start_date = args['start-date']
|
||||
if (args['end-date']) body.end_date = args['end-date']
|
||||
result = await api('POST', '/analytics/campaign/step', body)
|
||||
break
|
||||
}
|
||||
case 'account': {
|
||||
const startDate = args['start-date']
|
||||
const endDate = args['end-date']
|
||||
if (!startDate) { result = { error: '--start-date required' }; break }
|
||||
if (!endDate) { result = { error: '--end-date required' }; break }
|
||||
result = await api('POST', '/analytics/campaign/count', { api_key: API_KEY, start_date: startDate, end_date: endDate })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown analytics subcommand. Use: campaign, steps, account' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'blocklist':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/blocklist')
|
||||
break
|
||||
case 'add': {
|
||||
const entries = args.entries
|
||||
if (!entries) { result = { error: '--entries required (comma-separated emails or domains)' }; break }
|
||||
const entryList = entries.split(',').map(e => e.trim())
|
||||
result = await api('POST', '/blocklist/add', { api_key: API_KEY, entries: entryList })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown blocklist subcommand. Use: list, add' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
campaigns: {
|
||||
list: 'campaigns list [--limit <n>] [--skip <n>]',
|
||||
get: 'campaigns get --id <id>',
|
||||
status: 'campaigns status --id <id>',
|
||||
launch: 'campaigns launch --id <id>',
|
||||
pause: 'campaigns pause --id <id>',
|
||||
},
|
||||
leads: {
|
||||
list: 'leads list --campaign-id <id> [--limit <n>] [--skip <n>]',
|
||||
add: 'leads add --campaign-id <id> --email <email> [--first-name <name>] [--last-name <name>] [--company <name>]',
|
||||
delete: 'leads delete --campaign-id <id> --email <email>',
|
||||
status: 'leads status --campaign-id <id> --email <email>',
|
||||
},
|
||||
accounts: {
|
||||
list: 'accounts list [--limit <n>] [--skip <n>]',
|
||||
status: 'accounts status --account-id <email>',
|
||||
'warmup-status': 'accounts warmup-status --account-id <email>',
|
||||
},
|
||||
analytics: {
|
||||
campaign: 'analytics campaign --campaign-id <id> [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]',
|
||||
steps: 'analytics steps --campaign-id <id> [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]',
|
||||
account: 'analytics account --start-date YYYY-MM-DD --end-date YYYY-MM-DD',
|
||||
},
|
||||
blocklist: {
|
||||
list: 'blocklist list',
|
||||
add: 'blocklist add --entries <email-or-domain,email-or-domain>',
|
||||
},
|
||||
options: '--dry-run (show request without executing)',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
221
tools/clis/lemlist.js
Executable file
221
tools/clis/lemlist.js
Executable file
|
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.LEMLIST_API_KEY
|
||||
const BASE_URL = 'https://api.lemlist.com/api'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'LEMLIST_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const AUTH = 'Basic ' + Buffer.from(`:${API_KEY}`).toString('base64')
|
||||
|
||||
async function api(method, path, body) {
|
||||
if (args['dry-run']) {
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { Authorization: '***', 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined }
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': AUTH,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const offset = args.offset ? Number(args.offset) : 0
|
||||
const limit = args.limit ? Number(args.limit) : 100
|
||||
|
||||
switch (cmd) {
|
||||
case 'team':
|
||||
switch (sub) {
|
||||
case 'info':
|
||||
result = await api('GET', '/team')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown team subcommand. Use: info' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('offset', String(offset))
|
||||
params.set('limit', String(limit))
|
||||
result = await api('GET', `/campaigns?${params}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!args.id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${args.id}`)
|
||||
break
|
||||
}
|
||||
case 'stats': {
|
||||
if (!args.id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${args.id}/stats`)
|
||||
break
|
||||
}
|
||||
case 'export': {
|
||||
if (!args.id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${args.id}/export`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get, stats, export' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'leads':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!args['campaign-id']) { result = { error: '--campaign-id required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
params.set('offset', String(offset))
|
||||
params.set('limit', String(limit))
|
||||
result = await api('GET', `/campaigns/${args['campaign-id']}/leads?${params}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!args['campaign-id']) { result = { error: '--campaign-id required' }; break }
|
||||
if (!args.email) { result = { error: '--email required' }; break }
|
||||
result = await api('GET', `/campaigns/${args['campaign-id']}/leads/${args.email}`)
|
||||
break
|
||||
}
|
||||
case 'add': {
|
||||
if (!args['campaign-id']) { result = { error: '--campaign-id required' }; break }
|
||||
if (!args.email) { result = { error: '--email required' }; break }
|
||||
const body = {}
|
||||
if (args['first-name']) body.firstName = args['first-name']
|
||||
if (args['last-name']) body.lastName = args['last-name']
|
||||
if (args.company) body.companyName = args.company
|
||||
result = await api('POST', `/campaigns/${args['campaign-id']}/leads/${args.email}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (!args['campaign-id']) { result = { error: '--campaign-id required' }; break }
|
||||
if (!args.email) { result = { error: '--email required' }; break }
|
||||
result = await api('DELETE', `/campaigns/${args['campaign-id']}/leads/${args.email}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown leads subcommand. Use: list, get, add, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'unsubscribes':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('offset', String(offset))
|
||||
params.set('limit', String(limit))
|
||||
result = await api('GET', `/unsubscribes?${params}`)
|
||||
break
|
||||
}
|
||||
case 'add': {
|
||||
if (!args.email) { result = { error: '--email required' }; break }
|
||||
result = await api('POST', `/unsubscribes/${args.email}`)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (!args.email) { result = { error: '--email required' }; break }
|
||||
result = await api('DELETE', `/unsubscribes/${args.email}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown unsubscribes subcommand. Use: list, add, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'activities':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args['campaign-id']) params.set('campaignId', args['campaign-id'])
|
||||
if (args.type) params.set('type', args.type)
|
||||
params.set('offset', String(offset))
|
||||
params.set('limit', String(limit))
|
||||
result = await api('GET', `/activities?${params}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown activities subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'hooks':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/hooks')
|
||||
break
|
||||
case 'create': {
|
||||
if (!args['target-url']) { result = { error: '--target-url required' }; break }
|
||||
if (!args.event) { result = { error: '--event required' }; break }
|
||||
result = await api('POST', '/hooks', { targetUrl: args['target-url'], event: args.event })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (!args.id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/hooks/${args.id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown hooks subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
team: 'team info',
|
||||
campaigns: 'campaigns [list | get --id <id> | stats --id <id> | export --id <id>] [--offset 0] [--limit 100]',
|
||||
leads: 'leads [list | get --email <email> | add --email <email> | delete --email <email>] --campaign-id <id> [--first-name <name>] [--last-name <name>] [--company <name>]',
|
||||
unsubscribes: 'unsubscribes [list | add --email <email> | delete --email <email>] [--offset 0] [--limit 100]',
|
||||
activities: 'activities list [--campaign-id <id>] [--type emailsSent|emailsOpened|emailsClicked|emailsReplied|emailsBounced] [--offset 0] [--limit 100]',
|
||||
hooks: 'hooks [list | create --target-url <url> --event <event> | delete --id <id>]',
|
||||
options: '--dry-run --offset <n> --limit <n>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
237
tools/clis/snov.js
Executable file
237
tools/clis/snov.js
Executable file
|
|
@ -0,0 +1,237 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const CLIENT_ID = process.env.SNOV_CLIENT_ID
|
||||
const CLIENT_SECRET = process.env.SNOV_CLIENT_SECRET
|
||||
const BASE_URL = 'https://api.snov.io/v1'
|
||||
|
||||
if (!CLIENT_ID || !CLIENT_SECRET) {
|
||||
console.error(JSON.stringify({ error: 'SNOV_CLIENT_ID and SNOV_CLIENT_SECRET environment variables required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let cachedToken = null
|
||||
|
||||
async function getToken() {
|
||||
if (cachedToken) return cachedToken
|
||||
const res = await fetch('https://api.snov.io/v1/oauth/access_token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ grant_type: 'client_credentials', client_id: CLIENT_ID, client_secret: CLIENT_SECRET }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!data.access_token) {
|
||||
throw new Error(data.error_description || data.error || 'Failed to obtain access token')
|
||||
}
|
||||
cachedToken = data.access_token
|
||||
return cachedToken
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
if (args['dry-run']) {
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { Authorization: '***', 'Content-Type': 'application/json', Accept: 'application/json' }, body: body || undefined }
|
||||
}
|
||||
const token = await getToken()
|
||||
const opts = {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
}
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(`${BASE_URL}${path}`, opts)
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'domain':
|
||||
switch (sub) {
|
||||
case 'search': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
const body = { domain, type: args.type || 'all', limit: Number(args.limit || 100), lastId: 0 }
|
||||
result = await api('POST', '/get-domain-emails-with-info', body)
|
||||
break
|
||||
}
|
||||
case 'count': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
result = await api('POST', '/get-domain-emails-count', { domain })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown domain subcommand. Use: search, count' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'email':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const domain = args.domain
|
||||
const firstName = args['first-name']
|
||||
const lastName = args['last-name']
|
||||
if (!domain || !firstName || !lastName) { result = { error: '--domain, --first-name, and --last-name required' }; break }
|
||||
result = await api('POST', '/get-emails-from-names', { firstName, lastName, domain })
|
||||
break
|
||||
}
|
||||
case 'verify': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('POST', '/get-emails-verification-status', { emails: [email] })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown email subcommand. Use: find, verify' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'prospect':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('POST', '/get-prospect-by-email', { email })
|
||||
break
|
||||
}
|
||||
case 'add': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = { email }
|
||||
if (args['first-name']) body.firstName = args['first-name']
|
||||
if (args['last-name']) body.lastName = args['last-name']
|
||||
if (args['list-id']) body.listId = args['list-id']
|
||||
result = await api('POST', '/add-prospect-to-list', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown prospect subcommand. Use: find, add' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'lists':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/get-user-lists')
|
||||
break
|
||||
case 'prospects': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams({ listId: id })
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args['per-page']) params.set('perPage', args['per-page'])
|
||||
result = await api('GET', `/prospect-list?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown lists subcommand. Use: list, prospects' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'technology':
|
||||
switch (sub) {
|
||||
case 'check': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
result = await api('POST', '/get-technology-checker', { domain })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown technology subcommand. Use: check' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'drips':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/get-user-campaigns')
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/get-emails-from-campaign?id=${encodeURIComponent(id)}`)
|
||||
break
|
||||
}
|
||||
case 'add-prospect': {
|
||||
const campaignId = args['campaign-id']
|
||||
const email = args.email
|
||||
if (!campaignId || !email) { result = { error: '--campaign-id and --email required' }; break }
|
||||
const body = { campaignId, email }
|
||||
if (args['first-name']) body.firstName = args['first-name']
|
||||
if (args['last-name']) body.lastName = args['last-name']
|
||||
result = await api('POST', '/add-prospect-to-email-campaign', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown drips subcommand. Use: list, get, add-prospect' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
domain: {
|
||||
search: 'domain search --domain <domain> [--type all|personal|generic] [--limit <n>]',
|
||||
count: 'domain count --domain <domain>',
|
||||
},
|
||||
email: {
|
||||
find: 'email find --domain <domain> --first-name <name> --last-name <name>',
|
||||
verify: 'email verify --email <email>',
|
||||
},
|
||||
prospect: {
|
||||
find: 'prospect find --email <email>',
|
||||
add: 'prospect add --email <email> [--first-name <name>] [--last-name <name>] [--list-id <id>]',
|
||||
},
|
||||
lists: {
|
||||
list: 'lists list',
|
||||
prospects: 'lists prospects --id <id> [--page <n>] [--per-page <n>]',
|
||||
},
|
||||
technology: 'technology check --domain <domain>',
|
||||
drips: {
|
||||
list: 'drips list',
|
||||
get: 'drips get --id <id>',
|
||||
'add-prospect': 'drips add-prospect --campaign-id <id> --email <email> [--first-name <name>] [--last-name <name>]',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
90
tools/integrations/hunter.md
Normal file
90
tools/integrations/hunter.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Hunter.io
|
||||
|
||||
Email finding and verification platform for outreach and link building.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Integration | Available | Notes |
|
||||
|-------------|-----------|-------|
|
||||
| API | ✓ | REST API for domain search, email finder, verification |
|
||||
| MCP | - | Not available |
|
||||
| CLI | [✓](../clis/hunter.js) | Zero-dependency Node.js CLI |
|
||||
| SDK | - | API-only |
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Type**: API Key (query parameter)
|
||||
- **Parameter**: `api_key={key}`
|
||||
- **Env var**: `HUNTER_API_KEY`
|
||||
- **Get key**: [Hunter dashboard > API](https://hunter.io/api-keys)
|
||||
|
||||
## Common Agent Operations
|
||||
|
||||
### Find emails for a domain
|
||||
|
||||
```bash
|
||||
node tools/clis/hunter.js domain search --domain example.com --limit 10
|
||||
```
|
||||
|
||||
### Find a specific person's email
|
||||
|
||||
```bash
|
||||
node tools/clis/hunter.js email find --domain example.com --first-name John --last-name Doe
|
||||
```
|
||||
|
||||
### Verify an email address
|
||||
|
||||
```bash
|
||||
node tools/clis/hunter.js email verify --email john@example.com
|
||||
```
|
||||
|
||||
### Count emails available for a domain
|
||||
|
||||
```bash
|
||||
node tools/clis/hunter.js domain count --domain example.com
|
||||
```
|
||||
|
||||
### Manage leads
|
||||
|
||||
```bash
|
||||
# List leads
|
||||
node tools/clis/hunter.js leads list --limit 20
|
||||
|
||||
# Create a lead
|
||||
node tools/clis/hunter.js leads create --email john@example.com --first-name John --last-name Doe --company "Example Inc"
|
||||
|
||||
# Delete a lead
|
||||
node tools/clis/hunter.js leads delete --id 12345
|
||||
```
|
||||
|
||||
### Manage campaigns
|
||||
|
||||
```bash
|
||||
# List campaigns
|
||||
node tools/clis/hunter.js campaigns list
|
||||
|
||||
# Get campaign details
|
||||
node tools/clis/hunter.js campaigns get --id 12345
|
||||
|
||||
# Start/pause a campaign
|
||||
node tools/clis/hunter.js campaigns start --id 12345
|
||||
node tools/clis/hunter.js campaigns pause --id 12345
|
||||
```
|
||||
|
||||
### Check account usage
|
||||
|
||||
```bash
|
||||
node tools/clis/hunter.js account info
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
- Free plan: 25 searches/month, 50 verifications/month
|
||||
- Paid plans scale with tier
|
||||
- API rate limit: 10 requests/second
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Link building**: Find email contacts at target domains for outreach
|
||||
- **Prospecting**: Build lead lists from company domains
|
||||
- **Verification**: Clean email lists before sending campaigns
|
||||
104
tools/integrations/instantly.md
Normal file
104
tools/integrations/instantly.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Instantly.ai
|
||||
|
||||
Cold email platform with built-in email warmup and campaign management at scale.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Integration | Available | Notes |
|
||||
|-------------|-----------|-------|
|
||||
| API | ✓ | REST API for campaigns, leads, accounts, analytics |
|
||||
| MCP | - | Not available |
|
||||
| CLI | [✓](../clis/instantly.js) | Zero-dependency Node.js CLI |
|
||||
| SDK | - | API-only |
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Type**: API Key (query parameter)
|
||||
- **Parameter**: `api_key={key}`
|
||||
- **Env var**: `INSTANTLY_API_KEY`
|
||||
- **Get key**: [Instantly Settings > Integrations > API](https://app.instantly.ai/app/settings/integrations)
|
||||
|
||||
## Common Agent Operations
|
||||
|
||||
### Manage campaigns
|
||||
|
||||
```bash
|
||||
# List campaigns
|
||||
node tools/clis/instantly.js campaigns list --limit 20
|
||||
|
||||
# Get campaign details
|
||||
node tools/clis/instantly.js campaigns get --id cam_abc123
|
||||
|
||||
# Check campaign status
|
||||
node tools/clis/instantly.js campaigns status --id cam_abc123
|
||||
|
||||
# Launch a campaign
|
||||
node tools/clis/instantly.js campaigns launch --id cam_abc123
|
||||
|
||||
# Pause a campaign
|
||||
node tools/clis/instantly.js campaigns pause --id cam_abc123
|
||||
```
|
||||
|
||||
### Manage leads
|
||||
|
||||
```bash
|
||||
# List leads in a campaign
|
||||
node tools/clis/instantly.js leads list --campaign-id cam_abc123 --limit 50
|
||||
|
||||
# Add a lead
|
||||
node tools/clis/instantly.js leads add --campaign-id cam_abc123 --email john@example.com --first-name John --last-name Doe --company "Example Inc"
|
||||
|
||||
# Delete a lead
|
||||
node tools/clis/instantly.js leads delete --campaign-id cam_abc123 --email john@example.com
|
||||
|
||||
# Check lead status
|
||||
node tools/clis/instantly.js leads status --campaign-id cam_abc123 --email john@example.com
|
||||
```
|
||||
|
||||
### Manage email accounts
|
||||
|
||||
```bash
|
||||
# List connected accounts
|
||||
node tools/clis/instantly.js accounts list --limit 20
|
||||
|
||||
# Check account status
|
||||
node tools/clis/instantly.js accounts status --account-id me@example.com
|
||||
|
||||
# Check warmup status
|
||||
node tools/clis/instantly.js accounts warmup-status --account-id me@example.com
|
||||
```
|
||||
|
||||
### View analytics
|
||||
|
||||
```bash
|
||||
# Campaign analytics
|
||||
node tools/clis/instantly.js analytics campaign --campaign-id cam_abc123 --start 2024-01-01 --end 2024-01-31
|
||||
|
||||
# Step-by-step analytics
|
||||
node tools/clis/instantly.js analytics steps --campaign-id cam_abc123
|
||||
|
||||
# Account-level analytics
|
||||
node tools/clis/instantly.js analytics account --start 2024-01-01 --end 2024-01-31
|
||||
```
|
||||
|
||||
### Manage blocklist
|
||||
|
||||
```bash
|
||||
# List blocked emails/domains
|
||||
node tools/clis/instantly.js blocklist list
|
||||
|
||||
# Add to blocklist
|
||||
node tools/clis/instantly.js blocklist add --entries "competitor.com,spam@example.com"
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
- API rate limits vary by plan
|
||||
- Recommended: stay under 10 requests/second
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Link building at scale**: Run large-volume outreach campaigns with built-in warmup
|
||||
- **Campaign management**: Launch, pause, and monitor cold email campaigns
|
||||
- **Account health**: Monitor email account warmup and deliverability
|
||||
- **Analytics**: Track open rates, reply rates, and campaign performance
|
||||
110
tools/integrations/lemlist.md
Normal file
110
tools/integrations/lemlist.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Lemlist
|
||||
|
||||
Cold email outreach platform with personalization and campaign management.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Integration | Available | Notes |
|
||||
|-------------|-----------|-------|
|
||||
| API | ✓ | REST API for campaigns, leads, activities, webhooks |
|
||||
| MCP | - | Not available |
|
||||
| CLI | [✓](../clis/lemlist.js) | Zero-dependency Node.js CLI |
|
||||
| SDK | - | API-only |
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Type**: Basic Auth (empty username, API key as password)
|
||||
- **Header**: `Authorization: Basic base64(:api_key)`
|
||||
- **Env var**: `LEMLIST_API_KEY`
|
||||
- **Get key**: [Lemlist Settings > Integrations](https://app.lemlist.com/settings/integrations)
|
||||
|
||||
## Common Agent Operations
|
||||
|
||||
### List campaigns
|
||||
|
||||
```bash
|
||||
node tools/clis/lemlist.js campaigns list --offset 0 --limit 20
|
||||
```
|
||||
|
||||
### Get campaign details and stats
|
||||
|
||||
```bash
|
||||
# Get campaign
|
||||
node tools/clis/lemlist.js campaigns get --id cam_abc123
|
||||
|
||||
# Get campaign stats
|
||||
node tools/clis/lemlist.js campaigns stats --id cam_abc123
|
||||
|
||||
# Export campaign data
|
||||
node tools/clis/lemlist.js campaigns export --id cam_abc123
|
||||
```
|
||||
|
||||
### Manage leads in a campaign
|
||||
|
||||
```bash
|
||||
# List leads
|
||||
node tools/clis/lemlist.js leads list --campaign-id cam_abc123
|
||||
|
||||
# Add a lead
|
||||
node tools/clis/lemlist.js leads add --campaign-id cam_abc123 --email john@example.com --first-name John --last-name Doe --company "Example Inc"
|
||||
|
||||
# Get lead details
|
||||
node tools/clis/lemlist.js leads get --campaign-id cam_abc123 --email john@example.com
|
||||
|
||||
# Remove a lead
|
||||
node tools/clis/lemlist.js leads delete --campaign-id cam_abc123 --email john@example.com
|
||||
```
|
||||
|
||||
### Manage unsubscribes
|
||||
|
||||
```bash
|
||||
# List unsubscribed emails
|
||||
node tools/clis/lemlist.js unsubscribes list
|
||||
|
||||
# Add to unsubscribe list
|
||||
node tools/clis/lemlist.js unsubscribes add --email john@example.com
|
||||
|
||||
# Remove from unsubscribe list
|
||||
node tools/clis/lemlist.js unsubscribes delete --email john@example.com
|
||||
```
|
||||
|
||||
### View activities
|
||||
|
||||
```bash
|
||||
# All activities
|
||||
node tools/clis/lemlist.js activities list
|
||||
|
||||
# Filter by campaign and type
|
||||
node tools/clis/lemlist.js activities list --campaign-id cam_abc123 --type emailsOpened
|
||||
```
|
||||
|
||||
### Manage webhooks
|
||||
|
||||
```bash
|
||||
# List hooks
|
||||
node tools/clis/lemlist.js hooks list
|
||||
|
||||
# Create a webhook
|
||||
node tools/clis/lemlist.js hooks create --target-url https://example.com/webhook --event emailsOpened
|
||||
|
||||
# Delete a webhook
|
||||
node tools/clis/lemlist.js hooks delete --id hook_123
|
||||
```
|
||||
|
||||
### Team info
|
||||
|
||||
```bash
|
||||
node tools/clis/lemlist.js team info
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
- API rate limits vary by plan
|
||||
- Recommended: stay under 10 requests/second
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Link building outreach**: Add prospects to campaigns for backlink requests
|
||||
- **Campaign management**: Monitor open/reply rates across outreach campaigns
|
||||
- **Lead management**: Add, remove, and track leads across campaigns
|
||||
- **Webhook integration**: Get real-time notifications for email events
|
||||
94
tools/integrations/snov.md
Normal file
94
tools/integrations/snov.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Snov.io
|
||||
|
||||
Email finding, verification, and drip campaign platform for outreach.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Integration | Available | Notes |
|
||||
|-------------|-----------|-------|
|
||||
| API | ✓ | REST API for email finding, verification, prospects, drip campaigns |
|
||||
| MCP | - | Not available |
|
||||
| CLI | [✓](../clis/snov.js) | Zero-dependency Node.js CLI |
|
||||
| SDK | - | API-only |
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Type**: OAuth2 client credentials
|
||||
- **Flow**: POST to `/oauth/access_token` with client_id + client_secret
|
||||
- **Env vars**: `SNOV_CLIENT_ID`, `SNOV_CLIENT_SECRET`
|
||||
- **Get keys**: [Snov.io > Integration > API](https://app.snov.io/integration/api)
|
||||
|
||||
The CLI handles token acquisition automatically.
|
||||
|
||||
## Common Agent Operations
|
||||
|
||||
### Search emails by domain
|
||||
|
||||
```bash
|
||||
node tools/clis/snov.js domain search --domain example.com --type all --limit 10
|
||||
```
|
||||
|
||||
### Find a specific person's email
|
||||
|
||||
```bash
|
||||
node tools/clis/snov.js email find --domain example.com --first-name John --last-name Doe
|
||||
```
|
||||
|
||||
### Verify an email
|
||||
|
||||
```bash
|
||||
node tools/clis/snov.js email verify --email john@example.com
|
||||
```
|
||||
|
||||
### Find prospect by email
|
||||
|
||||
```bash
|
||||
node tools/clis/snov.js prospect find --email john@example.com
|
||||
```
|
||||
|
||||
### Add prospect to a list
|
||||
|
||||
```bash
|
||||
node tools/clis/snov.js prospect add --email john@example.com --first-name John --last-name Doe --list-id 12345
|
||||
```
|
||||
|
||||
### Manage prospect lists
|
||||
|
||||
```bash
|
||||
# List all lists
|
||||
node tools/clis/snov.js lists list
|
||||
|
||||
# Get prospects in a list
|
||||
node tools/clis/snov.js lists prospects --id 12345 --page 1 --per-page 50
|
||||
```
|
||||
|
||||
### Check domain technology stack
|
||||
|
||||
```bash
|
||||
node tools/clis/snov.js technology check --domain example.com
|
||||
```
|
||||
|
||||
### Manage drip campaigns
|
||||
|
||||
```bash
|
||||
# List campaigns
|
||||
node tools/clis/snov.js drips list
|
||||
|
||||
# Get campaign details
|
||||
node tools/clis/snov.js drips get --id 12345
|
||||
|
||||
# Add prospect to drip campaign
|
||||
node tools/clis/snov.js drips add-prospect --id 12345 --email john@example.com
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
- Rate limits vary by plan
|
||||
- OAuth tokens expire after a set period; CLI handles refresh automatically
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Link building**: Find contacts and run automated drip outreach
|
||||
- **Prospecting**: Build and manage prospect lists
|
||||
- **Technology research**: Check what tech stack a target domain uses
|
||||
- **Email verification**: Clean lists before sending
|
||||
Loading…
Reference in a new issue