Skip to main content
Glama
campaign-service.ts17.4 kB
/** * 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)' ] }; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bcharleson/Instantly-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server