Instantly Reference Architecture
Overview
Reference architecture for production Instantly.ai integrations. Covers project layout, API client design, event-driven webhook processing, campaign management, lead pipeline, and analytics dashboard. Designed for teams building outreach automation on top of Instantly API v2.
Architecture Diagram
Instantly API v2
(api.instantly.ai)
|
┌─────────────────────┼─────────────────────┐
| | |
Campaign Mgmt Lead Pipeline Account Mgmt
(create/launch/ (import/move/ (warmup/
pause/analytics) enrich/block) vitals/pause)
| | |
└─────────┬───────────┘ |
| |
┌─────────┴─────────┐ |
| Your Backend |◄────────────────────┘
| (Node/Python) |
└────────┬──────────┘
|
┌────────┴──────────┐
| Webhook Receiver |◄──── Instantly Webhooks
| (Cloud Run / | (reply_received,
| Vercel / Fly) | email_bounced, etc.)
└────────┬──────────┘
|
┌─────────────┼─────────────┐
| | |
CRM Sync Slack Alerts Analytics DB
Project Layout
instantly-integration/
├── src/
│ ├── instantly/
│ │ ├── client.ts # API client wrapper with retry + auth
│ │ ├── types.ts # TypeScript interfaces for API schemas
│ │ ├── pagination.ts # Cursor-based pagination helpers
│ │ └── cache.ts # Analytics caching layer
│ ├── campaigns/
│ │ ├── create.ts # Campaign creation with sequences
│ │ ├── launch.ts # Campaign activation workflow
│ │ ├── analytics.ts # Campaign performance tracking
│ │ └── templates.ts # Email sequence templates
│ ├── leads/
│ │ ├── import.ts # Lead import from CSV/CRM
│ │ ├── lists.ts # Lead list management
│ │ ├── enrich.ts # Lead enrichment pipeline
│ │ └── blocklist.ts # Block list management
│ ├── accounts/
│ │ ├── warmup.ts # Warmup enable/disable/monitor
│ │ ├── health.ts # Account vitals testing
│ │ └── rotation.ts # Account assignment to campaigns
│ ├── webhooks/
│ │ ├── server.ts # Express webhook receiver
│ │ ├── handlers.ts # Event type handlers
│ │ └── validation.ts # Payload validation
│ └── config.ts # Environment config
├── tests/
│ ├── unit/ # Unit tests (mocked API)
│ ├── integration/ # Integration tests (mock server)
│ └── e2e/ # End-to-end (live API, read-only)
├── scripts/
│ ├── seed-leads.ts # Seed campaign with test leads
│ ├── audit.ts # Workspace audit script
│ └── migrate-v1-to-v2.ts # API migration helper
├── .env.example
├── .github/workflows/ci.yml
├── Dockerfile
├── package.json
└── tsconfig.json
Core Module Implementation
API Client (src/instantly/client.ts)
import "dotenv/config";
export class InstantlyClient {
constructor(
private apiKey = process.env.INSTANTLY_API_KEY!,
private baseUrl = "https://api.instantly.ai/api/v2"
) {}
async request<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
...init,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
...init.headers,
},
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, 2000));
return this.request<T>(path, init);
}
if (!res.ok) {
throw new InstantlyApiError(res.status, path, await res.text());
}
return res.json() as Promise<T>;
}
// Campaign operations
campaigns = {
list: (limit = 50) => this.request<Campaign[]>(`/campaigns?limit=${limit}`),
get: (id: string) => this.request<Campaign>(`/campaigns/${id}`),
create: (data: CreateCampaignInput) =>
this.request<Campaign>("/campaigns", { method: "POST", body: JSON.stringify(data) }),
activate: (id: string) =>
this.request<void>(`/campaigns/${id}/activate`, { method: "POST" }),
pause: (id: string) =>
this.request<void>(`/campaigns/${id}/pause`, { method: "POST" }),
analytics: (id: string) =>
this.request<CampaignAnalytics>(`/campaigns/analytics?id=${id}`),
analyticsDaily: (id: string, start: string, end: string) =>
this.request(`/campaigns/analytics/daily?campaign_id=${id}&start_date=${start}&end_date=${end}`),
};
// Lead operations
leads = {
create: (data: CreateLeadInput) =>
this.request<Lead>("/leads", { method: "POST", body: JSON.stringify(data) }),
list: (filter: ListLeadsInput) =>
this.request<Lead[]>("/leads/list", { method: "POST", body: JSON.stringify(filter) }),
get: (id: string) => this.request<Lead>(`/leads/${id}`),
delete: (id: string) => this.request(`/leads/${id}`, { method: "DELETE" }),
updateInterest: (email: string, campaignId: string, value: number) =>
this.request("/leads/update-interest-status", {
method: "POST",
body: JSON.stringify({ lead_email: email, campaign_id: campaignId, interest_value: value }),
}),
};
// Account operations
accounts = {
list: (limit = 50) => this.request<Account[]>(`/accounts?limit=${limit}`),
get: (email: string) => this.request<Account>(`/accounts/${encodeURIComponent(email)}`),
enableWarmup: (emails: string[]) =>
this.request("/accounts/warmup/enable", { method: "POST", body: JSON.stringify({ emails }) }),
warmupAnalytics: (emails: string[]) =>
this.request("/accounts/warmup-analytics", { method: "POST", body: JSON.stringify({ emails }) }),
testVitals: (emails: string[]) =>
this.request("/accounts/test/vitals", { method: "POST", body: JSON.stringify({ accounts: emails }) }),
pause: (email: string) =>
this.request(`/accounts/${encodeURIComponent(email)}/pause`, { method: "POST" }),
resume: (email: string) =>
this.request(`/accounts/${encodeURIComponent(email)}/resume`, { method: "POST" }),
};
// Webhook operations
webhooks = {
list: () => this.request<Webhook[]>("/webhooks?limit=50"),
create: (data: CreateWebhookInput) =>
this.request<Webhook>("/webhooks", { method: "POST", body: JSON.stringify(data) }),
test: (id: string) => this.request(`/webhooks/${id}/test`, { method: "POST" }),
delete: (id: string) => this.request(`/webhooks/${id}`, { method: "DELETE" }),
};
}
Campaign Template System (src/campaigns/templates.ts)
export const SEQUENCE_TEMPLATES = {
"3-step-cold": {
name: "3-Step Cold Outreach",
sequences: [{
steps: [
{ type: "email" as const, delay: 0, variants: [
{ subject: "{{firstName}}, quick question about {{companyName}}", body: "..." },
]},
{ type: "email" as const, delay: 3, delay_unit: "days" as const, variants: [
{ subject: "Re: {{firstName}}, quick question", body: "..." },
]},
{ type: "email" as const, delay: 5, delay_unit: "days" as const, variants: [
{ subject: "Re: {{firstName}}, quick question", body: "..." },
]},
],
}],
defaults: {
daily_limit: 50,
stop_on_reply: true,
email_gap: 120,
link_tracking: false,
open_tracking: true,
},
},
"meeting-request": {
name: "Meeting Request",
sequences: [{
steps: [
{ type: "email" as const, delay: 0, variants: [
{ subject: "15 min chat, {{firstName}}?", body: "..." },
]},
{ type: "email" as const, delay: 2, delay_unit: "days" as const, variants: [
{ subject: "Re: 15 min chat", body: "..." },
]},
],
}],
defaults: {
daily_limit: 30,
stop_on_reply: true,
email_gap: 180,
},
},
};
Data Flow Summary
Lead CSV/CRM → import.ts → POST /leads → Campaign
↓
POST /campaigns/{id}/activate
↓
Instantly sends emails
↓
Webhook events fire
↓
handlers.ts routes events
↓
┌─────────┬─────────┬───────────┐
CRM Slack Analytics Block List
Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Client constructor fails | Missing INSTANTLY_API_KEY | Check .env file |
| Namespace methods fail | API scope mismatch | Verify key has correct scopes |
| Import fails midway | Network/rate limit | Batch with retry (see instantly-performance-tuning) |
| Webhook events missing | Webhook not registered | Register after deploy (see instantly-webhooks-events) |
Resources
Next Steps
For multi-environment setup, see instantly-multi-env-setup.