Production architecture for cold outreach automation with Instantly. Covers campaign orchestration, lead management pipelines, multi-account warmup strategies, and reply handling with CRM synchronization.
set -euo pipefail
┌──────────────────────────────────────────────────────┐
│ Lead Sources │
│ Clay │ Apollo │ CSV Import │ CRM Export │ API │
└──────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Instantly Platform │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Email │ │ Campaigns │ │ Lead │ │
│ │ Accounts │ │ & Sequences │ │ Lists │ │
│ │ (warmup) │ │ (A/B test) │ │ (import) │ │
│ └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Sending Engine │ │
│ │ Throttling │ Rotation │ Tracking │ Warmup │ │
│ └──────────────────────┬───────────────────────┘ │
└─────────────────────────┼───────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────────┐ ┌────────┐ ┌──────────────┐
│ Replies │ │ Opens │ │ Bounces │
│ (webhook) │ │ Clicks │ │ Unsubscribes │
└──────┬───────┘ └────────┘ └──────────────┘
│
▼
┌──────────────┐
│ CRM Sync │
│ (HubSpot/SF) │
└──────────────┘
const INSTANTLY_API = 'https://api.instantly.ai/api/v1';
async function instantlyRequest(endpoint: string, options?: RequestInit) {
const url = new URL(`${INSTANTLY_API}${endpoint}`);
url.searchParams.set('api_key', process.env.INSTANTLY_API_KEY!);
const response = await fetch(url.toString(), {
...options,
headers: { 'Content-Type': 'application/json', ...options?.headers },
});
if (!response.ok) throw new Error(`Instantly API error: ${response.status}`);
return response.json();
}
async function listCampaigns() {
return instantlyRequest('/campaign/list');
}
async function getCampaignStatus(campaignId: string) {
return instantlyRequest(`/campaign/get?campaign_id=${campaignId}`);
}
interface Lead {
email: string;
first_name?: string;
last_name?: string;
company_name?: string;
personalization?: string;
custom_variables?: Record<string, string>;
}
async function uploadLeads(campaignId: string, leads: Lead[]) {
const BATCH_SIZE = 100;
let totalUploaded = 0;
for (let i = 0; i < leads.length; i += BATCH_SIZE) {
const batch = leads.slice(i, i + BATCH_SIZE);
await instantlyRequest('/lead/add', {
method: 'POST',
body: JSON.stringify({
campaign_id: campaignId,
skip_if_in_workspace: true,
leads: batch,
}),
});
totalUploaded += batch.length;
await new Promise(r => setTimeout(r, 200)); // Rate limit # HTTP 200 OK
}
return { uploaded: totalUploaded };
}
// Campaign sequence design for cold outreach
const CAMPAIGN_SEQUENCE = {
name: 'Q1 Outreach - {ICP Segment}',
sequences: [
{
step: 1,
delay: 0,
subject: 'Quick question about {{company_name}}',
body: 'Hi {{first_name}},\n\n{{personalization}}\n\n...',
variant: 'A',
},
{
step: 1,
delay: 0,
subject: '{{first_name}} - saw something interesting',
body: 'Hi {{first_name}},\n\n...',
variant: 'B', // A/B test variant
},
{
step: 2,
delay: 3, // 3 days after step 1
subject: 'Re: Quick question',
body: 'Hi {{first_name}},\n\nJust following up...',
},
{
step: 3,
delay: 5, // 5 days after step 2
subject: 'Last try',
body: 'Hi {{first_name}},\n\nI know you\'re busy...',
},
],
};
app.post('/webhooks/instantly', express.json(), async (req, res) => {
const { event_type, lead_email, campaign_id, reply_text } = req.body;
switch (event_type) {
case 'reply_received':
await syncToCRM({ email: lead_email, status: 'replied', note: reply_text });
break;
case 'email_bounced':
await syncToCRM({ email: lead_email, status: 'bounced' });
break;
case 'unsubscribed':
await addToSuppressionList(lead_email);
break;
}
res.json({ processed: true });
});
| Issue | Cause | Solution |
|---|---|---|
| Low deliverability | Accounts not warmed | Run warmup 2+ weeks before campaigns |
| High bounce rate | Bad lead data | Verify emails before upload |
| Duplicate sends | Lead in multiple campaigns | Use skip_if_in_workspace flag |
| Rate limit | Too many API calls | Add 200ms delay between requests |
async function getCampaignMetrics() {
const campaigns = await listCampaigns();
return Promise.all(campaigns.map(async (c: any) => ({
name: c.name,
status: c.status,
analytics: await instantlyRequest(
`/analytics/campaign/summary?campaign_id=${c.id}`
),
})));
}