Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/src/types/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export interface NodeCredentials {
apify?: {
apiToken: string
}
/** Instantly.ai API credentials */
instantly?: {
apiKey: string
}
/** Google Sheets OAuth2 credentials */
googleSheets?: {
clientId: string
Expand Down
20 changes: 20 additions & 0 deletions packages/nodes/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,18 @@ export {
WordPressPostSchema,
WordPressMediaSchema,
WordPressCredential,
// Instantly
instantlyAddLeadNode,
InstantlyAddLeadInputSchema,
InstantlyAddLeadOutputSchema,
instantlyCreateCampaignNode,
InstantlyCreateCampaignInputSchema,
InstantlyCreateCampaignOutputSchema,
instantlyGetAnalyticsNode,
InstantlyGetAnalyticsInputSchema,
InstantlyGetAnalyticsOutputSchema,
SequenceStepSchema,
instantlyCredential,
// Google Sheets
googleSheetsAppendNode,
googleSheetsClearNode,
Expand Down Expand Up @@ -267,6 +279,14 @@ export type {
WordPressMedia,
WordPressUploadMediaInput,
WordPressUploadMediaOutput,
// Instantly
InstantlyAddLeadInput,
InstantlyAddLeadOutput,
InstantlyCreateCampaignInput,
InstantlyCreateCampaignOutput,
InstantlyGetAnalyticsInput,
InstantlyGetAnalyticsOutput,
SequenceStep,
// Google Sheets
AppendInput,
AppendOutput,
Expand Down
22 changes: 22 additions & 0 deletions packages/nodes/src/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,28 @@ export {
apifyCredential,
} from './apify/index.js'

// Instantly integrations
export {
instantlyAddLeadNode,
InstantlyAddLeadInputSchema,
InstantlyAddLeadOutputSchema,
type InstantlyAddLeadInput,
type InstantlyAddLeadOutput,
instantlyCreateCampaignNode,
InstantlyCreateCampaignInputSchema,
InstantlyCreateCampaignOutputSchema,
type InstantlyCreateCampaignInput,
type InstantlyCreateCampaignOutput,
instantlyGetAnalyticsNode,
InstantlyGetAnalyticsInputSchema,
InstantlyGetAnalyticsOutputSchema,
type InstantlyGetAnalyticsInput,
type InstantlyGetAnalyticsOutput,
SequenceStepSchema,
type SequenceStep,
instantlyCredential,
} from './instantly/index.js'

// Google Sheets integrations
export {
googleSheetsAppendNode,
Expand Down
17 changes: 17 additions & 0 deletions packages/nodes/src/integrations/instantly/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { z } from 'zod';
import { defineBearerCredential } from '@jam-nodes/core';

export const instantlyCredential = defineBearerCredential({
name: 'instantly',
displayName: 'Instantly.ai API Key',
documentationUrl: 'https://developer.instantly.ai/',
schema: z.object({
apiKey: z.string(),
}),
authenticate: {
type: 'header',
properties: {
Authorization: 'Bearer {{apiKey}}',
},
},
});
30 changes: 30 additions & 0 deletions packages/nodes/src/integrations/instantly/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export {
instantlyAddLeadNode,
InstantlyAddLeadInputSchema,
InstantlyAddLeadOutputSchema,
type InstantlyAddLeadInput,
type InstantlyAddLeadOutput,
} from './instantly-add-lead.js';

export {
instantlyCreateCampaignNode,
InstantlyCreateCampaignInputSchema,
InstantlyCreateCampaignOutputSchema,
type InstantlyCreateCampaignInput,
type InstantlyCreateCampaignOutput,
} from './instantly-create-campaign.js';

export {
instantlyGetAnalyticsNode,
InstantlyGetAnalyticsInputSchema,
InstantlyGetAnalyticsOutputSchema,
type InstantlyGetAnalyticsInput,
type InstantlyGetAnalyticsOutput,
} from './instantly-get-analytics.js';

export {
SequenceStepSchema,
type SequenceStep,
} from './schemas.js';

export { instantlyCredential } from './credentials.js';
106 changes: 106 additions & 0 deletions packages/nodes/src/integrations/instantly/instantly-add-lead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { defineNode } from '@jam-nodes/core';
import { fetchWithRetry } from '../../utils/http.js';
import {
InstantlyAddLeadInputSchema,
InstantlyAddLeadOutputSchema,
type InstantlyAddLeadInput,
type InstantlyAddLeadOutput,
} from './schemas.js';

const INSTANTLY_API_BASE = 'https://api.instantly.ai/api/v1';

interface InstantlyAddLeadResponse {
success?: boolean;
leadId?: string;
id?: string;
error?: string;
}

export {
InstantlyAddLeadInputSchema,
InstantlyAddLeadOutputSchema,
type InstantlyAddLeadInput,
type InstantlyAddLeadOutput,
} from './schemas.js';

export const instantlyAddLeadNode = defineNode({
type: 'instantly_add_lead',
name: 'Instantly Add Lead',
description: 'Add a lead to an Instantly.ai cold email campaign',
category: 'integration',
inputSchema: InstantlyAddLeadInputSchema,
outputSchema: InstantlyAddLeadOutputSchema,
estimatedDuration: 3,
capabilities: {
supportsRerun: true,
},

executor: async (input: InstantlyAddLeadInput, context) => {
try {
const apiKey = context.credentials?.instantly?.apiKey;
if (!apiKey) {
return {
success: false,
error:
'Instantly API key not configured. Please provide context.credentials.instantly.apiKey.',
};
}

const body: Record<string, unknown> = {
campaignId: input.campaignId,
email: input.email,
};
if (input.firstName !== undefined) body.firstName = input.firstName;
if (input.lastName !== undefined) body.lastName = input.lastName;
if (input.companyName !== undefined) body.companyName = input.companyName;
if (input.personalization !== undefined) body.personalization = input.personalization;
if (input.customVariables !== undefined) body.customVariables = input.customVariables;

const response = await fetchWithRetry(
`${INSTANTLY_API_BASE}/leads`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
},
{ maxRetries: 3, backoffMs: 1000, timeoutMs: 30000 }
);

if (!response.ok) {
const errorText = (await response.text()).slice(0, 500);
return {
success: false,
error: `Instantly API error ${response.status}: ${errorText}`,
};
}

const data = (await response.json()) as InstantlyAddLeadResponse;

if (data.success === false) {
return {
success: false,
error: `Instantly API error: ${data.error ?? 'unknown_error'}`,
};
}

const leadId = data.leadId ?? data.id ?? '';

const output: InstantlyAddLeadOutput = {
success: true,
leadId,
campaignId: input.campaignId,
email: input.email,
};

return { success: true, output };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to add Instantly lead',
};
}
},
});
103 changes: 103 additions & 0 deletions packages/nodes/src/integrations/instantly/instantly-create-campaign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { defineNode } from '@jam-nodes/core';
import { fetchWithRetry } from '../../utils/http.js';
import {
InstantlyCreateCampaignInputSchema,
InstantlyCreateCampaignOutputSchema,
type InstantlyCreateCampaignInput,
type InstantlyCreateCampaignOutput,
} from './schemas.js';

const INSTANTLY_API_BASE = 'https://api.instantly.ai/api/v1';

interface InstantlyCreateCampaignResponse {
success?: boolean;
campaignId?: string;
id?: string;
error?: string;
}

export {
InstantlyCreateCampaignInputSchema,
InstantlyCreateCampaignOutputSchema,
type InstantlyCreateCampaignInput,
type InstantlyCreateCampaignOutput,
} from './schemas.js';

export const instantlyCreateCampaignNode = defineNode({
type: 'instantly_create_campaign',
name: 'Instantly Create Campaign',
description: 'Create an Instantly.ai cold email campaign with a drip sequence',
category: 'integration',
inputSchema: InstantlyCreateCampaignInputSchema,
outputSchema: InstantlyCreateCampaignOutputSchema,
estimatedDuration: 5,
capabilities: {
supportsRerun: false,
},

executor: async (input: InstantlyCreateCampaignInput, context) => {
try {
const apiKey = context.credentials?.instantly?.apiKey;
if (!apiKey) {
return {
success: false,
error:
'Instantly API key not configured. Please provide context.credentials.instantly.apiKey.',
};
}

const body = {
name: input.name,
emailAccounts: input.emailAccounts,
sequence: input.sequence,
};

const response = await fetchWithRetry(
`${INSTANTLY_API_BASE}/campaigns`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
},
{ maxRetries: 3, backoffMs: 1000, timeoutMs: 30000 }
);

if (!response.ok) {
const errorText = (await response.text()).slice(0, 500);
return {
success: false,
error: `Instantly API error ${response.status}: ${errorText}`,
};
}

const data = (await response.json()) as InstantlyCreateCampaignResponse;

if (data.success === false) {
return {
success: false,
error: `Instantly API error: ${data.error ?? 'unknown_error'}`,
};
}

const campaignId = data.campaignId ?? data.id ?? '';

const output: InstantlyCreateCampaignOutput = {
success: true,
campaignId,
name: input.name,
emailAccountCount: input.emailAccounts.length,
sequenceStepCount: input.sequence.length,
};

return { success: true, output };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to create Instantly campaign',
};
}
},
});
Loading