UNPKG

n8n-nodes-instantly-dev

Version:

n8n community node for Instantly API v2

458 lines 23.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CampaignOperations = void 0; const n8n_workflow_1 = require("n8n-workflow"); const generic_functions_1 = require("../../generic.functions"); const paginationHelpers_1 = require("../functions/paginationHelpers"); const resourceLocatorHelpers_1 = require("../functions/resourceLocatorHelpers"); /** * Campaign operations handler */ class CampaignOperations { /** * Create a new campaign */ static async create(context, itemIndex) { // HTML formatter for final API payload (after n8n expression processing) const convertToHtmlFormat = (content) => { if (!content) return ''; // Split content by line breaks and wrap each line in div tags // This ensures proper line spacing and formatting in Instantly emails const lines = content.split('\n'); const htmlContent = lines.map(line => { // Handle empty lines with a div containing a break if (line.trim() === '') { return '<div><br /></div>'; } // Wrap non-empty lines in div tags return `<div>${line}</div>`; }).join(''); return htmlContent; }; // Required fields const name = context.getNodeParameter('name', itemIndex); const scheduleName = context.getNodeParameter('scheduleName', itemIndex); const scheduleStartTime = context.getNodeParameter('scheduleStartTime', itemIndex); const scheduleEndTime = context.getNodeParameter('scheduleEndTime', itemIndex); const scheduleDays = context.getNodeParameter('scheduleDays', itemIndex); const scheduleTimezone = context.getNodeParameter('scheduleTimezone', itemIndex); // Additional fields const additionalFields = context.getNodeParameter('additionalFields', itemIndex, {}); // Build the days object from the selected days array const days = {}; scheduleDays.forEach(day => { const dayKey = day; days[dayKey] = true; }); // Build the schedule object const scheduleItem = { name: scheduleName, timing: { from: scheduleStartTime, to: scheduleEndTime, }, days, timezone: scheduleTimezone, }; const campaignSchedule = { schedules: [scheduleItem], }; // Add optional start/end dates if provided if (additionalFields.startDate) { campaignSchedule.start_date = additionalFields.startDate; } if (additionalFields.endDate) { campaignSchedule.end_date = additionalFields.endDate; } // Build the campaign creation payload const campaignData = { name, campaign_schedule: campaignSchedule, }; // Add optional fields if provided if (additionalFields.plValue !== undefined) { campaignData.pl_value = additionalFields.plValue; } if (additionalFields.isEvergreen !== undefined) { campaignData.is_evergreen = additionalFields.isEvergreen; } if (additionalFields.emailGap !== undefined) { campaignData.email_gap = additionalFields.emailGap; } if (additionalFields.randomWaitMax !== undefined) { campaignData.random_wait_max = additionalFields.randomWaitMax; } if (additionalFields.textOnly !== undefined) { campaignData.text_only = additionalFields.textOnly; } if (additionalFields.dailyLimit !== undefined) { campaignData.daily_limit = additionalFields.dailyLimit; } if (additionalFields.dailyMaxLeads !== undefined) { campaignData.daily_max_leads = additionalFields.dailyMaxLeads; } if (additionalFields.linkTracking !== undefined) { campaignData.link_tracking = additionalFields.linkTracking; } if (additionalFields.openTracking !== undefined) { campaignData.open_tracking = additionalFields.openTracking; } if (additionalFields.stopOnReply !== undefined) { campaignData.stop_on_reply = additionalFields.stopOnReply; } if (additionalFields.stopOnAutoReply !== undefined) { campaignData.stop_on_auto_reply = additionalFields.stopOnAutoReply; } if (additionalFields.stopForCompany !== undefined) { campaignData.stop_for_company = additionalFields.stopForCompany; } if (additionalFields.prioritizeNewLeads !== undefined) { campaignData.prioritize_new_leads = additionalFields.prioritizeNewLeads; } if (additionalFields.matchLeadEsp !== undefined) { campaignData.match_lead_esp = additionalFields.matchLeadEsp; } if (additionalFields.insertUnsubscribeHeader !== undefined) { campaignData.insert_unsubscribe_header = additionalFields.insertUnsubscribeHeader; } if (additionalFields.allowRiskyContacts !== undefined) { campaignData.allow_risky_contacts = additionalFields.allowRiskyContacts; } if (additionalFields.disableBounceProtect !== undefined) { campaignData.disable_bounce_protect = additionalFields.disableBounceProtect; } // Handle array fields if (additionalFields.emailList && additionalFields.emailList.length > 0) { campaignData.email_list = additionalFields.emailList; } if (additionalFields.emailTagList && additionalFields.emailTagList.length > 0) { campaignData.email_tag_list = additionalFields.emailTagList; } if (additionalFields.ccList && additionalFields.ccList.length > 0) { campaignData.cc_list = additionalFields.ccList; } if (additionalFields.bccList && additionalFields.bccList.length > 0) { campaignData.bcc_list = additionalFields.bccList; } // Handle auto variant select if (additionalFields.autoVariantSelectTrigger) { campaignData.auto_variant_select = { trigger: additionalFields.autoVariantSelectTrigger, }; } // Handle email sequence steps if (additionalFields.sequenceSteps && additionalFields.sequenceSteps.steps && additionalFields.sequenceSteps.steps.step) { const sequenceSteps = Array.isArray(additionalFields.sequenceSteps.steps.step) ? additionalFields.sequenceSteps.steps.step : [additionalFields.sequenceSteps.steps.step]; // Validate and build steps array for the sequence const steps = []; sequenceSteps.forEach((step, index) => { const stepNumber = index + 1; // Validate required fields if (!step.subject || !step.body) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Email step ${stepNumber} is missing required subject or body`, { itemIndex }); } // Smart content processor for hybrid variable detection (HTML conversion moved to post-processing) const smartProcessContent = (content) => { if (!content) return ''; // Step 1: Extract and protect Instantly variables (simple word patterns) const instantlyVars = {}; let varIndex = 0; // Protect Instantly variables like {{firstName}}, {{lastName}}, {{companyName}} // Pattern matches: {{word}} where word contains only letters, numbers, and underscores const protectedContent = content.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (match, varName) => { const placeholder = `__INSTANTLY_VAR_${varIndex++}__`; instantlyVars[placeholder] = match; return placeholder; }); // Step 2: n8n expressions (like {{ $json.fieldName }}) are processed normally by n8n // since we allow expressions on these fields for automation purposes // Step 3: Restore Instantly variables after n8n processing const finalContent = protectedContent.replace(/__INSTANTLY_VAR_\d+__/g, (placeholder) => { return instantlyVars[placeholder] || placeholder; }); // Step 4: Keep line breaks as \n for now - HTML conversion happens after n8n expression processing return finalContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); }; // Build sequence step object with variants array // The Instantly API expects each step to have a 'variants' array for A/B testing // Note: HTML conversion happens later, after n8n expression processing const sequenceStep = { type: 'email', // Required by Instantly API for email steps variants: [ { subject: smartProcessContent(step.subject), body: smartProcessContent(step.body), } ] }; // Add delay - ALL steps require a delay property if (stepNumber === 1) { // First step has delay of 0 (immediate) sequenceStep.delay = 0; } else { // Subsequent steps require a delay of at least 1 day if (!step.delay || step.delay < 1) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Email step ${stepNumber} requires a delay of at least 1 day`, { itemIndex }); } sequenceStep.delay = step.delay; } steps.push(sequenceStep); }); // Add sequences to campaign data if any were provided // The API expects an array with a single sequence object containing steps if (steps.length > 0) { campaignData.sequences = [{ steps: steps }]; } } // Apply HTML formatting to email content right before API call // This ensures it happens after all n8n expression processing is complete if (campaignData.sequences && campaignData.sequences[0] && campaignData.sequences[0].steps) { campaignData.sequences[0].steps.forEach((step, stepIndex) => { if (step.variants && step.variants.length > 0) { step.variants.forEach((variant, variantIndex) => { if (variant.subject) { variant.subject = convertToHtmlFormat(variant.subject); } if (variant.body) { variant.body = convertToHtmlFormat(variant.body); } }); } }); } return await generic_functions_1.instantlyApiRequest.call(context, 'POST', '/api/v2/campaigns', campaignData); } /** * Get a single campaign by ID */ static async get(context, itemIndex) { const campaignId = (0, resourceLocatorHelpers_1.getCampaignId)(context, itemIndex); return await generic_functions_1.instantlyApiRequest.call(context, 'GET', `/api/v2/campaigns/${campaignId}`); } /** * Get many campaigns with pagination support */ static async getMany(context, itemIndex) { const returnAll = context.getNodeParameter('returnAll', itemIndex, false); const limit = context.getNodeParameter('limit', itemIndex, 50); // Validate limit doesn't exceed 100 if (limit > 100) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Limit cannot exceed 100. Instantly API has a maximum limit of 100.', { itemIndex }); } if (returnAll) { // Get all campaigns with pagination const allCampaigns = await (0, paginationHelpers_1.paginateInstantlyApi)(context, '/api/v2/campaigns', 'campaigns'); return { items: allCampaigns }; } else { // Get single page with specified limit const queryParams = { limit }; return await generic_functions_1.instantlyApiRequest.call(context, 'GET', '/api/v2/campaigns', {}, queryParams); } } /** * Update a campaign */ static async update(context, itemIndex) { const campaignId = (0, resourceLocatorHelpers_1.getCampaignId)(context, itemIndex); const name = context.getNodeParameter('name', itemIndex, ''); const updateAdditionalFields = context.getNodeParameter('updateAdditionalFields', itemIndex, {}); const campaignSchedule = context.getNodeParameter('campaignSchedule', itemIndex, {}); // Build the request body const body = {}; // Only add name if it's provided if (name && name.trim() !== '') { body.name = name.trim(); } // Add additional fields if provided if (updateAdditionalFields.plValue !== undefined) { body.pl_value = updateAdditionalFields.plValue; } if (updateAdditionalFields.isEvergreen !== undefined) { body.is_evergreen = updateAdditionalFields.isEvergreen; } if (updateAdditionalFields.emailGap !== undefined) { body.email_gap = updateAdditionalFields.emailGap; } if (updateAdditionalFields.randomWaitMax !== undefined) { body.random_wait_max = updateAdditionalFields.randomWaitMax; } if (updateAdditionalFields.textOnly !== undefined) { body.text_only = updateAdditionalFields.textOnly; } if (updateAdditionalFields.dailyLimit !== undefined) { body.daily_limit = updateAdditionalFields.dailyLimit; } if (updateAdditionalFields.dailyMaxLeads !== undefined) { body.daily_max_leads = updateAdditionalFields.dailyMaxLeads; } if (updateAdditionalFields.emailList !== undefined && updateAdditionalFields.emailList.length > 0) { body.email_list = updateAdditionalFields.emailList; } if (updateAdditionalFields.emailTagList !== undefined && updateAdditionalFields.emailTagList.trim() !== '') { body.email_tag_list = updateAdditionalFields.emailTagList.split(',').map((tag) => tag.trim()); } if (updateAdditionalFields.ccList !== undefined && updateAdditionalFields.ccList.trim() !== '') { body.cc_list = updateAdditionalFields.ccList.split(',').map((email) => email.trim()); } if (updateAdditionalFields.bccList !== undefined && updateAdditionalFields.bccList.trim() !== '') { body.bcc_list = updateAdditionalFields.bccList.split(',').map((email) => email.trim()); } if (updateAdditionalFields.linkTracking !== undefined) { body.link_tracking = updateAdditionalFields.linkTracking; } if (updateAdditionalFields.openTracking !== undefined) { body.open_tracking = updateAdditionalFields.openTracking; } if (updateAdditionalFields.stopOnReply !== undefined) { body.stop_on_reply = updateAdditionalFields.stopOnReply; } if (updateAdditionalFields.stopOnAutoReply !== undefined) { body.stop_on_auto_reply = updateAdditionalFields.stopOnAutoReply; } if (updateAdditionalFields.stopForCompany !== undefined) { body.stop_for_company = updateAdditionalFields.stopForCompany; } if (updateAdditionalFields.prioritizeNewLeads !== undefined) { body.prioritize_new_leads = updateAdditionalFields.prioritizeNewLeads; } if (updateAdditionalFields.matchLeadEsp !== undefined) { body.match_lead_esp = updateAdditionalFields.matchLeadEsp; } if (updateAdditionalFields.insertUnsubscribeHeader !== undefined) { body.insert_unsubscribe_header = updateAdditionalFields.insertUnsubscribeHeader; } if (updateAdditionalFields.allowRiskyContacts !== undefined) { body.allow_risky_contacts = updateAdditionalFields.allowRiskyContacts; } if (updateAdditionalFields.disableBounceProtect !== undefined) { body.disable_bounce_protect = updateAdditionalFields.disableBounceProtect; } if (updateAdditionalFields.autoVariantSelectTrigger !== undefined && updateAdditionalFields.autoVariantSelectTrigger !== '') { body.auto_variant_select = { trigger: updateAdditionalFields.autoVariantSelectTrigger, }; } if (updateAdditionalFields.sequences !== undefined && updateAdditionalFields.sequences.trim() !== '') { try { body.sequences = JSON.parse(updateAdditionalFields.sequences); } catch (error) { throw new Error('Invalid JSON format for sequences field'); } } // Handle campaign schedule if (campaignSchedule && Object.keys(campaignSchedule).length > 0) { const schedule = {}; if (campaignSchedule.startDate) { schedule.start_date = campaignSchedule.startDate; } if (campaignSchedule.endDate) { schedule.end_date = campaignSchedule.endDate; } if (campaignSchedule.schedules && campaignSchedule.schedules.schedule && campaignSchedule.schedules.schedule.length > 0) { schedule.schedules = campaignSchedule.schedules.schedule.map((sched) => { const scheduleItem = { name: sched.name || 'Default Schedule', timing: { from: sched.from || '09:00', to: sched.to || '17:00', }, timezone: sched.timezone || 'America/New_York', }; // Convert days array to object format expected by API if (sched.days && Array.isArray(sched.days)) { const daysObj = {}; for (let i = 0; i <= 6; i++) { daysObj[i.toString()] = sched.days.includes(i.toString()); } scheduleItem.days = daysObj; } else { // Default to weekdays scheduleItem.days = { '0': false, // Sunday '1': true, // Monday '2': true, // Tuesday '3': true, // Wednesday '4': true, // Thursday '5': true, // Friday '6': false, // Saturday }; } return scheduleItem; }); } if (Object.keys(schedule).length > 0) { body.campaign_schedule = schedule; } } return await generic_functions_1.instantlyApiRequest.call(context, 'PATCH', `/api/v2/campaigns/${campaignId}`, body); } /** * Delete a campaign */ static async delete(context, itemIndex) { const campaignId = (0, resourceLocatorHelpers_1.getCampaignId)(context, itemIndex); return await generic_functions_1.instantlyApiRequest.call(context, 'DELETE', `/api/v2/campaigns/${campaignId}`); } /** * Launch (activate) a campaign * Phase 1A: Critical Campaign Control */ static async launch(context, itemIndex) { const campaignId = (0, resourceLocatorHelpers_1.getCampaignId)(context, itemIndex); try { return await generic_functions_1.instantlyApiRequest.call(context, 'POST', `/api/v2/campaigns/${campaignId}/activate`); } catch (error) { // Handle specific error cases for launch operation if (error.response?.statusCode === 404) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Campaign with ID '${campaignId}' not found. Please verify the campaign ID is correct and the campaign exists in your Instantly workspace.`, { itemIndex }); } else if (error.response?.statusCode === 400 || error.response?.statusCode === 422) { // Campaign might already be active or missing prerequisites const errorMessage = error.response?.body?.message || error.message; throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Cannot launch campaign '${campaignId}'. ${errorMessage}. Please check that the campaign has leads, assigned email accounts, and all required settings are configured.`, { itemIndex }); } else { // Re-throw other errors with more context throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Failed to launch campaign '${campaignId}': ${error.message}`, { itemIndex }); } } } /** * Pause a campaign * Phase 1A: Critical Campaign Control */ static async pause(context, itemIndex) { const campaignId = (0, resourceLocatorHelpers_1.getCampaignId)(context, itemIndex); try { return await generic_functions_1.instantlyApiRequest.call(context, 'POST', `/api/v2/campaigns/${campaignId}/pause`); } catch (error) { // Handle specific error cases for pause operation if (error.response?.statusCode === 404) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Campaign with ID '${campaignId}' not found. Please verify the campaign ID is correct and the campaign exists in your Instantly workspace.`, { itemIndex }); } else if (error.response?.statusCode === 400 || error.response?.statusCode === 422) { // Campaign might already be paused or in an invalid state const errorMessage = error.response?.body?.message || error.message; throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Cannot pause campaign '${campaignId}'. ${errorMessage}. The campaign may already be paused or not in a state that allows pausing.`, { itemIndex }); } else { // Re-throw other errors with more context throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Failed to pause campaign '${campaignId}': ${error.message}`, { itemIndex }); } } } } exports.CampaignOperations = CampaignOperations; //# sourceMappingURL=CampaignOperations.js.map