/**
* Instantly MCP Server - Campaign Service
*
* Business logic and helper functions for campaign management.
*/
import { getAllAccounts, getEligibleSenderAccounts } from './account-service.js';
import { DEFAULT_TIMEZONE } from '../timezone-config.js';
/**
* Gather campaign prerequisites and validate requirements
*
* This function analyzes the current state of accounts and campaign parameters
* to provide comprehensive guidance for campaign creation.
*
* @param args - Campaign creation arguments
* @param apiKey - Instantly.ai API key
* @returns Prerequisite analysis and guidance
*/
export async function gatherCampaignPrerequisites(args: any, apiKey?: string): Promise<any> {
console.error('[Instantly MCP] π Gathering campaign prerequisites...');
// Step 1: Fetch and analyze available accounts
let accounts: any[] = [];
let eligibleAccounts: any[] = [];
let accountAnalysis: any = {};
try {
const accountsResult = await getAllAccounts(apiKey);
// FIX: getAllAccounts returns { data: [...], metadata: {...} }, not an array directly
accounts = accountsResult.data || accountsResult;
if (!accounts || !Array.isArray(accounts) || accounts.length === 0) {
return {
stage: 'prerequisite_check',
status: 'no_accounts_found',
error: 'No accounts found in your workspace',
required_action: {
step: 1,
action: 'add_accounts',
description: 'You need to add at least one email account before creating campaigns',
instructions: [
'Go to your Instantly dashboard',
'Navigate to Accounts section',
'Add and verify email accounts',
'Complete warmup process for each account',
'Then retry campaign creation'
]
}
};
}
// Analyze account eligibility
eligibleAccounts = accounts.filter(account =>
account.status === 1 &&
!account.setup_pending &&
account.warmup_status === 1
);
const ineligibleAccounts = accounts.filter(account =>
account.status !== 1 ||
account.setup_pending ||
account.warmup_status !== 1
);
accountAnalysis = {
total_accounts: accounts.length,
eligible_accounts: eligibleAccounts.length,
ineligible_accounts: ineligibleAccounts.length,
eligible_emails: eligibleAccounts.map(acc => ({
email: acc.email,
warmup_score: acc.warmup_score || 0,
status: 'ready'
})),
ineligible_emails: ineligibleAccounts.map(acc => ({
email: acc.email,
issues: [
...(acc.status !== 1 ? ['Account not active'] : []),
...(acc.setup_pending ? ['Setup pending'] : []),
...(acc.warmup_status !== 1 ? ['Warmup not complete'] : [])
]
}))
};
} catch (error: any) {
return {
stage: 'prerequisite_check',
status: 'account_fetch_failed',
error: `Failed to fetch accounts: ${error.message}`,
suggestion: 'Please check your API key and try again, or call list_accounts directly to troubleshoot'
};
}
// Step 2: Analyze provided campaign data
const providedFields: Record<string, any> = {
name: args?.name || null,
subject: args?.subject || null,
body: args?.body || null,
email_list: args?.email_list || null,
campaign_schedule: args?.campaign_schedule || null,
sequences: args?.sequences || null
};
// Determine required fields based on campaign type
const hasComplexStructure = args?.campaign_schedule && args?.sequences;
const requiredFields = hasComplexStructure
? ['name', 'email_list'] // Complex campaigns only need name and email_list
: ['name', 'subject', 'body', 'email_list']; // Simple campaigns need all fields
const missingFields = requiredFields.filter(field => !providedFields[field]);
const hasAllRequired = missingFields.length === 0;
// Step 3: Validate email_list if provided
let emailValidation: any = null;
if (args?.email_list && Array.isArray(args.email_list)) {
const eligibleEmailSet = new Set(eligibleAccounts.map(acc => acc.email.toLowerCase()));
const invalidEmails = args.email_list.filter((email: string) =>
!eligibleEmailSet.has(email.toLowerCase())
);
emailValidation = {
provided_emails: args.email_list,
valid_emails: args.email_list.filter((email: string) =>
eligibleEmailSet.has(email.toLowerCase())
),
invalid_emails: invalidEmails,
validation_passed: invalidEmails.length === 0
};
}
// Step 4: Generate comprehensive guidance
const guidance = {
next_steps: [] as any[],
field_requirements: {
name: {
required: true,
description: 'Campaign name for identification',
example: 'Q4 Product Launch Campaign',
provided: !!args?.name
},
subject: {
required: true,
description: 'Email subject line',
example: 'Introducing our new product line',
formatting_tips: [
'Keep under 50 characters for better deliverability',
'Avoid spam trigger words',
'Use personalization: {{firstName}} or {{companyName}}'
],
provided: !!args?.subject
},
body: {
required: true,
description: 'Email body content',
formatting_requirements: [
'Use plain text with \\n for line breaks',
'Personalization variables: {{firstName}}, {{lastName}}, {{companyName}}',
'Only <p>, <br>, and <br/> HTML tags are allowed',
'Avoid escaped characters like \\" or \\t'
],
example: 'Hi {{firstName}},\\n\\nI hope this email finds you well.\\n\\nBest regards,\\nYour Name',
provided: !!args?.body
},
email_list: {
required: true,
description: 'Array of sender email addresses (must be from your verified accounts)',
constraint: 'Only one sender email per campaign creation call',
available_options: eligibleAccounts.map(acc => acc.email),
example: ['john@yourcompany.com'],
provided: !!args?.email_list,
validation_status: emailValidation
}
},
optional_settings: {
tracking: {
track_opens: {
default: false,
description: 'Track when recipients open emails (disabled by default)',
why_disabled: 'Email tracking can hurt deliverability and raises privacy concerns. Many email clients now block tracking pixels.'
},
track_clicks: {
default: false,
description: 'Track when recipients click links (disabled by default)',
why_disabled: 'Link tracking can trigger spam filters and reduces trust. Enable only if analytics are critical.'
}
},
scheduling: {
timezone: { default: DEFAULT_TIMEZONE, description: 'Timezone for sending schedule (verified working timezone)' },
timing_from: { default: '09:00', description: 'Start time for sending (24h format)' },
timing_to: { default: '17:00', description: 'End time for sending (24h format)' },
days: {
default: 'Monday-Friday',
description: 'Days of week for sending',
format: 'Object with boolean values for each day'
}
},
limits: {
daily_limit: {
default: 30,
description: 'Maximum emails per day per account (30 for cold email compliance)',
compliance_note: 'Higher limits may trigger spam filters and hurt deliverability. 30/day is the recommended maximum for cold outreach.'
},
email_gap: { default: 10, description: 'Minutes between emails from same account (1-1440 minutes)' }
}
}
};
// Add specific next steps based on current state
if (eligibleAccounts.length === 0) {
guidance.next_steps.push({
priority: 'critical',
action: 'fix_account_issues',
description: 'Resolve account eligibility issues before proceeding',
details: accountAnalysis.ineligible_emails
});
} else {
guidance.next_steps.push({
priority: 'recommended',
action: 'review_available_accounts',
description: `You have ${eligibleAccounts.length} eligible sending accounts available`,
accounts: accountAnalysis.eligible_emails
});
}
if (missingFields.length > 0) {
guidance.next_steps.push({
priority: 'required',
action: 'provide_missing_fields',
description: `Provide the following required fields: ${missingFields.join(', ')}`,
missing_fields: missingFields
});
}
if (emailValidation && !emailValidation.validation_passed) {
guidance.next_steps.push({
priority: 'critical',
action: 'fix_email_list',
description: 'Email list contains invalid addresses',
invalid_emails: emailValidation.invalid_emails,
suggestion: 'Use only emails from your eligible accounts listed above'
});
}
// Include comprehensive guidance for users
const fullGuidance = generateCampaignGuidance();
return {
stage: 'prerequisite_check',
status: hasAllRequired && eligibleAccounts.length > 0 && (!emailValidation || emailValidation.validation_passed)
? 'ready_for_creation'
: 'missing_requirements',
message: hasAllRequired
? 'All required fields provided. Ready to create campaign.'
: `Missing required information. Please provide: ${missingFields.join(', ')}`,
account_analysis: accountAnalysis,
field_analysis: providedFields,
validation_results: emailValidation,
step_by_step_guidance: guidance,
comprehensive_guide: fullGuidance,
ready_for_next_stage: hasAllRequired && eligibleAccounts.length > 0 && (!emailValidation || emailValidation.validation_passed)
};
}
/**
* Generate comprehensive campaign creation guidance
*
* @returns Detailed guidance object with best practices and examples
*/
export function generateCampaignGuidance(): any {
return {
getting_started: {
title: 'Campaign Creation Quick Start Guide',
overview: 'Creating successful email campaigns requires proper setup and understanding of key requirements.',
essential_first_step: {
action: 'Call list_accounts first',
reason: 'You need to see which email accounts are available and eligible for sending',
command: 'list_accounts {"get_all": true}'
}
},
required_fields: {
name: {
description: 'Campaign identifier for your reference',
examples: ['Q4 Product Launch Campaign', 'Holiday Sales Outreach', 'Partnership Inquiry - SaaS Companies'],
tips: ['Be descriptive but concise', 'Include target audience or purpose', 'Use consistent naming convention']
},
subject: {
description: 'Email subject line that recipients see',
best_practices: [
'Keep under 50 characters for better open rates',
'Personalize with {{firstName}} or {{companyName}}',
'Be specific and relevant to recipient',
'Avoid spam trigger words (FREE, URGENT, etc.)'
],
examples: [
'{{firstName}}, quick question about {{companyName}}',
'Helping {{companyName}} with [specific problem]',
'{{firstName}}, saw your recent [achievement/news]'
]
},
body: {
description: 'Main email content',
formatting_rules: [
'Use plain text with \\n for line breaks - they will be automatically converted to <br /> tags for HTML email rendering',
'Double line breaks (\\n\\n) create new paragraphs with <p> tags',
'Single line breaks (\\n) become <br /> tags within paragraphs',
'Personalize with {{firstName}}, {{lastName}}, {{companyName}}',
'Avoid escaped characters like \\" or \\t',
'The tool automatically handles HTML conversion - just use \\n in your input'
],
structure_template: 'Hi {{firstName}},\\n\\n[Opening line referencing something specific about them/their company]\\n\\n[Your value proposition or question]\\n\\n[Clear call to action]\\n\\nBest regards,\\n[Your name]',
example: 'Hi {{firstName}},\\n\\nI noticed {{companyName}} recently expanded into the European market. Congratulations!\\n\\nI help companies like yours streamline their international operations. Would you be open to a brief conversation about how we could support your expansion?\\n\\nBest regards,\\nJohn Smith',
html_conversion_note: 'This example will be automatically converted to proper HTML with <p> tags for paragraphs and <br /> tags for line breaks'
},
email_list: {
description: 'Sender email addresses from your verified accounts',
constraints: [
'Must use emails from your verified Instantly accounts',
'Only one sender email per campaign creation call',
'Accounts must be active (status=1) and warmed up (warmup_status=1)'
],
how_to_find: 'Call list_accounts to see available options',
example: ['john@yourcompany.com']
}
},
optional_settings_explained: {
tracking: {
track_opens: 'Tracks when recipients open your emails (recommended: true)',
track_clicks: 'Tracks when recipients click links in your emails (recommended: true)'
},
scheduling: {
timezone: `Timezone for sending schedule (default: ${DEFAULT_TIMEZONE} - verified working timezone)`,
timing_from: 'Start time for sending in 24h format (default: 09:00)',
timing_to: 'End time for sending in 24h format (default: 17:00)',
days: 'Days of week for sending (default: Monday-Friday only)'
},
limits: {
daily_limit: 'Maximum emails per day per account (default: 30 for cold email compliance)',
email_gap: 'Minutes between emails from same account (default: 10) - API v2 parameter'
},
behavior: {
stop_on_reply: 'Stop campaign when recipient replies (default: true)',
stop_on_auto_reply: 'Stop on auto-replies like out-of-office (default: true)'
},
sequences: {
sequence_steps: 'Number of emails in the sequence (default: 1 for single email, max: 10)',
step_delay_days: 'Days to wait AFTER sending each step before sending the next step (default: 3 days). CRITICAL: This delay applies to ALL steps including Step 1. Example with 2-day delay: Step 1 sends β Wait 2 days β Step 2 sends β Wait 2 days β Step 3 sends.',
sequence_bodies: 'Optional custom email content for each step (array of strings)',
sequence_subjects: 'Optional custom subject lines for each step (array of strings)',
when_to_use: 'Multi-step sequences are effective for cold outreach, nurturing leads, and following up on proposals',
best_practices: [
'Start with 2-3 steps for cold outreach',
'Increase value with each follow-up',
'Reference previous emails in follow-ups',
'Space follow-ups 2-7 days apart (step_delay_days parameter)',
'Personalize each step differently',
'CRITICAL: The delay applies to ALL steps - Step 1 will wait before Step 2, Step 2 will wait before Step 3, etc.'
]
}
},
common_mistakes: [
{
mistake: 'Not calling list_accounts first',
consequence: 'Using invalid email addresses that cause campaign creation to fail',
solution: 'Always call list_accounts to see available sender emails'
},
{
mistake: 'Using escaped characters in email body',
consequence: 'Email body appears with literal \\n instead of line breaks',
solution: 'Use plain \\n characters - they will be automatically converted to <br /> tags for HTML rendering'
},
{
mistake: 'Subject lines too long',
consequence: 'Poor open rates due to truncated subjects',
solution: 'Keep subjects under 50 characters'
},
{
mistake: 'Setting daily limits too high',
consequence: 'Triggers spam filters and hurts deliverability',
solution: 'Use maximum 30 emails per day for cold outreach compliance'
},
{
mistake: 'Enabling email tracking',
consequence: 'Reduced deliverability and privacy compliance issues',
solution: 'Keep tracking disabled unless analytics are absolutely necessary'
},
{
mistake: 'Generic, non-personalized content',
consequence: 'Low response rates and potential spam classification',
solution: 'Use personalization variables and specific, relevant content'
}
],
success_checklist: [
'β Called list_accounts to verify available sender emails',
'β Campaign name is descriptive and follows your naming convention',
'β Subject line is under 50 characters and personalized',
'β Email body uses \\n for line breaks (automatically converted to HTML)',
'β Content is personalized and relevant to recipients',
'β Sender email is from verified, warmed-up account',
'β Daily limit set to 30 or less for cold email compliance',
'β Email tracking disabled for better deliverability',
'β Sending schedule matches your target audience timezone',
'β Multi-step sequences configured appropriately (if using)'
]
};
}