Developer docs
Outbound as code. Signal queries, dry-run, vibejoy.yaml, cost ledger, reply triage, BYO sending. Drive it from Cursor/Claude (MCP), your terminal (CLI), or any REST client. Edit every line of copy before it sends.
Quickstart
Get buyers found and a campaign live in under 5 minutes. Dry-run first if you want to preview volume without spending.
Fastest path for developers
Set up brand (URL or YAML)
Paste a site URL, or commit vibejoy.yaml with brand + signalQuery + mail provider.
Dry-run, then run
vibejoy query 'hiring AND funding' --dry-run then vibejoy run or vibejoy apply.
CLI
vibejoy setup url <url>vibejoy run --dry-runvibejoy applyMCP (Agent)
signal_queryapply_configrun_pipelineREST
POST /api/leads/signal-queryPOST /api/config/applyPOST /api/pipeline/runConnect VibeJoy to Claude Desktop or Cursor. Your agent can then find leads, enrich contacts, and run full campaigns through natural-language prompts.
Get your API key
Go to Settings → Developer and click Generate API Key. It starts with vj_.
Add to your MCP config
Windows path tip: hold Shift and right-click vibejoy\mcp\index.js in Explorer → Copy as path. Then replace every \ with \\ in the JSON.
{
"mcpServers": {
"vibejoy": {
"command": "node",
"args": ["C:\\Users\\Donna\\Desktop\\vibejoy\\mcp\\index.js"],
"env": {
"VIBEJOY_API_KEY": "vj_your_api_key_here",
"VIBEJOY_API_URL": "https://api.vibejoy.io"
}
}
}
}For Claude Desktop on macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
For Claude Desktop on Windows: %APPDATA%\Claude\claude_desktop_config.json
Reload Cursor (or restart Claude Desktop)
In Cursor: Ctrl+Shift+P → Reload Window. The vibejoy tools will appear in your agent's tool list.
Run your first pipeline - one prompt does everything
Try these in order for fastest results:
"Set up my brand from my website: vibejoy.io"
setup_brand_from_url→ AI scrapes your site and fills brand + ICP"Dry-run: hiring AND funding for my ICP"
signal_query→ preview count, nothing saved"Apply my vibejoy.yaml and activate the pipeline"
apply_configrun_pipeline→ config applied, campaign live"Show my credits, spend, and pricing"
get_costsget_pricing→ wallet + Free / $25 / Growth $249Run a Full Campaign
Three things required before a campaign sends - all fully automatable
1. Brand context - set once via MCP (setup_brand), CLI (vibejoy brand setup), or dashboard.
2. SendGrid - connect once via MCP (connect_sendgrid), CLI (vibejoy sendgrid connect), or dashboard.
3. Recipients + send date - pass allEnrichedLeads: true (or leadIds[]) to create_campaign or vibejoy campaign create --all-leads. No dashboard required.
All three steps work via MCP, CLI, and REST API. A single agent prompt or CLI command can run the full pipeline end to end.
Complete pipeline
Via the Dashboard
The dashboard walks you through campaign creation in 3 steps. Leads must be found and enriched first - the campaign builder pulls recipients directly from your enriched leads list.
Go to Leads → click Find leads → choose a signal (hiring, funding, Reddit, LinkedIn, local) or run Auto-find. Intent leads on paid plans (Pay as you go or Growth) get an AI opener automatically. Click Enrich on any lead to get their verified email and contact details. You must enrich leads before they can be added to a campaign.
Click New Campaign. Optionally override the target audience, goal, or add extra context for the AI - or leave blank to use your saved brand defaults. Click Generate Campaign. The AI writes a 3-email sequence, LinkedIn post, DM sequence, and ad headlines in your brand voice.
Read through the generated emails, LinkedIn post, and DM sequence. Edit anything inline. Set the Email 1 send date - emails 2 and 3 are auto-scheduled 3 and 7 days later. The LinkedIn post date defaults to the same time as Email 1; clear it if you do not want it scheduled. Save as draft if you need more time.
Click "Import from leads →" to open the leads picker. This shows all your enriched leads with verified emails. Tick the ones you want to include and click Add. You can also add recipients manually (+ Add row) or paste from a CSV. If the list is empty, go back to the Leads page and enrich some leads first.
Check the Pre-launch checklist - all items must be green (brand set, recipients added, send date in the future, SendGrid connected). Click Activate campaign. Emails are scheduled and send automatically at the configured times - no external queue needed.
Leads must be enriched before they appear in the campaign builder
The "Import from leads" picker only shows leads with a verified email address. If the list is empty, go to Leads → Enrich first. Enrichment pulls the decision-maker's verified email and contact details - this is what makes personalised delivery possible.
Via MCP - full setup and campaign in one conversation
The agent handles everything. A fresh account goes from zero to live campaign without touching the dashboard:
"Check my VibeJoy setup - is everything ready to run campaigns?"
check_integrations→ shows brand context status, SendGrid status, LinkedIn status"Set up my brand: business is Acme CRM, we help SDR teams automate outreach and book more demos, targeting VP Sales at 50-500 person B2B SaaS companies. Tone: Direct, goal: Book demos."
setup_brand→ brand context saved, AI now writes for your exact ICP"Connect SendGrid with key SG.xxx, sending from 'Jane at Acme' <jane@acme.com>"
connect_sendgrid→ SendGrid connected, email delivery ready"Find 25 companies in London actively hiring SDRs"
find_leads→ 25 leads saved with websites and LinkedIn"Enrich those leads - I need decision-maker emails"
enrich_contacts→ verified emails, job titles, LinkedIn profiles"Write a campaign for sales leaders at companies scaling their SDR team. Goal: book a 20-minute demo. Use all my enriched leads as recipients and activate it."
create_campaign→ 3-email sequence written, recipients added, campaign live - first replies incoming"Show me the social content for that campaign so I can schedule it"
get_campaign_social→ LinkedIn post, DM sequence, Reddit post and reply templates"What are my campaign stats?"
get_campaignsget_dashboard_stats→ sends, opens, repliesDo it all in one prompt (after initial setup)
"Find 25 funded Series A startups, enrich their contacts, write a campaign to book demos, use all enriched leads as recipients, and activate it."
The agent chains find_leads → enrich_contacts → create_campaign (with allEnrichedLeads + activate) automatically. Your first replies come in before your next standup.
Via CLI
# ── ONE-TIME SETUP ──────────────────────────────────────────────────────────
# Set up brand context (required before campaigns)
vibejoy brand setup \
--name "Acme CRM" \
--offer "We help SDR teams automate outreach and book more demos" \
--icp "VP Sales at 50-500 person B2B SaaS companies" \
--tone "Direct" \
--goal "Book demos"
# Connect SendGrid (required before email delivery)
vibejoy sendgrid connect \
--key "SG.your_sendgrid_key" \
--from-name "Jane at Acme" \
--from-email "jane@acme.com"
# Verify everything is ready
vibejoy status
# ── REPEATABLE CAMPAIGN PIPELINE ─────────────────────────────────────────────
# Step 1: Find companies hiring for roles your SaaS automates
vibejoy find --signal hiring --query "RevOps Manager" --location "United Kingdom" --limit 25 --output leads.csv
# Step 2: Enrich - target only the decision-maker you want
vibejoy enrich --unenriched --titles "CEO,Founder,CTO"
# Or filter by seniority level (c_suite, founder, vp, director, manager, owner)
# vibejoy enrich --unenriched --seniority "c_suite,founder"
# Or enrich a specific batch, targeting CFOs only
# vibejoy enrich --file leads.csv --titles "CFO,Finance Director" --output enriched.csv
# Step 3: Create campaign, add all enriched leads, and activate in one command
vibejoy campaign create \
--audience "RevOps managers scaling their CRM stack" \
--goal "book a 20-minute product demo" \
--all-leads \
--email1-date 2026-07-10 \
--activate
# Step 4: Get social content (LinkedIn + Reddit) to schedule via Zernio
vibejoy campaign social --id CAMPAIGN_ID --output social.txt
# Step 5: Monitor stats
vibejoy stats
vibejoy campaign listVia REST API (Node.js)
const BASE = 'https://api.vibejoy.io';
const KEY = 'vj_your_api_key_here';
const headers = { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json' };
// ── ONE-TIME SETUP ────────────────────────────────────────────────────────────
// Step 0a: Set up brand context (do this once - the AI uses it for all campaigns)
await fetch(`${BASE}/api/brand`, {
method: 'POST', headers,
body: JSON.stringify({
businessName: 'Acme CRM',
offer: 'We help SDR teams automate outreach and book more meetings',
icp: 'VP Sales at 50-500 person B2B SaaS companies',
tone: 'Direct', // Bold | Warm | Direct | Professional | Conversational
goal: 'Book demos' // Book demos | Get signups | Build awareness | Book a call
})
}).then(r => r.json());
// Step 0b: Connect SendGrid (do this once - required for email delivery)
await fetch(`${BASE}/api/integrations/sendgrid/connect`, {
method: 'POST', headers,
body: JSON.stringify({
apiKey: 'SG.your_sendgrid_api_key',
fromName: 'Jane at Acme',
fromEmail: 'jane@acme.com' // must be a verified sender in SendGrid
})
}).then(r => r.json());
// ── REPEATABLE CAMPAIGN PIPELINE ──────────────────────────────────────────────
// Step 1: Find hiring signal leads
const { leads } = await fetch(`${BASE}/api/leads/intent/jobs`, {
method: 'POST', headers,
body: JSON.stringify({ jobTitles: ['RevOps Manager'], location: 'United Kingdom', maxResults: 25 })
}).then(r => r.json());
console.log(`Found ${leads.length} leads`);
const leadIds = leads.map(l => l._id);
// Step 2: Enrich contacts (parallel, streams progress if you use /enrich-stream)
const { enriched, newLeadsCreated } = await fetch(`${BASE}/api/leads/enrich`, {
method: 'POST', headers,
body: JSON.stringify({ leadIds })
}).then(r => r.json());
console.log(`Enriched ${enriched} leads, ${newLeadsCreated} extra contacts found`);
// Step 3: Generate + add recipients + activate in one call
// Pass allEnrichedLeads: true to auto-add every enriched lead
// Pass activate: true to go live immediately (requires SendGrid connected)
const { campaignId, name, output, activated, recipientCount } = await fetch(`${BASE}/api/campaigns/generate`, {
method: 'POST', headers,
body: JSON.stringify({
targetAudience: 'RevOps managers scaling their tech stack',
campaignGoal: 'book a 20-minute product demo',
extraContext: 'Mention we integrate with their existing CRM',
allEnrichedLeads: true, // add all enriched leads
email1At: new Date(Date.now() + 24*60*60*1000).toISOString(), // send tomorrow
activate: true, // go live immediately
})
}).then(r => r.json());
console.log(`Campaign: ${name} (ID: ${campaignId})`);
console.log(`Recipients: ${recipientCount} | Activated: ${activated}`);
console.log(`Email 1 subject: ${output.emails?.email1?.subject}`);
// Step 5: Check stats any time
const stats = await fetch(`${BASE}/api/dashboard/stats`, { headers }).then(r => r.json());
console.log(`Total leads: ${stats.totalLeads} | AI openers: ${stats.openersGenerated} (${stats.openersCoverage}% coverage) | Emails sent: ${stats.totalEmailsSent}`);Via REST API (Python)
import requests
BASE = "https://api.vibejoy.io"
KEY = "vj_your_api_key_here"
HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
# ── ONE-TIME SETUP ────────────────────────────────────────────────────────────
# Set up brand context
requests.post(f"{BASE}/api/brand", headers=HEADERS, json={
"businessName": "Acme CRM",
"offer": "We help SDR teams automate outreach and book more meetings",
"icp": "VP Sales at 50-500 person B2B SaaS companies",
"tone": "Direct",
"goal": "Book demos"
})
# Connect SendGrid
requests.post(f"{BASE}/api/integrations/sendgrid/connect", headers=HEADERS, json={
"apiKey": "SG.your_sendgrid_api_key",
"fromName": "Jane at Acme",
"fromEmail": "jane@acme.com"
})
# ── REPEATABLE CAMPAIGN PIPELINE ──────────────────────────────────────────────
# Step 1: Find leads
r = requests.post(f"{BASE}/api/leads/intent/jobs",
headers=HEADERS,
json={"jobTitles": ["RevOps Manager"], "location": "United Kingdom", "maxResults": 25})
leads = r.json()["leads"]
lead_ids = [l["_id"] for l in leads]
print(f"Found {len(lead_ids)} leads")
# Step 2: Enrich
r = requests.post(f"{BASE}/api/leads/enrich", headers=HEADERS, json={"leadIds": lead_ids})
print(r.json()) # { enriched, newLeadsCreated, failed }
# Step 3: Generate + add all enriched leads + activate in one call
from datetime import datetime, timedelta
tomorrow = (datetime.utcnow() + timedelta(days=1)).isoformat() + "Z"
r = requests.post(f"{BASE}/api/campaigns/generate", headers=HEADERS, json={
"targetAudience": "RevOps managers scaling their tech stack",
"campaignGoal": "book a 20-minute product demo",
"allEnrichedLeads": True, # auto-add every enriched lead as recipient
"email1At": tomorrow, # send tomorrow
"activate": True # go live immediately (requires SendGrid)
})
data = r.json()
print(f"Campaign: {data['name']} | Recipients: {data['recipientCount']} | Live: {data['activated']}")
# Step 4: Check opener + send analytics
stats = requests.get(f"{BASE}/api/dashboard/stats", headers=HEADERS).json()
print(f"Leads: {stats['totalLeads']} | AI openers: {stats['openersGenerated']} ({stats['openersCoverage']}% coverage)")
print(f"Openers by signal: {stats.get('openersBySource', {})}")AI Openers
An AI opener is a single sentence — under 20 words — that Claude writes for each individual lead based on the exact intent signal that found them. It opens the email. It is the difference between a reply and a delete.
The goal: make them think "this person gets exactly what I'm dealing with"
Signal: Hiring: SDR Manager
"Bringing on SDRs before the sequence is proven tends to multiply the noise, not the pipeline."
Signal: Funding: Series B announced
"The jump from Series A growth to Series B execution usually surfaces a very specific GTM problem."
Signal: Reddit: struggling with cold email open rates
"Hitting the wall where the list is fine but the angle just isn't landing is a specific kind of frustrating."
Signal: LinkedIn post: asking about hiring for growth
"The sequence that works at 20 reps usually needs a full rebuild by the time you hit 60."
Signal: GitHub: new devops repo pushed
"Teams that build the infra first and the ops playbook second usually hit the same gap around month three."
Signal: Contract: federal award $2M
"Winning a large government contract usually surfaces the vendor management overhead nobody planned for."
Why they work
- Signal-specific, not generic. Each opener is written from the specific signal — the job title they're hiring for, the exact stage they raised, the specific thing they posted about. Not "I noticed you raised $X."
- Never mentions the source. If someone vented on Reddit, the opener reflects that frustration without saying "I saw your Reddit post." It sounds like you thought of it yourself.
- Peer tone, not sales tone. The system prompt instructs Claude: "sounds like a peer observation typed on a phone — not a sales line." No em dashes. No clichés. No quotes.
- Under 20 words. Short enough to read in a second. Specific enough to stop the scroll.
Pay as you go and Growth only
AI openers are generated automatically for every intent-signal lead (job_board, funding, github, contracts, local_biz, yc, linkedin_post, reddit) on paid plans. The free plan does not include AI openers. Local / Maps discovery leads may not get openers as they have no intent signal text to write from.
How they work technically
- Stored on the lead as
personalizedOpener. - Generated immediately after lead discovery, in batches of 12, using Claude Sonnet.
- Each signal type has its own prompt style — the hiring prompt focuses on the challenge that role reveals; the funding prompt acknowledges the milestone and pivots to a stage-specific problem; the Reddit/LinkedIn prompts reflect the frustration without citing the source.
- You can edit any opener before the campaign sends — go to Leads, click a lead, edit the opener field.
- Cost: $0.03 per opener (shown in your cost ledger as
ai_opener).
Where to see them
Each row shows the opener under Intent / opener. Click to read the full line + original signal.
Stat card: AI openers count, % of leads covered, and breakdown by signal source.
All-time + monthly opener counts, coverage %, and openers by signal (hiring, funding, reddit, linkedin_post).
GET /api/dashboard/stats and GET /api/dashboard/usage return openersGenerated, openersCoverage, and openersBySource.
Stats API response
GET /api/dashboard/stats returns:
{
"totalCampaigns": 4,
"activeCampaigns": 1,
"totalEmailsSent": 120,
"linkedinPostsSent": 2,
"totalLeads": 840,
"enrichedLeads": 310,
"openersGenerated": 210,
"openersCoverage": 25,
"openersBySource": {
"job_board": 90,
"funding": 55,
"linkedin_post": 40,
"reddit": 25
}
}GET /api/dashboard/usage?month=YYYY-MM includes the same opener fields under usage and allTime, plus limits.aiOpeners (whether your plan includes them).
What is not tracked yet
Reply rate or open rate broken down by opener vs no-opener is not available yet. Current analytics count how many openers were generated and by which signal - not send performance. Wire SendGrid engagement events if you need that later.
Editing openers
Click any opener on the Leads page to edit or write your own. Saved via PATCH /api/leads/:id with personalizedOpener. See Edit Copy for campaign emails, LinkedIn, Reddit, and reply drafts.
Edit Copy
AI drafts are a starting point. Edit everything before (or after) activation: campaign emails, LinkedIn posts/DMs, Reddit posts, ad headlines, lead openers, and triage reply drafts. Unsent messages pick up your edits; already-delivered messages are not resent.
Step 2 Review & edit: every email subject/body, LinkedIn, DMs, Reddit, ad headlines are editable before activate.
Edit copy button on draft, scheduled, paused, or active campaigns. Save writes to PATCH /api/campaigns/:id { output }.
Click an opener (or Write opener) to edit personalizedOpener inline.
Dashboard: Edit reply, then Approve or Approve & send with your edited text.
update_campaign with output; PATCH /api/leads/:id; POST /api/replies/:id/approve { approvedReply }.
# Update campaign copy via API
PATCH /api/campaigns/:id
{
"output": {
"emails": {
"email1": { "subject": "Quick question", "body": "…" }
},
"linkedinPost": "…"
}
}
# Edit a lead opener
PATCH /api/leads/:id
{ "personalizedOpener": "Scaling eng usually means deploy friction hit the board deck." }Filter Leads by Position
Once you have enriched leads, you can filter them by job title or position and add the filtered set directly to a new or existing campaign — without running the full pipeline again.
Three ways to filter and add
The "Filter leads by position" widget lets you type a role (e.g. "CMO", "VP Sales"), see matching enriched leads instantly, then create a new campaign or append them to an existing one.
Use the "Filter by position…" input in the filter bar. All leads matching the role are shown. Select them and use "Add to campaign →" — a modal lets you pick new or existing campaign.
In any campaign's Recipients section, click "Add leads". Use the "Filter by role…" input inside the modal to narrow down enriched leads before selecting.
Adding to an existing campaign
When you add leads to an existing campaign that is already active or scheduled, the new recipients are appended — they receive the remaining emails in the sequence starting from the next unsent step. If the campaign is a draft, you can edit the copy before activating.
Via API
# Filter enriched leads by job title (server-side, paginated)
GET /api/leads?enriched=true&title=CMO&limit=100
# Append those lead IDs to an existing campaign
POST /api/campaigns/:id/add-leads
{ "leadIds": ["lead1", "lead2", ...] }
# Or create a brand-new campaign from a specific set of leads
POST /api/campaigns/generate
{
"leadIds": ["lead1", "lead2"],
"audience": "CMOs at B2B SaaS companies",
"activate": true
}Autopilot — Set & Forget
Configure once. VibeJoy runs the full outbound loop every hour, indefinitely — no manual intervention needed. The pipeline finds fresh hiring signals, enriches contacts, adds them to a running campaign, and emails them automatically.
What runs automatically
| What | Frequency | Notes |
|---|---|---|
| Job board scan | Every hour | Finds companies actively hiring your target roles |
| Lead enrichment | Every hour | Finds decision-maker email via Apollo waterfall |
| Email 1 | Within minutes of enrichment | Sent during 9am–7pm BST only |
| Email 2 | 3 days after Email 1 | Auto-scheduled per recipient |
| Email 3 | 7 days after Email 1 | Final soft close |
| Reply capture | Within 30 seconds of reply | Classified + suggested response appears in dashboard |
One-time setup checklist
- Set your brand in Settings → Brand or commit a
vibejoy.yamland runvibejoy apply - Connect SendGrid (or SES) in Settings → Integrations — use a Restricted key with Mail Send permission
- Set up Reply tracking — add MX record and configure SendGrid Inbound Parse (instructions shown after connecting SendGrid)
- Apply your pipeline schedule via the
vibejoy.yaml— or enable it from the API Docs page
vibejoy.yaml for full autopilot
version: 1
brand:
businessName: "AppzJoy"
offer: "Unlimited dev subscription — one flat monthly rate"
icp: "SaaS founders hiring frontend or fullstack engineers"
tone: Direct
goal: "Get signups"
pipeline:
skip: true # schedule handles runs — don't fire one on apply
mail:
provider: sendgrid
schedule:
enabled: true
mode: full_pipeline # find → enrich → generate copy → send
cadenceDays: 0.042 # hourly
sources: [job_board]
leadLimit: 7 # 7 per run × 24 runs = ~168/day max
dailyEmailCap: 500 # paid SendGrid plan
targetRoles: [Founder, CTO, CEO, "Co-Founder", "VP Engineering"]How the campaign works
VibeJoy maintains one active campaign per pipeline. New contacts found each hour are added as recipients to that campaign. Each new recipient gets the full 3-email sequence on its own schedule — Email 1 immediately, Email 2 after 3 days, Email 3 after 7 days. The campaign runs indefinitely — there is no end date unless you disable the schedule in your yaml.
Daily email cap prevents blasting. Sends are spread across business hours (9am–7pm BST). Roughly 7–17 new contacts are added per hourly run, so you send ~100–200 emails per day naturally.
Dry-run Mode
Preview discovery volume without saving leads, creating campaigns, or sending email. Pass dryRun: true on the pipeline, signal query, or config apply endpoints.
# CLI
vibejoy run --dry-run --audience "Series A CTOs"
vibejoy query 'hiring AND funding' --dry-run
vibejoy apply --file vibejoy.yaml --dry-run
# API
POST /api/pipeline/run { "dryRun": true, "sources": ["job_board"], "leadLimit": 50 }
POST /api/leads/signal-query { "query": "hiring AND funding", "dryRun": true }Config-as-code (vibejoy.yaml)
Commit outbound config next to your app. Apply brand, schedule, mail provider preference, signal query, and pipeline in one shot. Secrets (API keys) stay in Settings / env - never in YAML.
version: 1
brand:
businessName: "Acme DevTools"
offer: "Ship faster with fewer incidents"
icp: "CTOs at Series A-C SaaS"
tone: Direct
goal: "Book calls"
pipeline:
dryRun: false
leadLimit: 50
sources: [job_board, funding]
titles: [CTO, Founder]
activate: true
signalQuery: 'hiring AND (funding OR keyword:"devops")'
mail:
provider: sendgrid # or ses | postal
schedule:
enabled: false
targetCount: 100
runAtHour: 8vibejoy apply --file vibejoy.yaml --dry-run
vibejoy apply --file vibejoy.yaml
# API
POST /api/config/apply { "yaml": "<file contents>", "dryRun": false }
GET /api/config/schemaSee vibejoy.example.yaml in the repo root. MCP: apply_config.
Custom Signal Query Language
Boolean combinators over intent atoms. Prefer this when you need intersections (e.g. hiring and recently funded) instead of OR-ing sources.
hiringCompanies hiring relevant eng/product roles
fundingRecently funded startups
linkedinLinkedIn demand / posts
painReddit / pain signals
mapsGoogle Maps company discovery
keyword:"..."Google Search company discovery for a phrase
Operators: AND OR NOT and parentheses.
vibejoy query 'hiring AND (funding OR keyword:"platform eng")' --limit 40
# Or via pipeline
vibejoy run --query 'hiring AND funding' --dry-run
POST /api/leads/signal-query
{ "query": "hiring AND funding", "maxResults": 50 }MCP: signal_query. Also pass signalQuery on run_pipeline.
Your costs & pricing
The ledger shows what you pay (credit debit), not VibeJoy infrastructure costs. Free trial usage is $0. Paid usage deducts from your wallet at published rates (~$0.05 per lead found).
| Option | Price | Credits | Notes |
|---|---|---|---|
| Free | $0 | — | 100-lead trial |
| Pay as you go | $25 | $25 | ≈125 fully piped · ≈500 found · ≈50 campaigns |
| Growth | $249/mo | $320/mo | ≈1,600 fully piped leads / mo |
vibejoy costs
vibejoy costs --month 2026-08
vibejoy pricing
vibejoy credits balance
vibejoy credits buy
GET /api/costs
GET /api/costs/entries
GET /api/costs/pricing
POST /api/paypal/credits { "pack": "pack25" }MCP: get_costs · get_pricing · buy_credits (returns PayPal URL). Dashboard: Buy $25 credits starts checkout immediately.
BYO Sending - SendGrid, AWS SES, Postal
VibeJoy is intelligence-first. You bring delivery: default SendGrid, or AWS SES / self-hosted Postal. Connect credentials in Settings (or via API). Campaign scheduler routes through your active provider.
/api/integrations/mailCurrent provider status
/api/integrations/mail/providerSet active provider
{ "provider": "ses" }
/api/integrations/ses/connectConnect AWS SES
{ "accessKeyId": "...", "secretAccessKey": "...", "region": "us-east-1", "fromEmail": "you@domain.com" }
/api/integrations/postal/connectConnect Postal
{ "apiUrl": "https://postal.yourdomain.com", "apiKey": "...", "fromEmail": "you@domain.com" }
SES requires @aws-sdk/client-sesv2 on the API server. Set mail.provider in vibejoy.yaml to prefer a provider (credentials still via Settings).
Daily Lead Pipeline - Wake Up to New Buyers Every Morning
Set VibeJoy on a schedule so it finds in-market buyers every day, enriches only the ones it hasn't seen before, and drips them into a live campaign - while you keep building. Replies arrive in your inbox without any manual prospecting.
How it works - the daily loop
Duplicate detection is built in - if VibeJoy finds a company it already has, it skips it. Only genuinely new leads are enriched and added.
Option 1 - CLI + cron (simplest for developers)
Save this as a shell script and schedule it with cron (Linux/macOS) or Task Scheduler (Windows).
#!/bin/bash
# Daily VibeJoy pipeline - finds CEOs/CFOs at companies hiring for relevant roles,
# enriches new ones, and adds them to a live campaign.
# Schedule: run daily at 7am with cron: 0 7 * * * /home/yourname/vibejoy-daily.sh
set -e # stop on any error
echo "[$(date)] Starting daily VibeJoy pipeline..."
# ── Step 1: Find new companies (duplicates are skipped automatically) ──────────
vibejoy find \
--signal hiring \
--query "CFO OR Chief Financial Officer OR Finance Director" \
--location "United Kingdom" \
--limit 25
vibejoy find \
--signal hiring \
--query "CEO OR Chief Executive OR Managing Director" \
--location "United States" \
--limit 25
# ── Step 2: Enrich only leads not yet enriched, targeting CFOs/CEOs ────────────
vibejoy enrich \
--unenriched \
--titles "CEO,CFO,Chief Executive,Chief Financial Officer,Managing Director,Finance Director" \
--seniority "c_suite,founder"
# ── Step 3: Add new enriched leads to the existing live campaign ───────────────
# Replace CAMPAIGN_ID with your actual campaign ID (vibejoy campaign list)
vibejoy campaign activate \
--id CAMPAIGN_ID \
--all-leads
echo "[$(date)] Pipeline complete."Make it executable and schedule it:
# Make executable
chmod +x vibejoy-daily.sh
# Schedule via cron (Linux/macOS) - runs every day at 7am
crontab -e
# Add this line:
0 7 * * * /home/yourname/vibejoy-daily.sh >> /var/log/vibejoy.log 2>&1
# Windows Task Scheduler - create a Basic Task:
# Program: node
# Arguments: C:\Users\YourName\Desktop\vibejoy-daily.sh
# Trigger: Daily at 7:00 AMOption 2 - GitHub Actions (zero-infrastructure scheduling)
No server needed. GitHub runs the workflow on your schedule for free on public repos, and free minutes on private repos are generous.
name: Daily Lead Discovery
on:
schedule:
- cron: '0 7 * * 1-5' # 7am UTC, Monday to Friday
workflow_dispatch: # also allows manual trigger from GitHub UI
jobs:
discover:
runs-on: ubuntu-latest
steps:
- name: Install VibeJoy CLI
run: npm install -g vibejoy-cli # or: npm install -g .
- name: Authenticate
run: vibejoy auth --key ${{ secrets.VIBEJOY_API_KEY }}
env:
VIBEJOY_API_KEY: ${{ secrets.VIBEJOY_API_KEY }}
- name: Find CEOs at companies hiring senior roles
run: |
vibejoy find --signal hiring --query "CEO OR Chief Executive" --location "United Kingdom" --limit 20
vibejoy find --signal hiring --query "CFO OR Finance Director" --location "United States" --limit 20
- name: Enrich only new leads (CEOs and CFOs only)
run: |
vibejoy enrich --unenriched \
--titles "CEO,CFO,Chief Executive,Finance Director" \
--seniority "c_suite,founder"
- name: Add to campaign and re-activate
run: vibejoy campaign activate --id ${{ secrets.CAMPAIGN_ID }} --all-leads
env:
CAMPAIGN_ID: ${{ secrets.CAMPAIGN_ID }}GitHub Secrets to add
In your GitHub repo: Settings → Secrets → New repository secret
VIBEJOY_API_KEY- yourvj_key from SettingsCAMPAIGN_ID- the campaign ID to keep feeding (runvibejoy campaign listto find it)
Option 3 - REST API + any scheduler (cron-job.org, Zapier, n8n)
If you don't want to run code, use a free HTTP scheduler like cron-job.org to hit the API directly. Chain calls in order:
// Step 1: Find new companies hiring for senior roles (run daily)
// VibeJoy automatically skips companies it already has - safe to run every day
const findRes = await fetch('https://api.vibejoy.io/api/leads/intent/jobs', {
method: 'POST',
headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
body: JSON.stringify({
jobTitles: ['CEO', 'CFO', 'Chief Financial Officer', 'Managing Director'],
location: 'United Kingdom',
maxResults: 25
})
}).then(r => r.json());
console.log(`Found ${findRes.count} new leads, skipped ${findRes.skippedDuplicates} duplicates`);
// Step 2: Enrich only unenriched leads - target CEO/CFO titles specifically
const enrichRes = await fetch('https://api.vibejoy.io/api/leads/enrich', {
method: 'POST',
headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
body: JSON.stringify({
unenrichedOnly: true,
titles: ['CEO', 'CFO', 'Chief Executive Officer', 'Chief Financial Officer', 'Managing Director', 'Finance Director'],
seniority: ['c_suite', 'founder']
})
}).then(r => r.json());
console.log(`Enriched ${enrichRes.enriched} contacts`);
// Step 3: Add all enriched leads to a live campaign (safe to re-run - no duplicates added)
const updateRes = await fetch('https://api.vibejoy.io/api/campaigns/CAMPAIGN_ID', {
method: 'PATCH',
headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
body: JSON.stringify({ allEnrichedLeads: true })
}).then(r => r.json());
console.log(`Campaign now has ${updateRes.recipientCount} recipients`);Option 4 - MCP / AI Agent (conversational recurring prompt)
Paste this prompt in Claude or Cursor each morning, or schedule it with an agent runner:
Daily agent prompt - copy and run each morning
"Find 25 UK companies hiring CEOs or CFOs - skip any I already have"
find_leads→ 18 new leads, 7 skipped (already in your list)"Enrich the new unenriched leads - I only want CEO or CFO contacts"
enrich_contacts→ 14 contacts enriched with decision-maker emails"Add all enriched leads to my outreach campaign and give me the new recipient count"
update_campaignget_campaigns→ campaign updated, 47 total recipientsOption 5 - n8n workflow (full no-code automation)
Build this in n8n and schedule it with the Cron trigger node. Set to run Monday-Friday at 7am.
Schedule: 0 7 * * 1-5 (Mon-Fri 7am)POST /api/leads/intent/jobs · body: { jobTitles: ["CEO","CFO"], location: "UK", maxResults: 25 }{{ $json.count > 0 }} - stops if no new leads foundPOST /api/leads/enrich · body: { unenrichedOnly: true, titles: ["CEO","CFO"], seniority: ["c_suite"] }PATCH /api/campaigns/CAMPAIGN_ID · body: { allEnrichedLeads: true }"Daily pipeline: {{ $node[2].json.count }} new leads, {{ $node[4].json.enriched }} enriched, {{ $node[5].json.recipientCount }} in campaign"Tips for a healthy daily pipeline
- Duplicate detection is automatic - running the same find query daily is safe. VibeJoy skips companies it already has and tells you how many were skipped (
skippedDuplicatesin the response). - Watch your monthly lead quota - check the usage page or run
vibejoy statsto see how many leads you've used this month. - Vary your queries - rotate job titles and locations week by week to reach different segments without exhausting one source.
- Use a "drip" campaign - keep one evergreen campaign active and keep adding enriched leads to it. Email 1 fires 24h after a lead is added, emails 2 and 3 follow automatically.
- Start small - 10-15 leads per day is a better starting point than 50. Quality over volume when warming up a sending domain.
SendGrid Setup
SendGrid delivers your campaign emails. You connect your own account so emails come from your domain with your sender reputation - not a shared VibeJoy pool. Setup takes about 5 minutes. You can connect multiple accounts (different domains or different clients) and choose which one each campaign sends from.
SendGrid free tier
The free plan includes 100 emails/day with no credit card required. That covers most early-stage outreach campaigns. Paid plans start at $19.95/month for higher volumes.
Step 1 - Create a SendGrid account
Go to sendgrid.com and sign up. No credit card needed for the free plan.
Step 2 - Verify your sender identity
This is the address that appears in the "From" field of every email. SendGrid will reject any send from an unverified address - this causes silent failures.
In SendGrid: go to Settings → Sender Authentication. Choose one of:
Single Sender Verification - quickest (2 min)
Click Verify a Single Sender, fill in your name, email, and company. SendGrid sends you a confirmation email. Click the link. Done. Best for getting started fast.
Domain Authentication - better deliverability (10 min)
Adds SPF + DKIM DNS records to your domain. Emails send from you@yourdomain.com with full authentication. Significantly improves inbox placement. Recommended if you have access to your DNS settings.
In SendGrid: Settings → Sender Authentication → Authenticate Your Domain. Follow the wizard - it generates the exact DNS records to add.
Important: If you skip sender verification, emails will silently fail to send. Always verify before connecting to VibeJoy.
Step 3 - Create a SendGrid API key
In SendGrid: Settings → API Keys → Create API Key.
Name it anything (e.g. "VibeJoy")
Choose Restricted Access
Enable Mail Send → Full Access
Enable Suppressions → Read Access (for managing unsubscribes)
Click Create & View and copy the key immediately - it starts with SG. and is shown only once
Step 4 - Connect to VibeJoy
Connect via dashboard, MCP, CLI, or REST API. All methods are equivalent.
Via Dashboard (easiest)
Go to Settings → Integrations → SendGrid. Fill in the form:
SG. key you just copiedjane@acme.comVia MCP (ask your agent)
"Connect SendGrid with key SG.your_key, sending from 'Jane at Acme' <jane@acme.com>"
connect_sendgrid→ validates key, saves account, email delivery readyVia CLI
vibejoy sendgrid connect \
--key "SG.your_sendgrid_api_key" \
--from-name "Jane at Acme" \
--from-email "jane@acme.com"
# Verify it worked
vibejoy status
# ✅ SendGrid email - connected, sending from jane@acme.comVia REST API
curl -X POST https://api.vibejoy.io/api/integrations/sendgrid/connect \
-H "Authorization: Bearer vj_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"apiKey": "SG.your_sendgrid_api_key",
"fromName": "Jane at Acme",
"fromEmail": "jane@acme.com"
}'Multiple SendGrid accounts (multi-domain or agency use)
You can connect multiple SendGrid accounts - useful if you have different sending domains (e.g. outreach@acme.com and team@acme.io) or if you manage campaigns for different clients.
Adding a second account (Dashboard)
Go to Settings → Integrations → SendGrid and click + Add account. Fill in the label, API key, from name, and from email for the new domain. Each account can have its own API key.
Adding accounts via REST API
# List all connected accounts
curl https://api.vibejoy.io/api/integrations/sendgrid/accounts \
-H "Authorization: Bearer vj_your_api_key"
# → { accounts: [{ _id, label, fromName, fromEmail, isDefault }, ...] }
# Add a second account (different domain)
curl -X POST https://api.vibejoy.io/api/integrations/sendgrid/accounts \
-H "Authorization: Bearer vj_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"label": "EU domain",
"apiKey": "SG.another_key",
"fromName": "Acme Europe",
"fromEmail": "hello@acme.eu"
}'
# Remove an account
curl -X DELETE https://api.vibejoy.io/api/integrations/sendgrid/accounts/ACCOUNT_ID \
-H "Authorization: Bearer vj_your_api_key"
# Set a different default sender
curl -X PATCH https://api.vibejoy.io/api/integrations/sendgrid/accounts/ACCOUNT_ID/default \
-H "Authorization: Bearer vj_your_api_key"Selecting sender per campaign
When creating a campaign in the dashboard, a Send from dropdown appears on the schedule step if you have more than one account - pick which one to use. Via REST API, pass sendgridAccountId in the campaign PATCH:
curl -X PATCH https://api.vibejoy.io/api/campaigns/CAMPAIGN_ID \
-H "Authorization: Bearer vj_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"sendgridAccountId": "ACCOUNT_ID"
}'
# Campaigns default to your primary/default account if not specifiedDeliverability tips
- Use domain authentication rather than single sender if possible - it significantly improves inbox placement
- Start with 20-50 emails per day and ramp up gradually to warm your domain
- Each sending domain (different
fromEmail) should be separately verified in SendGrid - Never send to unverified or purchased lists - this damages your sender reputation
- Add an unsubscribe link to stay CAN-SPAM and GDPR compliant - VibeJoy includes this automatically
Authentication
All API requests require an API key. Generate one from Settings → Developer.
Pass your key in the Authorization header on every request:
Authorization: Bearer vj_your_api_key_hereKeep your key secret
Never commit it to source control. Store it in an environment variable. If it's leaked, regenerate immediately from Settings.
Limits by plan
| Plan | API calls/month | MCP | CLI | Webhooks | SSE streaming |
|---|---|---|---|---|---|
| Free | 100 | - | - | 0 | - |
| Pay as you go | 50,000 | ✓ | ✓ | 10 | ✓ |
| Growth | 50,000 | ✓ | ✓ | 10 | ✓ |
MCP Server
The VibeJoy MCP server exposes your full lead engine as tools that any MCP-compatible AI can call - Claude Desktop, Cursor Agent, or any app that speaks the Model Context Protocol.
Setup
Cursor (global config - recommended)
Find your path in 2 seconds
Open File Explorer, navigate to the vibejoy\mcp folder, hold Shift and right-click index.js, then select Copy as path. Paste it into the args field - then replace every single \ with \\.
Example: if your Windows username is Donna the path is:C:\\Users\\Donna\\Desktop\\vibejoy\\mcp\\index.js
{
"mcpServers": {
"vibejoy": {
"command": "node",
"args": ["C:\\Users\\Donna\\Desktop\\vibejoy\\mcp\\index.js"],
"env": {
"VIBEJOY_API_KEY": "vj_your_api_key_here",
"VIBEJOY_API_URL": "https://api.vibejoy.io"
}
}
}
}After saving, press Ctrl+Shift+P → Reload Window in Cursor. The VibeJoy tools will appear in Agent mode.
Claude Desktop (macOS)
{
"mcpServers": {
"vibejoy": {
"command": "node",
"args": ["/Users/yourname/vibejoy/mcp/index.js"],
"env": {
"VIBEJOY_API_KEY": "vj_your_api_key_here",
"VIBEJOY_API_URL": "https://api.vibejoy.io"
}
}
}
}Claude Desktop (Windows)
File location: %APPDATA%\Claude\claude_desktop_config.json - paste this in your browser address bar to open the folder.
{
"mcpServers": {
"vibejoy": {
"command": "node",
"args": ["C:\\Users\\Donna\\Desktop\\vibejoy\\mcp\\index.js"],
"env": {
"VIBEJOY_API_KEY": "vj_your_api_key_here",
"VIBEJOY_API_URL": "https://api.vibejoy.io"
}
}
}
}Restart Claude Desktop after saving. VibeJoy tools appear automatically in Claude's tool list.
Available tools
run_pipelineTHE SIMPLEST TOOL. One call that does everything: finds in-market buyers, enriches their emails, writes AI copy, and activates the campaign. Use this instead of calling find_leads + enrich_contacts + create_campaign separately. Requires brand context and SendGrid connected.
StartertargetAudience?: string - who to target (defaults to saved ICP) sources?: string[] - ["job_board","funding","linkedin_post"] (default: job_board + funding) leadLimit?: number - max leads per source query (default 30) titles?: string[] - contact titles to enrich (default: CEO, Founder, CTO, VP Sales) email1At?: string - ISO date for first email (default: tomorrow) activate?: boolean - activate immediately (default: true) dryRun?: boolean - plan only: nothing saved or sent signalQuery?: string - boolean query e.g. hiring AND funding
setup_brand_from_urlPaste any URL - website, LinkedIn profile, Product Hunt, competitor site, or job listing. AI scrapes the page and auto-fills the entire brand profile. Fastest setup option.
Allurl: string - any URL (with or without https://)
setup_brand_from_descriptionNo website? Answer up to 3 plain-language questions and AI builds the brand profile. All fields optional - answer what you know.
Allwhat?: string - what you sell who?: string - who you sell to outcome?: string - the outcome customers get
check_integrationsCheck your setup status: whether brand context is configured, SendGrid is connected, and LinkedIn is linked. Run this first to confirm everything is ready before creating a campaign.
All(none)
setup_brandSet your brand context - business name, offer, ICP, tone, and campaign goal. The AI uses this as its voice for every campaign. Must be completed before campaign generation.
AllbusinessName: string offer: string icp: string (ideal customer profile) tone: "Bold" | "Warm" | "Direct" | "Professional" | "Conversational" goal: "Book demos" | "Get signups" | "Build awareness" | "Book a call"
connect_sendgridConnect a SendGrid account for email delivery. Required before campaigns can send emails. The sender email must be verified in your SendGrid account. You can call this tool multiple times to add multiple accounts (different domains or clients) - they all appear in Settings and can be selected per campaign.
AllapiKey: string (starts with SG.) fromName: string (display name in From field) fromEmail: string (verified sender address) label?: string (e.g. "Sales domain" or "Client A" - helps identify the account)
find_leadsFind in-market buyers by intent signal. Pass multiple queries in one call - either comma-separated in query ("SDR, RevOps Manager") or as a queries array - to search multiple job titles or keywords in parallel and get more results faster.
Startersource: "local" | "job_board" | "reddit" | "funding" | "linkedin_post" query: string - primary search query (for job_board, comma-separate multiple titles) queries?: string[] - optional extra queries to run in parallel for the same source location?: string - city/country (job_board and local) limit?: number - max per query, default 50
find_leads_bulkFind leads across multiple sources AND multiple queries in ONE parallel call. Use this instead of calling find_leads multiple times. Searches all specified sources simultaneously and returns combined, deduplicated results with a breakdown. The fastest way to build a large lead list.
Startersearches: Array<{ source, queries: string[], location? }> - each entry is a source + array of queries to run in parallel
limit?: number - max leads per individual query, default 50enrich_contactsEnrich leads with verified emails, job titles, LinkedIn profiles, and phone numbers. Returns counts of enriched leads and any new contacts discovered at the same company.
StarterleadIds: string[] - array of _id values returned by find_leads
create_campaignGenerate an AI-written outreach sequence - 3 emails that move from door-opener to the yes-or-no, plus a LinkedIn post and Reddit content. The AI writes copy that makes prospects feel understood, which is what produces replies and books demo calls. Optionally activate immediately.
StartertargetAudience: string - who the campaign targets (e.g. "fintech founders who just raised Series A") campaignGoal: string - the outcome you want (e.g. "book a 15-minute demo call") extraContext?: string - extra context to sharpen the copy leadIds?: string[] - add specific leads as recipients allEnrichedLeads?: boolean - add every enriched lead as a recipient email1At?: string - ISO date for first email (defaults to tomorrow) activate?: boolean - true to activate immediately (default false)
update_campaignKeep building on an existing campaign - add more leads, rename it, reschedule send dates, or activate. Works on any non-completed campaign. Use this instead of create_campaign when you already have a campaign you want to grow.
StartercampaignId: string - the campaign ID (use get_campaigns to find it) name?: string - rename the campaign leadIds?: string[] - add these lead IDs as new recipients (duplicates ignored) allEnrichedLeads?: boolean - add every enriched lead as a recipient (duplicates ignored) email1At?: string - reschedule when email 1 sends activate?: boolean - activate after updating pause?: boolean - pause a live campaign to stop further sends
get_campaignsList all your campaigns with status, email sequences, reply rate, and delivery stats. No parameters required.
All(none)
get_dashboard_statsFetch dashboard analytics: total leads, enriched leads, AI openers generated, opener coverage %, openers by signal source, campaigns, emails sent, LinkedIn sent. No parameters required.
All(none)
get_brand_contextRead your current brand context: business name, offer, ideal customer profile, tone, and campaign goal. Used as the AI's voice for all campaign generation.
All(none)
signal_queryBoolean signal expression over hiring, funding, linkedin, pain, maps, keyword:"…". Prefer over find_leads when you need AND/OR intersections.
Starterquery: string maxResults?: number dryRun?: boolean location?: string
triage_repliesList, ingest, approve, or dismiss inbound replies. AI classifies and drafts a response.
Starteraction: "list" | "ingest" | "approve" | "dismiss" body?, recipientEmail? (ingest) replyId?, approvedReply?, send? (approve)
get_costsYour spend and credit wallet — what you pay by operation. Includes Free / $25 PAYG / Growth $249 pricing.
Allmonth?: string (YYYY-MM)
get_pricingPublished plans and per-action rates (lead $0.05, enrich $0.12, etc.). Use for “how much does it cost?”
All(none)
buy_creditsStart PayPal checkout for a $25 credit pack. Returns a URL the user must open to pay.
All(none)
apply_configApply vibejoy.yaml (config-as-code). Pass YAML string; dryRun defaults true for safety.
Paidyaml: string dryRun?: boolean
Example agent conversation
This is a real transcript of what happens when you talk to Cursor Agent with VibeJoy connected:
"What's my brand context?"
get_brand_context→ shows your business name, ICP, offer, tone"Find 100 companies: hiring SDRs or RevOps managers in the UK, AND that just raised Series A - do it in one search"
find_leads_bulk→ runs all searches in parallel, returns 100+ deduplicated leads in one shot"Enrich them - I need decision-maker emails"
enrich_contacts→ verified emails + titles via B2B database"Write a 3-email campaign targeting their new CTO/VP Eng. Goal is a product demo."
create_campaign→ AI writes in your brand voice"Looks good. Activate it."
create_campaign (activate: true)→ scheduled, sends automatically at the configured times"How many AI openers and emails have we sent?"
get_dashboard_stats→ leads, openersGenerated, openersCoverage, openersBySource, emails sent"Show my credits and how pricing works"
get_costsget_pricing→ wallet + Free / $25 PAYG / Growth $249 + rates"I'm low on credits — buy me another $25 pack"
buy_credits→ PayPal checkout URL to open"Dry-run: hiring AND funding, then show my cost ledger"
signal_queryget_costs→ preview count + your $ spend + credits left"List replies waiting for triage"
triage_replies→ classified inbox with suggested repliesContinuing an existing campaign
You never have to start over. Use update_campaign to keep adding leads to a campaign you already created. The agent automatically calls get_campaigns to look up the campaign ID - just refer to it by name and the agent does the rest. You can also find campaign IDs in the Campaigns page on your dashboard.
"Find 15 more funded startups in fintech"
find_leads (source: funding)→ 15 new leads added to your database"Enrich them - senior contacts only"
enrich_contacts (seniority: c_suite, founder)→ 12 contacts enriched"Add them to my July campaign"
get_campaignsupdate_campaign→ agent looks up the campaign by name, adds 12 recipients, total now 59"Reschedule that campaign - send the first email next Monday at 9am"
update_campaign (email1At: ...)→ email 1 rescheduled, follow-ups auto-adjusted"Actually, pause that campaign - I need to review the copy first"
update_campaign (pause: true)→ campaign paused, no further sendsEditing send dates from the dashboard
Open any campaign → Delivery timeline → Edit dates. You can update the LinkedIn post date and Email 1 date at any time, even after a campaign has been activated. Emails 2 and 3 are auto-recalculated (+3 and +7 days from Email 1). Hit Save and the cron scheduler picks up the new times on its next tick (within 60 seconds).
3 ways to find a campaign ID
- Ask the agent - just say "my July campaign" and it calls
get_campaignsautomatically to look it up - Dashboard - open the Campaigns page, click any campaign, and the ID is shown in the URL and campaign detail
- CLI - run
vibejoy campaign listto print all campaigns with their IDs
AI Agents
Beyond MCP, you can use VibeJoy's REST API directly from any AI agent framework - LangChain, LlamaIndex, OpenAI function calling, CrewAI, or your own custom agent loop.
Cursor Agent mode
With the MCP configured, Cursor's Agent mode can run your entire outbound pipeline autonomously. Just describe what you want:
Cursor Agent prompt
"Every Monday morning, find 30 companies that are actively hiring for the role my SaaS automates. Enrich them. Check if we already have a campaign targeting this audience - if not, write a new one and activate it. Then update a Notion table with this week's campaign stats."
→ Agent calls: get_campaigns → find_leads → enrich_contacts → create_campaign → (Notion MCP)
OpenAI function calling
import OpenAI from 'openai';
import fetch from 'node-fetch';
const openai = new OpenAI();
const VJ_KEY = process.env.VIBEJOY_API_KEY;
const VJ_BASE = 'https://api.vibejoy.io';
const h = { Authorization: `Bearer ${VJ_KEY}`, 'Content-Type': 'application/json' };
const tools = [
{
type: 'function',
function: {
name: 'find_leads',
description: 'Find leads by intent signal',
parameters: {
type: 'object',
properties: {
source: { type: 'string', enum: ['job_board','reddit','funding','linkedin_post','local'] },
query: { type: 'string' },
location: { type: 'string' },
limit: { type: 'number' }
},
required: ['source','query']
}
}
},
{
type: 'function',
function: {
name: 'enrich_contacts',
description: 'Enrich leads with verified emails, job titles, and LinkedIn. Filter by titles or seniority.',
parameters: {
type: 'object',
properties: {
leadIds: { type: 'array', items: { type: 'string' } },
titles: { type: 'array', items: { type: 'string' }, description: 'Job title keywords e.g. ["CEO","Founder"]' },
seniority: { type: 'array', items: { type: 'string' }, description: 'Seniority levels e.g. ["c_suite","vp"]' }
},
required: ['leadIds']
}
}
}
];
async function callVibeJoy(name, args) {
if (name === 'find_leads') {
const res = await fetch(`${VJ_BASE}/api/leads/intent/jobs`, {
method: 'POST', headers: h,
body: JSON.stringify({ jobTitles: [args.query], location: args.location, maxResults: args.limit || 25 })
});
return res.json();
}
if (name === 'enrich_contacts') {
const res = await fetch(`${VJ_BASE}/api/leads/enrich`, {
method: 'POST', headers: h,
body: JSON.stringify({ leadIds: args.leadIds, titles: args.titles || [], seniority: args.seniority || [] })
});
return res.json();
}
}
// Agent loop
let messages = [{ role: 'user', content: 'Find 10 companies hiring sales ops in London, then enrich them.' }];
while (true) {
const res = await openai.chat.completions.create({ model: 'gpt-4o', tools, messages });
const msg = res.choices[0].message;
messages.push(msg);
if (res.choices[0].finish_reason === 'stop') {
console.log(msg.content);
break;
}
for (const call of msg.tool_calls || []) {
const result = await callVibeJoy(call.function.name, JSON.parse(call.function.arguments));
messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) });
}
}LangChain agent
from langchain.agents import create_openai_functions_agent, AgentExecutor
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
import requests, os
BASE = "https://api.vibejoy.io"
H = {"Authorization": f"Bearer {os.environ['VIBEJOY_API_KEY']}", "Content-Type": "application/json"}
@tool
def find_leads(source: str, query: str, location: str = "", limit: int = 25) -> dict:
"""Find leads by intent signal. source: job_board | reddit | funding | linkedin_post | local"""
r = requests.post(f"{BASE}/api/leads/intent/jobs", headers=H,
json={"jobTitles": [query], "location": location, "maxResults": limit})
return r.json()
@tool
def enrich_contacts(lead_ids: list[str]) -> dict:
"""Enrich leads with verified email, title, and LinkedIn"""
r = requests.post(f"{BASE}/api/leads/enrich", headers=H, json={"leadIds": lead_ids})
return r.json()
@tool
def get_dashboard_stats() -> dict:
"""Get analytics: leads, AI openers, coverage %, openers by signal, emails sent"""
return requests.get(f"{BASE}/api/dashboard/stats", headers=H).json()
llm = ChatOpenAI(model="gpt-4o")
agent = create_openai_functions_agent(llm, [find_leads, enrich_contacts, get_dashboard_stats])
executor = AgentExecutor(agent=agent, tools=[find_leads, enrich_contacts, get_dashboard_stats], verbose=True)
executor.invoke({"input": "Find 20 companies hiring RevOps managers in the UK and enrich them"})Real-time enrichment progress (SSE)
Use the streaming endpoint to get live progress events during enrichment - useful in agent UIs, dashboards, or long-running scripts.
// Node.js: stream enrichment progress
const response = await fetch('https://api.vibejoy.io/api/leads/enrich-stream', {
method: 'POST',
headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
body: JSON.stringify({ leadIds: [...] })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value).split('\n')) {
if (!line.startsWith('data: ')) continue;
const evt = JSON.parse(line.slice(6));
if (evt.type === 'progress') {
console.log(`[${evt.enriched + evt.failed}/${evt.total}] ${evt.leadName}`);
}
if (evt.type === 'done') {
console.log(`Done: ${evt.enriched} enriched, ${evt.newLeadsCreated} new contacts, ${evt.failed} not found`);
}
}
}CLI
The VibeJoy CLI lets you run lead discovery from your terminal, pipe output into other tools, and integrate VibeJoy into scripts and CI workflows.
Install & authenticate
npm install -g vibejoy
vibejoy auth --key vj_your_api_key_here
vibejoy auth --show # verify stored keyCommands
vibejoy findFind intent-signal leads. Flags: --signal (hiring|reddit|funding|linkedin|local), --query, --location, --limit, --output (csv path)
Startervibejoy enrichEnrich leads. --unenriched enriches every lead not yet enriched. --titles "CEO,CTO" targets specific job title keywords. --seniority "c_suite,founder" filters by seniority level. Also accepts --file (csv), --ids, --output (csv).
Startervibejoy campaign createCreate an AI campaign from a leads CSV. Flags: --leads, --name, --audience, --goal, --activate
Startervibejoy campaign updateContinue working on an existing draft - add leads, rename, reschedule, or activate. Flags: --id (required), --ids (comma-separated lead IDs), --all-enriched (add every enriched lead), --name (rename), --send-date (reschedule email 1), --activate
Startervibejoy campaign listList all campaigns with status, sends, and reply rate
Allvibejoy campaign activateActivate a draft campaign. Flags: --id
Startervibejoy campaign pausePause an active campaign to stop further sends. Flags: --id
Startervibejoy statsPrint analytics: leads, enriched, AI openers (+ coverage %), campaigns, emails sent
Allvibejoy runFull pipeline. Flags: --audience, --sources, --limit, --titles, --send-at, --draft, --dry-run, --query (signal expression)
Startervibejoy applyConfig-as-code. Flags: --file vibejoy.yaml, --dry-run
Startervibejoy queryBoolean signal query. Args: <expr>. Flags: --limit, --dry-run, --location
Startervibejoy replies listList triage inbox. Flags: --status
Startervibejoy replies ingestIngest + AI triage. Flags: --email, --body, --subject
Startervibejoy replies approveApprove suggested reply. Flags: --send, --text
Startervibejoy costsYour spend + credit wallet + pricing summary. Flags: --month YYYY-MM
Allvibejoy pricingPlans (Free / $25 PAYG / Growth $249) and per-action rates
Allvibejoy credits balanceShow credit wallet balance
Allvibejoy credits buyBuy a $25 credit pack (PayPal checkout URL)
Allvibejoy leads listList saved leads. Flags: --enriched (true|false), --limit, --output
Allvibejoy authManage your API key. Flags: --key, --show, --revoke
AllFull pipeline examples
# --- Hiring signal pipeline ---
# Find companies hiring for the role your SaaS automates
vibejoy find --signal hiring --query "Revenue Operations Manager" --location "United Kingdom" --limit 50 --output /tmp/leads.csv
# Enrich all leads not yet enriched (simplest - no file needed)
vibejoy enrich --unenriched
# Or enrich a specific CSV file
# vibejoy enrich --file /tmp/leads.csv --output /tmp/enriched.csv
# Generate and activate campaign with all enriched leads
vibejoy campaign create \
--audience "RevOps managers scaling their CRM stack" \
--goal "book a 20-minute product demo" \
--all-leads \
--email1-date 2026-07-15 \
--activate
# --- Continue an existing campaign (most common use case) ---
# First, find your campaign ID - 3 ways:
# 1. Dashboard → Campaigns page (shown in URL and campaign detail)
# 2. vibejoy campaign list (prints all campaigns + IDs)
# 3. Ask your AI agent - it calls get_campaigns automatically
vibejoy campaign list
# Example output:
# ─────────────────────────────────────────────────────
# Name Status Recipients Created
# ─────────────────────────────────────────────────────
# August hiring push draft 23 2026-07-20
# July RevOps campaign scheduled 47 2026-07-15
# ─────────────────────────────────────────────────────
# Next day: find more leads and add them to the SAME campaign
vibejoy find --signal hiring --query "Revenue Operations Manager" --location "London" --limit 20
vibejoy enrich --unenriched
# Add new enriched leads to the existing campaign (no new campaign needed)
vibejoy campaign update --id CAMPAIGN_ID --all-enriched
# Or add specific leads by ID
vibejoy campaign update --id CAMPAIGN_ID --ids lead_id_1,lead_id_2,lead_id_3
# Rename and reschedule at the same time
vibejoy campaign update --id CAMPAIGN_ID --name "RevOps UK - Wave 2" --send-date 2026-08-01T09:00:00Z
# Update and activate in one step
vibejoy campaign update --id CAMPAIGN_ID --all-enriched --activate
# Pause a live campaign (stop further sends)
vibejoy campaign pause --id CAMPAIGN_ID
# --- Pain signal pipeline ---
# Find people on Reddit expressing your customer's pain
vibejoy find --signal reddit --query "cold email not getting replies" --limit 30 --output /tmp/pain.csv
# Enrich all unenriched leads
vibejoy enrich --unenriched
# Create and activate campaign
vibejoy campaign create \
--audience "founders frustrated with cold email deliverability" \
--goal "start a conversation about outbound consistency" \
--all-leads \
--email1-date 2026-07-15 \
--activate
# --- Check results ---
vibejoy stats
vibejoy campaign listCron / CI integration
# .github/workflows/weekly-leads.yml
name: Weekly lead run
on:
schedule:
- cron: '0 8 * * 1' # Monday 8am UTC
jobs:
find-and-enrich:
runs-on: ubuntu-latest
steps:
- run: npm install -g vibejoy
- run: vibejoy auth --key ${VIBEJOY_API_KEY}
env:
VIBEJOY_API_KEY: ${{ secrets.VIBEJOY_API_KEY }}
- run: |
EMAIL_DATE=$(date -u -d "+2 days" +%Y-%m-%d 2>/dev/null || date -u -v+2d +%Y-%m-%d)
vibejoy find --signal hiring --query "SDR" --limit 50 --output leads.csv
vibejoy enrich --unenriched
vibejoy campaign create \
--audience "Sales managers scaling outbound without growing headcount" \
--goal "start a conversation about pipeline efficiency" \
--all-leads \
--email1-date "$EMAIL_DATE" \
--activate
- run: vibejoy statsREST API
Base URL: https://api.vibejoy.io - all endpoints accept and return JSON. Authenticate with Authorization: Bearer vj_...
Setup (run once)
/api/brandSet brand context - required before campaign generation
{ "businessName": "...", "offer": "...", "icp": "...", "tone": "Direct", "goal": "Book demos" }
/api/brandRead current brand context
/api/integrations/sendgrid/connectConnect primary SendGrid account (legacy - single account)
{ "apiKey": "SG.xxx", "fromName": "Jane at Acme", "fromEmail": "jane@acme.com" }
/api/integrations/sendgrid/disconnectDisconnect primary SendGrid account
/api/integrations/sendgrid/accountsList all connected SendGrid accounts (primary + additional)
/api/integrations/sendgrid/accountsAdd an additional SendGrid account (different domain or client)
{ "apiKey": "SG.xxx", "fromName": "...", "fromEmail": "...", "label": "Sales domain" }
/api/integrations/sendgrid/accounts/:idRemove a SendGrid account by its ID
/api/integrations/sendgrid/accounts/:id/defaultSet a SendGrid account as the default sender
/api/integrations/zernio/connectConnect LinkedIn via your own Zernio account - requires your API key (sk_...) from Settings → API Keys, and Account ID from the Connections page, both at zernio.io
{ "apiKey": "sk_your_zernio_api_key", "accountId": "6a58876227846c0757346174" }
/api/integrations/zernio/disconnectDisconnect Zernio / LinkedIn scheduling
Lead discovery
/api/leads/scrapeLocal business search - find businesses by keyword and location (Google Maps via Apify)
{ "keyword": "software company", "location": "Austin", "maxResults": 50 }
/api/leads/intent/jobsHiring signals - companies actively recruiting. Paid plans auto-generate AI openers. Pass multiple job titles for broader results.
{ "jobTitles": ["Software Engineer", "Engineering Manager"], "location": "London", "maxResults": 50 }
/api/leads/intent/redditPain signals - Reddit/Quora posts expressing your customer's problem. Paid plans auto-generate AI openers.
{ "keywords": "CI pipeline failing", "subreddits": ["devops"], "maxResults": 50 }
/api/leads/intent/fundingFunding signals - recently funded startups (SEC EDGAR + tech news). Growth+ auto-generates AI openers.
{ "stages": ["seed", "series-a"], "maxResults": 50 }
/api/leads/intent/linkedin-postsLinkedIn demand signals - posts about your category. Growth+ auto-generates AI openers.
{ "keywords": "looking for developer tools", "maxResults": 50 }
/api/leads/auto-findICP-driven multi-source find: intent signals + Maps + Google Search company discovery (companies with websites for enrich-by-domain). Optional AI openers on intent leads.
{ "targetCount": 500, "sources": ["hiring", "pain", "linkedin", "funding", "bulk"] }
/api/leads/search-peopleDirect people search by title, company, industry, and location
{ "jobTitle": "CTO", "location": "London", "companySize": "11-50" }
/api/leads/fill-websitesBatch-resolve missing company websites (Clearbit + Google Search) so contacts can be enriched by domain
{ }
/api/leads/signal-queryBoolean signal query (hiring AND funding, keyword:"devops", etc.)
{ "query": "hiring AND funding", "maxResults": 50, "dryRun": false }
/api/pipeline/runFull pipeline with dryRun + signalQuery support
{ "sources": ["job_board"], "leadLimit": 30, "dryRun": false }
/api/config/applyApply vibejoy.yaml config-as-code
{ "yaml": "version: 1\\n...", "dryRun": true }
/api/costsYour spend + credit wallet + pricing summary (?month=YYYY-MM)
/api/costs/pricingPlans: Free, Pay as you go $25, Growth $249/mo — plus per-action rates
/api/repliesReply triage inbox
/api/repliesIngest inbound reply for AI triage
{ "recipientEmail": "a@b.com", "body": "..." }
/api/integrations/mailMail provider status (sendgrid|ses|postal)
Enrichment
/api/leads/enrichEnrich multiple leads in parallel (up to 6 concurrent). Returns when all complete.
{ "leadIds": ["id1", "id2"] }
/api/leads/enrich-streamSame as /enrich but streams Server-Sent Events. Each event: { type, enriched, failed, total, leadName }.
{ "leadIds": ["id1", "id2"] }
/api/leads/enrich-singleEnrich one lead, returns updated doc immediately.
{ "leadId": "id1" }
Leads management
/api/leadsPaginated list. Query params: page, limit, status, enriched (true|false)
/api/leads/:idUpdate editable fields on a lead
{ "website": "...", "email": "...", "contactName": "..." }
/api/leads/:idDelete a single lead
Campaigns
/api/campaigns/generateGenerate AI-written email sequence in your brand voice
{ "targetAudience": "...", "campaignGoal": "...", "extraContext": "..." }
/api/campaignsList all campaigns with status, email sequences, and delivery stats
/api/campaigns/:idSingle campaign - full emails, subject lines, and per-recipient delivery status
/api/campaigns/:id/activateActivate a draft - schedules all emails to send automatically at the configured times
/api/campaigns/:id/pausePause an active campaign (stops further sends)
/api/campaigns/:idDelete a campaign
Account & stats
/api/dashboard/statsAnalytics: totalLeads, enrichedLeads, openersGenerated, openersCoverage, openersBySource, campaigns, emails sent, LinkedIn sent
/api/dashboard/usageMonthly usage + all-time totals including AI openers. Query: ?month=YYYY-MM. Returns usage.openersGenerated, openersBySource, openersCoverage, limits.aiOpeners
/api/auth/meCurrent user: plan, brand context, integration status, API key
/api/auth/api-keyGenerate or regenerate your API key. Returns new vj_ key.
/api/auth/api-keyRevoke your API key immediately
/api/brandRead your brand context (name, offer, ICP, tone, goal)
Webhooks
VibeJoy can POST to your endpoint whenever a lead is found, enriched, or a campaign milestone is hit. Configure webhook URLs in Settings → Webhooks.
Available events
lead.foundA new intent-signal lead was discovered and saved
Starterlead.enrichedContact enrichment completed for a lead (email + title added)
Startercampaign.createdA new campaign was generated (emails + LinkedIn post ready)
Startercampaign.activatedA campaign was activated and emails are scheduled to send automatically
Startercampaign.email_sentAn individual email was delivered to a recipient
Growthcampaign.reply_receivedA reply to a campaign email was detected
GrowthPayload shape
{
"event": "lead.enriched",
"timestamp": "2026-07-08T09:00:00Z",
"data": {
"_id": "lead_abc123",
"businessName": "Acme Corp",
"contactName": "Jane Smith",
"contactTitle": "Head of Sales",
"email": "jane@acme.com",
"linkedinUrl": "https://linkedin.com/in/janesmith",
"website": "https://acme.com",
"intentSource": "job_board",
"intentSignal": "Hiring: Head of Engineering at Acme Corp",
"personalizedOpener": "Hiring a Head of Engineering usually means the old deploy process just became a board-level problem."
}
}Verifying signatures
Each request includes X-VibeJoy-Signature - an HMAC-SHA256 of the raw body using your webhook secret.
import crypto from 'crypto';
export function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Express example
app.post('/webhook/vibejoy', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-vibejoy-signature'];
if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = JSON.parse(req.body);
console.log(event, data);
res.sendStatus(200);
});n8n & Make.com
Wire VibeJoy intent signals into your existing automation workflows using the HTTP Request node. No custom integration required.
Example n8n workflow
n8n HTTP Request node - find leads
POSThttps://api.vibejoy.io/api/leads/intent/jobsHeader Auth → Authorization: Bearer vj_...{ "jobTitles": ["RevOps Manager"], "location": "London", "maxResults": 25 }n8n HTTP Request node - create campaign
POSThttps://api.vibejoy.io/api/campaigns/generateHeader Auth → Authorization: Bearer vj_...{ "targetAudience": "RevOps managers", "campaignGoal": "book a demo" }Make.com (Integromat)
Use the HTTP module in Make.com with the same base URL and Bearer token. Map leads[] from the find response into any downstream module.
# Make.com HTTP module: find leads
URL: https://api.vibejoy.io/api/leads/intent/jobs
Method: POST
Headers: Authorization = Bearer vj_your_key
Body type: Raw (JSON)
Body: {"jobTitles": ["SDR"], "location": "London", "maxResults": 25}
# Map leads[].businessName, leads[].website, leads[]._id
# to your CRM, Airtable, or Notion moduleZapier (via webhook)
Use Zapier's Webhooks by Zapier action with a POST request to any VibeJoy endpoint. Chain: Zap trigger → POST to /api/leads/intent/jobs → loop results → POST to your CRM.
Questions or issues?
Raise issues or ask questions via the dashboard. Docs are updated continuously.
Social Scheduling (LinkedIn + Reddit)
Every campaign VibeJoy generates includes ready-to-schedule social content. The AI writes copy that makes your ICP feel understood - not pitched - so posts generate comments and DMs from potential buyers. You review it, then schedule it.
What gets generated for every campaign
LinkedIn post
150-200 words, brand voice, observation-led - no hard sell, max 3 hashtags
LinkedIn DM sequence
Connection request note + 3 follow-up DMs spaced over 2 weeks
Reddit original post
Value-first post for your ICP subreddit - adds insight, does not pitch
Reddit reply templates
2 ready-to-use replies for relevant threads in your subreddit
Part A - Get your social content
After creating a campaign, retrieve the social content via your preferred interface:
Dashboard
Go to Campaigns → open your campaign → Social tab. You'll see the LinkedIn post, DM sequence, Reddit post, and reply templates - each with a copy button.
MCP (ask your agent)
"Show me the social content for my latest campaign - the LinkedIn post, DMs, and Reddit copy"
get_campaignsget_campaign_social→ returns all social copy, ready to review and scheduleCLI
REST API
Part B - Connect Zernio for LinkedIn scheduling
Zernio is the service that handles LinkedIn post and DM scheduling from VibeJoy. You connect it once and it stays connected.
Zernio is optional. If you prefer to copy and paste content manually into LinkedIn or another scheduler, you can skip this. Social content is always available in the dashboard Social tab.
Create a Zernio account
Go to zernio.io and sign up for a free account.
Connect your LinkedIn profile in Zernio
Once logged in to Zernio:
Create your Zernio API key
VibeJoy uses your own Zernio API key to schedule on your behalf - so billing, rate limits, and LinkedIn authentication all stay within your account.
sk_and is shown only onceImportant: Copy it immediately - Zernio only shows the full key once. If you lose it, delete it and create a new one.
Copy your Zernio Account ID
This tells VibeJoy which LinkedIn profile to post from. Here is exactly where to find it:
Go to the Connections page in Zernio
Look for the long alphanumeric string next to your LinkedIn profile name - it looks like:
6a58876227846c0757346174Click the copy icon next to it to copy the full string - that is your Account ID
Tip: The Zernio dashboard URL often contains your Account ID - e.g.
zernio.io/dashboard/6a58876227846c0757346174. The ID in the URL is your Account ID.Paste both into VibeJoy Settings
Go to Settings → Integrations → LinkedIn via Zernio:
sk_...) into the first fieldDone. VibeJoy now schedules LinkedIn posts through your own Zernio account. You only do this once.
Part C - Schedule your content
LinkedIn post
Copy the LinkedIn post text from the campaign Social tab. In Zernio, create a new Post, paste the copy, and set a publish time.
Best times for B2B LinkedIn posts
Tuesday, Wednesday, Thursday - 8am to 10am your audience's timezone. Avoid weekends and Monday mornings.
LinkedIn DM sequence
The DM sequence has four parts. Send them manually or through a LinkedIn automation tool:
Connection request
Send with the connection invite - Max 280 characters - VibeJoy keeps it short and personal
Message 1 - value drop
2-3 days after they accept - Share a useful observation, no ask yet
Message 2 - soft ask
5-7 days after Message 1 - Gentle question or invitation to chat
Message 3 - graceful exit
10-14 days after Message 2 (no reply) - Close the loop warmly, leave the door open
For fully automated DM sequences, tools like Expandi, Waalaxy, or Dripify can send the full sequence automatically. Copy the four messages from VibeJoy into their sequence builder.
Reddit
Original post
The AI suggests a subreddit based on your ICP. Go to that subreddit, verify the rules allow discussion posts (most do), and submit. The post adds value first - it does not pitch your product directly.
Reply templates
Search the subreddit for threads matching the scenario in each reply template (e.g. "struggling with SDR ramp time"). When you find one, paste the reply and edit it to match the specific thread context. Never paste unedited - Reddit readers spot templates immediately.
Reddit self-promotion rules
Most subreddits allow 1 self-promotional post for every 9 community contributions (the 9:1 rule). VibeJoy's posts are written to be value-first and community-friendly, but always check the subreddit sidebar for their specific rules before posting.
End-to-end via MCP - one conversation
Once Zernio is connected, a single agent conversation handles everything from lead discovery to content retrieval:
"Find 20 companies hiring SDRs in London, enrich their contacts targeting VP Sales or Head of Sales, write a campaign, and activate it"
find_leadsenrich_contactscreate_campaign→ leads found, enriched, campaign written and activated"Show me the social content for that campaign so I can schedule it"
get_campaign_social→ LinkedIn post + DM sequence + Reddit post + reply templates"What are my dashboard stats - openers and emails sent?"
get_dashboard_stats→ leads, openersGenerated, openersCoverage, emails sent