Create Instantly Campaign
Create a new campaign in Instantly with email sequences
Source Code
const [campaignName, emailAccountId, sequencesJson] = process.argv.slice(2);
if (!campaignName || !emailAccountId || !sequencesJson) {
console.error("Usage: outreach.instantly.create <campaignName> <emailAccountId> <sequencesJson>");
console.error(" sequencesJson: '[{\"subject\":\"...\",\"body\":\"...\",\"delay_days\":0}]'");
process.exit(1);
}
const API_BASE = "https://api.instantly.ai/api/v2";
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;
/**
* Sleep for specified milliseconds
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Fetch with retry logic for transient failures
*/
async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const res = await fetch(url, options);
if (res.ok) {
return res;
}
// Don't retry client errors (4xx)
if (res.status >= 400 && res.status < 500) {
return res;
}
// Server error - retry with backoff
if (attempt < retries) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
console.log(` Retry ${attempt}/${retries} after ${delay}ms (status: ${res.status})`);
await sleep(delay);
} else {
return res;
}
} catch (error) {
if (attempt < retries) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
console.log(` Retry ${attempt}/${retries} after ${delay}ms (error: ${error.message})`);
await sleep(delay);
} else {
throw error;
}
}
}
}
/**
* Parse API error response and provide actionable guidance
*/
async function handleApiError(res, context) {
const text = await res.text();
if (res.status === 401 || res.status === 403) {
throw new Error(
`Authentication failed (${res.status}). ` +
`Check that your Instantly connection is configured correctly and the API key has the required scopes.`
);
}
if (res.status === 429) {
throw new Error(
`Rate limit exceeded (${res.status}). ` +
`Instantly API is throttling requests. Wait a moment and try again.`
);
}
if (res.status >= 500) {
throw new Error(
`Instantly API server error (${res.status}) while ${context}. ` +
`This may be a temporary issue. If it persists, check Instantly's status page.`
);
}
throw new Error(`Instantly API failed while ${context}: ${res.status} - ${text.substring(0, 200)}`);
}
/**
* Parse sequences JSON safely
*/
function parseSequences(json) {
try {
const sequences = JSON.parse(json);
if (!Array.isArray(sequences)) {
throw new Error("Sequences must be an array");
}
// Validate sequence structure
return sequences.map((seq, idx) => {
const delay = seq.delay_days !== undefined ? seq.delay_days : (idx === 0 ? 0 : 3);
// First email should have delay 0
if (idx === 0 && delay !== 0) {
console.log(" Note: Setting first email delay to 0 (sends immediately when leads added)");
}
// Subsequent emails should have delay >= 1
if (idx > 0 && delay < 1) {
console.log(` Note: Email ${idx + 1} delay adjusted to 1 day minimum`);
}
return {
subject: seq.subject || `Email ${idx + 1}`,
body: seq.body || "",
delay: idx === 0 ? 0 : Math.max(1, delay),
};
});
} catch (e) {
throw new Error(`Invalid sequences JSON: ${e.message}`);
}
}
/**
* Create campaign via Instantly API v2
*/
async function createCampaign(name, emailAccount, sequences) {
const url = `${API_BASE}/campaigns`;
const payload = {
name: name,
email_account: emailAccount,
sequences: sequences.map((seq, idx) => ({
step: idx + 1,
subject: seq.subject,
body: seq.body,
delay: seq.delay,
})),
};
const res = await fetchWithRetry(url, {
method: "POST",
headers: {
"Authorization": "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!res.ok) {
await handleApiError(res, "creating campaign");
}
return await res.json();
}
/**
* List email accounts to help user pick one (v2 endpoint)
*/
async function listEmailAccounts() {
const url = `${API_BASE}/email-accounts?limit=100`;
const res = await fetchWithRetry(url, {
method: "GET",
headers: {
"Authorization": "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
},
});
if (!res.ok) {
console.log(" Warning: Could not fetch email accounts list");
return [];
}
const data = await res.json();
// v2 returns items array
return data.items || data || [];
}
try {
console.log(`Creating campaign: "${campaignName}"`);
// Parse and validate sequences
const sequences = parseSequences(sequencesJson);
console.log(` Email sequence: ${sequences.length} emails`);
// Validate minimum sequence requirements
if (sequences.length === 0) {
throw new Error("Campaign must have at least one email in the sequence");
}
for (let i = 0; i < sequences.length; i++) {
const seq = sequences[i];
if (!seq.subject || seq.subject.trim() === "") {
throw new Error(`Email ${i + 1} is missing a subject line`);
}
if (!seq.body || seq.body.trim() === "") {
throw new Error(`Email ${i + 1} is missing body content`);
}
const delay = i === 0 ? "immediately" : `after ${seq.delay} days`;
console.log(` ${i + 1}. "${seq.subject}" (${delay})`);
}
// Create the campaign
const result = await createCampaign(campaignName, emailAccountId, sequences);
console.log(`\nā Campaign created successfully`);
console.log(` ID: ${result.id || "unknown"}`);
console.log(` Name: ${campaignName}`);
console.log(` Emails: ${sequences.length}`);
console.log(` Status: draft (add leads to activate)`);
console.log(JSON.stringify({
success: true,
campaignId: result.id,
campaignName: campaignName,
sequenceCount: sequences.length,
status: "draft",
}));
} catch (error) {
console.error("Failed:", error.message);
throw error;
}