UNPKG

cakemail-mcp-server

Version:

Enterprise MCP server for Cakemail API integration with Claude AI - includes comprehensive template management, list management, sub-account management, BEEeditor visual email design, and advanced analytics

566 lines 22.8 kB
// Campaign API operations with corrected parameter syntax import { BaseApiClient } from './base-client.js'; import logger from '../utils/logger.js'; export class CampaignApi extends BaseApiClient { // FIXED: Campaign API methods with correct parameter syntax - Legacy method (deprecated) async getCampaigns(params) { let apiParams = {}; let filters = []; // 1. Handle sorting with correct API syntax ([-|+]term) if (params?.sort) { const direction = params?.order === 'desc' ? '-' : '+'; apiParams.sort = `${direction}${params.sort}`; } else { // Default to latest first using correct API syntax apiParams.sort = '-created_on'; } // 2. Handle filtering with correct API syntax (term==value;term2==value2) if (params?.status) { filters.push(`status==${params.status}`); } if (params?.name) { filters.push(`name==${params.name}`); } if (params?.type) { filters.push(`type==${params.type}`); } if (params?.list_id) { filters.push(`list_id==${params.list_id}`); } if (filters.length > 0) { apiParams.filter = filters.join(';'); } // 3. Handle valid direct parameters if (params?.page) apiParams.page = params.page; if (params?.per_page) apiParams.per_page = params.per_page; if (params?.with_count) apiParams.with_count = params.with_count; // 4. Add account_id support for proper scoping if (params?.account_id) { apiParams.account_id = params.account_id; } else { // Try to get current account ID for better results const accountId = await this.getCurrentAccountId(); if (accountId) { apiParams.account_id = accountId; } } // 5. Validate parameters against API specification const validSortTerms = ['name', 'created_on', 'scheduled_for', 'scheduled_on', 'updated_on', 'type']; if (params?.sort && !validSortTerms.includes(params.sort)) { throw new Error(`Invalid sort term '${params.sort}'. Valid terms: ${validSortTerms.join(', ')}`); } // 6. Validate pagination limits if (apiParams.per_page && apiParams.per_page > 50) { throw new Error('per_page cannot exceed 50 (API limit)'); } const query = Object.keys(apiParams).length > 0 ? `?${new URLSearchParams(apiParams)}` : ''; if (this.debugMode) { logger.info(`[Campaign API] Final query parameters:`, apiParams); logger.info(`[Campaign API] Final URL: GET /campaigns${query}`); } return this.makeRequest(`/campaigns${query}`); } // NEW: Unified pagination method for campaigns async getCampaignsPaginated(options = {}, // Filters are processed inline rather than using a dedicated object additionalFilters = {}) { const accountId = await this.getCurrentAccountId(); const params = { account_id: additionalFilters.account_id || accountId }; // Handle sorting with correct API syntax ([-|+]term) if (additionalFilters.sort) { const direction = additionalFilters.order === 'desc' ? '-' : '+'; params.sort = `${direction}${additionalFilters.sort}`; } else { params.sort = '-created_on'; // Default sort } // Handle filtering with correct API syntax (term==value;term2==value2) const filterParts = []; if (additionalFilters.status) filterParts.push(`status==${additionalFilters.status}`); if (additionalFilters.name) filterParts.push(`name==${additionalFilters.name}`); if (additionalFilters.type) filterParts.push(`type==${additionalFilters.type}`); if (additionalFilters.list_id) filterParts.push(`list_id==${additionalFilters.list_id}`); if (filterParts.length > 0) { params.filter = filterParts.join(';'); } return this.fetchPaginated('/campaigns', 'campaigns', options, params); } // NEW: Iterator for automatic pagination through campaigns getCampaignsIterator(options = {}, campaignFilters = {}) { // Use campaignFilters to build query parameters const queryParams = { ...campaignFilters }; return this.createRobustIterator('/campaigns', 'campaigns', { ...options, validateResponse: (response) => { return response && (Array.isArray(response.data) || (response.data && Array.isArray(response.data.data))); }, onError: (error, attempt) => { if (this.debugMode) { logger.info(`[Campaign API] Attempt ${attempt} failed:`, error.message); } } }, // Pass queryParams to be used by the iterator queryParams); } // NEW: Get all campaigns with automatic pagination async getAllCampaigns(options = {}, filters = {}) { const iterator = this.getCampaignsIterator(options, filters); return iterator.toArray(); } // NEW: Process campaigns in batches async processCampaignsInBatches(processor, options = {}, filters = {}) { const iterator = this.getCampaignsIterator(options, filters); for await (const batch of iterator.batches()) { await processor(batch); } } // Update sendCampaign to use the new scheduleCampaign method async sendCampaign(id) { return this.scheduleCampaign(id); } // FIXED: Enhanced getLatestCampaign with correct API usage async getLatestCampaign(status) { if (this.debugMode) { logger.info(`[Campaign API] Getting latest campaign with status filter: ${status || 'none'}`); } const params = { sort: 'created_on', order: 'desc', // Will be converted to sort='-created_on' per_page: 1, page: 1 }; if (status) { params.status = status; // Will be converted to filter='status==<value>' } // Try with account scoping first try { const accountId = await this.getCurrentAccountId(); if (accountId) { params.account_id = accountId; } const result = await this.getCampaigns(params); if (result.data && Array.isArray(result.data) && result.data.length > 0) { if (this.debugMode) { logger.info(`[Campaign API] Latest campaign found: ${result.data[0].id}`); } return result.data[0]; } } catch (error) { if (this.debugMode) { logger.info(`[Campaign API] Failed to get latest campaign:`, error.message); } } // Fallback: try without account scoping if (params.account_id) { delete params.account_id; try { const result = await this.getCampaigns(params); if (result.data && Array.isArray(result.data) && result.data.length > 0) { if (this.debugMode) { logger.info(`[Campaign API] Latest campaign found (fallback): ${result.data[0].id}`); } return result.data[0]; } } catch (error) { if (this.debugMode) { logger.info(`[Campaign API] Fallback also failed:`, error.message); } } } if (this.debugMode) { logger.info(`[Campaign API] No latest campaign found`); } return null; } // Enhanced getCampaignsWithDefaults for backward compatibility async getCampaignsWithDefaults(params) { // Apply smart defaults for better UX const enhancedParams = { sort: 'created_on', order: 'desc', ...params }; if (this.debugMode) { logger.info(`[Campaign API] Getting campaigns with defaults:`, enhancedParams); } return this.getCampaigns(enhancedParams); } // Individual campaign retrieval async getCampaign(id, options) { // Use provided account_id or get current account ID const accountId = options?.account_id || await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}${query}`); } // FIXED: Campaign creation with exact API spec structure async createCampaign(data) { // 1. Require and validate list_id if (!data.list_id) { throw new Error('list_id is required to create a campaign (audience.list_id)'); } // Build campaign data according to exact API specification const campaignData = { name: data.name, audience: { list_id: parseInt(String(data.list_id)) } }; // Add segment_id if provided if (data.segment_id) { campaignData.audience.segment_id = parseInt(String(data.segment_id)); } // 2. Tracking - with defaults matching API spec campaignData.tracking = { opens: true, clicks_html: true, clicks_text: true, ...(data.tracking || {}) // Allow override of tracking settings }; // 3. Sender - required structure with id and optional name if (data.sender_id) { campaignData.sender = { id: String(data.sender_id) }; if (data.from_name) { campaignData.sender.name = data.from_name; } } // 4. Reply-to email (separate from sender) if (data.reply_to) { campaignData.reply_to_email = data.reply_to; } // 5. Content - always set with at least a type campaignData.content = { type: 'html' }; if (data.subject) { campaignData.content.subject = data.subject; } if (data.html_content) { campaignData.content.html = data.html_content; } if (data.text_content) { campaignData.content.text = data.text_content; } // JSON content for BEE editor if (data.json_content) { campaignData.content.json = data.json_content; campaignData.content.type = 'bee'; } // Template reference if (data.template_id) { campaignData.content.template = { id: parseInt(String(data.template_id)) }; } // Blueprint reference if (data.blueprint_id) { campaignData.content.blueprint = { id: parseInt(String(data.blueprint_id)) }; } // Content type - allow override from input if (data.content_type) { campaignData.content.type = data.content_type; } // Encoding if (data.encoding) { campaignData.content.encoding = data.encoding; } // Default unsubscribe link if (data.default_unsubscribe_link !== undefined) { campaignData.content.default_unsubscribe_link = data.default_unsubscribe_link; } // Remove undefined fields from content Object.keys(campaignData.content).forEach(key => { if (campaignData.content[key] === undefined) { delete campaignData.content[key]; } }); // Use explicit account_id if provided, otherwise get current account ID const accountId = data.account_id || await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns${query}`, { method: 'POST', body: JSON.stringify(campaignData) }); } // FIXED: Campaign update with exact API spec structure async updateCampaign(id, data) { const updateData = {}; // Only include fields that are provided if (data.name !== undefined) { updateData.name = data.name; } // Sender - structured object with id and optional name if (data.sender_id !== undefined || data.from_name !== undefined) { updateData.sender = {}; if (data.sender_id !== undefined) { updateData.sender.id = String(data.sender_id); } if (data.from_name !== undefined) { updateData.sender.name = data.from_name; } } // Reply-to email if (data.reply_to !== undefined) { updateData.reply_to_email = data.reply_to; } // Tracking - structured object if provided if (data.tracking !== undefined) { updateData.tracking = data.tracking; } // Content - structured according to PatchCampaignContent schema const contentFields = ['subject', 'html_content', 'text_content', 'json_content', 'template_id', 'blueprint_id', 'content_type', 'encoding', 'default_unsubscribe_link']; const hasContentUpdate = contentFields.some(field => data[field] !== undefined); if (hasContentUpdate) { updateData.content = {}; if (data.subject !== undefined) { updateData.content.subject = data.subject; } if (data.html_content !== undefined) { updateData.content.html = data.html_content; } if (data.text_content !== undefined) { updateData.content.text = data.text_content; } // JSON content for BEE editor if (data.json_content !== undefined) { updateData.content.json = data.json_content; updateData.content.type = 'bee'; } // Template reference if (data.template_id !== undefined) { updateData.content.template = { id: parseInt(String(data.template_id)) }; } // Blueprint reference if (data.blueprint_id !== undefined) { updateData.content.blueprint = { id: parseInt(String(data.blueprint_id)) }; } // Content type if (data.content_type !== undefined) { updateData.content.type = data.content_type; } else if (data.html_content !== undefined && !data.json_content) { updateData.content.type = 'html'; } // Encoding if (data.encoding !== undefined) { updateData.content.encoding = data.encoding; } // Default unsubscribe link if (data.default_unsubscribe_link !== undefined) { updateData.content.default_unsubscribe_link = data.default_unsubscribe_link; } } // Use explicit account_id if provided, otherwise get current account ID const accountId = data.account_id || await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}${query}`, { method: 'PATCH', body: JSON.stringify(updateData) }); } async deleteCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}${query}`, { method: 'DELETE' }); } // Campaign rendering (preview) async renderCampaign(id, options) { const accountId = options?.account_id || await this.getCurrentAccountId(); const params = {}; if (options?.contact_id) params.contact_id = options.contact_id; if (accountId) params.account_id = accountId; const query = Object.keys(params).length > 0 ? `?${new URLSearchParams(params)}` : ''; return this.makeRequest(`/campaigns/${id}/render${query}`); } // Send test email async sendTestEmail(id, data) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/send-test${query}`, { method: 'POST', body: JSON.stringify(data) }); } // Campaign scheduling operations async scheduleCampaign(id, data) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; const requestOptions = { method: 'POST' }; if (data) { requestOptions.body = JSON.stringify(data); } return this.makeRequest(`/campaigns/${id}/schedule${query}`, requestOptions); } async unscheduleCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/unschedule${query}`, { method: 'POST' }); } async rescheduleCampaign(id, data) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/reschedule${query}`, { method: 'POST', body: JSON.stringify(data) }); } // Campaign control operations async suspendCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/suspend${query}`, { method: 'POST' }); } async resumeCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/resume${query}`, { method: 'POST' }); } async cancelCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/cancel${query}`, { method: 'POST' }); } // Campaign archiving operations async archiveCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/archive${query}`, { method: 'POST' }); } async unarchiveCampaign(id) { const accountId = await this.getCurrentAccountId(); const query = accountId ? `?account_id=${accountId}` : ''; return this.makeRequest(`/campaigns/${id}/unarchive${query}`, { method: 'POST' }); } // Campaign revisions async getCampaignRevisions(id, params) { const accountId = await this.getCurrentAccountId(); const apiParams = { page: params?.page || 1, per_page: params?.per_page || 50, with_count: params?.with_count !== false }; if (accountId) apiParams.account_id = accountId; const query = `?${new URLSearchParams(apiParams)}`; return this.makeRequest(`/campaigns/${id}/revisions${query}`); } // Campaign links async getCampaignLinks(id, params) { const accountId = await this.getCurrentAccountId(); const apiParams = { page: params?.page || 1, per_page: params?.per_page || 50, with_count: params?.with_count !== false }; if (accountId) apiParams.account_id = accountId; const query = `?${new URLSearchParams(apiParams)}`; return this.makeRequest(`/campaigns/${id}/links${query}`); } // Debug method to test different campaign access patterns async debugCampaignAccess(campaignId) { const results = { timestamp: new Date().toISOString(), tests: [] }; // Test 1: Get campaigns with default parameters try { const campaigns = await this.getCampaigns(); results.tests.push({ test: 'default-campaigns', success: true, campaignCount: Array.isArray(campaigns.data) ? campaigns.data.length : 0, firstCampaignId: Array.isArray(campaigns.data) && campaigns.data.length > 0 ? campaigns.data[0].id : null }); } catch (error) { results.tests.push({ test: 'default-campaigns', success: false, error: error.message }); } // Test 2: Get campaigns with explicit account ID try { const accountId = await this.getCurrentAccountId(); if (accountId) { const campaigns = await this.getCampaigns({ account_id: accountId }); results.tests.push({ test: 'account-scoped-campaigns', success: true, accountId, campaignCount: Array.isArray(campaigns.data) ? campaigns.data.length : 0, firstCampaignId: Array.isArray(campaigns.data) && campaigns.data.length > 0 ? campaigns.data[0].id : null }); } } catch (error) { results.tests.push({ test: 'account-scoped-campaigns', success: false, error: error.message }); } // Test 3: Get specific campaign if ID provided if (campaignId) { try { const campaign = await this.getCampaign(campaignId); results.tests.push({ test: 'specific-campaign', success: true, campaignId, found: !!campaign.data }); } catch (error) { results.tests.push({ test: 'specific-campaign', success: false, campaignId, error: error.message }); } } return results; } } //# sourceMappingURL=campaign-api.js.map