@profullstack/lead-generator
Version:
A CLI tool for sending mass lead emails to prospective clients from CSV files with AI-powered personalization
421 lines (371 loc) • 12.1 kB
JavaScript
/**
* @profullstack/lead-generator - Main module export
* Lead generation tool for mass email campaigns with AI personalization
*/
// Core modules
export {
parseCSV,
validateLeadData,
extractEmails,
processLeadsFromCSV,
getLeadStats
} from './src/csv-parser.js';
export {
AIService,
personalizeTemplate,
generatePlaceholderValues,
batchPersonalize,
getPersonalizationStats
} from './src/ai-service.js';
export {
EmailService,
sendEmail,
sendBatchEmails,
createEmailFromTemplate,
getEmailStats,
validateMailgunConfig
} from './src/email-service.js';
export {
VoiceService,
makeVoiceCall,
emailToVoiceScript,
isValidPhoneNumber,
formatPhoneNumber,
getVoiceServiceStatus
} from './src/voice-service.js';
// Email Templates
export {
templates,
templatesByCategory,
templatesByTone,
getTemplateById,
getTemplatesByCategory,
getTemplatesByTone,
getRandomTemplate,
getAllTemplateIds,
getAllCategories,
getAllTones
} from './src/email-templates/index.js';
// Import for internal use
import {
templates as emailTemplates,
getTemplateById as getEmailTemplateById,
getAllTemplateIds as getAllEmailTemplateIds
} from './src/email-templates/index.js';
// SMS Templates and Services
export {
smsTemplates,
smsTemplatesByCategory,
smsTemplatesByTone,
getSmsTemplateById,
getSmsTemplatesByCategory,
getSmsTemplatesByTone,
getRandomSmsTemplate,
getAllSmsTemplateIds,
getAllSmsCategories,
getAllSmsTones,
personalizeSmsTemplate
} from './src/sms-templates/index.js';
export {
SmsService,
createSmsFromTemplate,
sendBatchSmsFromTemplates
} from './src/sms-service.js';
/**
* Lead Generator class - Main orchestrator
*/
export class LeadGenerator {
constructor(config = {}) {
this.config = {
// Mailgun configuration
mailgun: {
apiKey: config.mailgunApiKey || process.env.MAILGUN_API_KEY,
domain: config.mailgunDomain || process.env.MAILGUN_DOMAIN,
baseUrl: config.mailgunBaseUrl || process.env.MAILGUN_BASE_URL
},
// OpenAI configuration
openai: {
apiKey: config.openaiApiKey || process.env.OPENAI_API_KEY,
model: config.openaiModel || process.env.OPENAI_MODEL || 'gpt-4o-mini',
maxTokens: config.openaiMaxTokens || parseInt(process.env.OPENAI_MAX_TOKENS) || 500,
temperature: config.openaiTemperature || parseFloat(process.env.OPENAI_TEMPERATURE) || 0.7
},
// Sender information
sender: {
name: config.senderName || process.env.DEFAULT_FROM_NAME,
email: config.senderEmail || process.env.DEFAULT_FROM_EMAIL,
title: config.senderTitle || 'Sales Director',
replyTo: config.replyTo || process.env.DEFAULT_REPLY_TO
},
// Campaign settings
campaign: {
batchSize: config.batchSize || parseInt(process.env.BATCH_SIZE) || 300,
delay: config.delay || parseInt(process.env.BATCH_DELAY_MS) || 1000,
maxRetries: config.maxRetries || parseInt(process.env.MAX_RETRIES) || 3,
enablePersonalization: config.enablePersonalization !== false,
trackOpens: config.trackOpens !== false,
trackClicks: config.trackClicks !== false
},
...config
};
}
/**
* Run complete lead generation campaign
* @param {string} csvFilePath - Path to CSV file with leads
* @param {Object} options - Campaign options
* @returns {Promise<Object>} Campaign results
*/
async runCampaign(csvFilePath, options = {}) {
const campaignOptions = { ...this.config.campaign, ...options };
const results = {
timestamp: new Date().toISOString(),
csvFile: csvFilePath,
config: campaignOptions
};
try {
// Step 1: Process CSV file
console.log('📄 Processing CSV file...');
const csvResults = await processLeadsFromCSV(csvFilePath);
results.csvProcessing = csvResults;
if (csvResults.validLeads.length === 0) {
throw new Error('No valid leads found in CSV file');
}
// Step 2: Select templates
let selectedTemplates = options.templates || emailTemplates;
if (options.templateId) {
const template = getEmailTemplateById(options.templateId);
if (!template) {
throw new Error(`Template not found: ${options.templateId}`);
}
selectedTemplates = [template];
}
// Step 3: Personalize emails
console.log('🤖 Personalizing emails...');
const personalizationResults = await batchPersonalize(
selectedTemplates,
csvResults.validLeads,
this.config.sender,
{
...this.config.openai,
fallbackToBasic: true,
batchSize: 10,
delay: 500
}
);
results.personalization = getPersonalizationStats(personalizationResults);
// Step 4: Create email data
const emailsToSend = personalizationResults
.filter(r => r.success)
.map(result => createEmailFromTemplate(
result.email,
result.lead,
this.config.sender,
{
trackOpens: campaignOptions.trackOpens,
trackClicks: campaignOptions.trackClicks
}
));
// Step 5: Send emails
console.log('📧 Sending emails...');
const sendResults = await sendBatchEmails(emailsToSend, {
...this.config.mailgun,
batchSize: campaignOptions.batchSize,
delay: campaignOptions.delay,
maxRetries: campaignOptions.maxRetries,
dryRun: campaignOptions.dryRun
});
results.sending = sendResults;
// Step 6: Generate final statistics
results.summary = {
totalLeads: csvResults.stats.total,
validLeads: csvResults.stats.valid,
emailsSent: sendResults.successful,
emailsFailed: sendResults.failed,
successRate: sendResults.successRate,
personalizationRate: results.personalization.aiEnhancementRate
};
return results;
} catch (error) {
results.error = error.message;
throw error;
}
}
/**
* Validate configuration
* @returns {Object} Validation result
*/
validateConfig() {
const errors = [];
// Check Mailgun config
if (!this.config.mailgun.apiKey) {
errors.push('Mailgun API key is required');
}
if (!this.config.mailgun.domain) {
errors.push('Mailgun domain is required');
}
// Check sender config
if (!this.config.sender.email) {
errors.push('Sender email is required');
}
if (!this.config.sender.name) {
errors.push('Sender name is required');
}
// OpenAI is optional but warn if personalization is enabled
if (this.config.campaign.enablePersonalization && !this.config.openai.apiKey) {
errors.push('OpenAI API key is required for AI personalization (or disable personalization)');
}
return {
isValid: errors.length === 0,
errors,
warnings: []
};
}
/**
* Get available templates
* @param {Object} filters - Template filters
* @returns {Array} Filtered templates
*/
getTemplates(filters = {}) {
let filteredTemplates = emailTemplates;
if (filters.category) {
filteredTemplates = getTemplatesByCategory(filters.category);
}
if (filters.tone) {
filteredTemplates = getTemplatesByTone(filters.tone);
}
return filteredTemplates;
}
/**
* Preview personalized email
* @param {string} templateId - Template ID
* @param {Object} leadData - Sample lead data
* @returns {Promise<Object>} Personalized email preview
*/
async previewEmail(templateId, leadData) {
const template = getEmailTemplateById(templateId);
if (!template) {
throw new Error(`Template not found: ${templateId}`);
}
return await personalizeTemplate(
template,
leadData,
this.config.sender,
{
...this.config.openai,
fallbackToBasic: true
}
);
}
}
/**
* Quick start function for simple campaigns
* @param {string} csvFilePath - Path to CSV file
* @param {Object} config - Configuration object
* @returns {Promise<Object>} Campaign results
*/
export async function quickStart(csvFilePath, config = {}) {
const generator = new LeadGenerator(config);
// Validate configuration
const validation = generator.validateConfig();
if (!validation.isValid) {
throw new Error(`Configuration invalid: ${validation.errors.join(', ')}`);
}
return await generator.runCampaign(csvFilePath, config);
}
/**
* Utility function to create sample CSV
* @param {string} filePath - Output file path
* @param {number} count - Number of sample leads
*/
export function createSampleCSV(filePath, count = 5) {
const sampleData = [
'FirstName,LastName,Company,WorkEmail,PersonalEmail,Phone,Industry,Title',
'John,Doe,Acme Corp,john.doe@acme.com,john@personal.com,555-0123,Technology,CTO',
'Jane,Smith,Beta Inc,jane.smith@beta.com,,555-0456,Healthcare,VP Engineering',
'Bob,Johnson,Gamma LLC,,bob@gmail.com,555-0789,Finance,Director',
'Alice,Williams,Delta Co,alice@delta.co,alice.w@email.com,555-0321,Marketing,Manager',
'Charlie,Brown,Echo Ltd,charlie.brown@echo.ltd,,555-0654,Consulting,Partner'
];
const csvContent = sampleData.slice(0, count + 1).join('\n');
import('fs').then(fs => {
fs.writeFileSync(filePath, csvContent);
console.log(`Sample CSV created: ${filePath}`);
});
}
// Voice Templates
export {
voiceTemplates,
voiceTemplatesByCategory,
voiceTemplatesByTone,
getVoiceTemplateById,
getVoiceTemplatesByCategory,
getVoiceTemplatesByTone,
getRandomVoiceTemplate,
getAllVoiceTemplateIds,
getAllVoiceCategories,
getAllVoiceTones
} from './src/voice-templates/index.js';
// CSV Status Tracker
export {
addStatusColumns,
updateCSVStatus,
getStatusFromCSV,
createStatusBackup,
getContactsByStatus,
EMAIL_STATUSES as EMAIL_STATUS,
VOICE_STATUSES as VOICE_STATUS,
SMS_STATUS
} from './src/csv-status-tracker.js';
// Convenience functions for SMS lead generation
export async function generateSmsLeads(csvFilePath, options = {}) {
const { SmsService } = await import('./src/sms-service.js');
const { processLeadsFromCSV } = await import('./src/csv-parser.js');
const { smsTemplates, getSmsTemplateById } = await import('./src/sms-templates/index.js');
const smsService = new SmsService(options.twilio || {});
const csvResults = await processLeadsFromCSV(csvFilePath);
let selectedTemplates = options.templates || smsTemplates;
if (options.templateId) {
const template = getSmsTemplateById(options.templateId);
if (!template) {
throw new Error(`SMS template not found: ${options.templateId}`);
}
selectedTemplates = [template];
}
return await smsService.sendBatchFromTemplates(
selectedTemplates,
csvResults.validLeads,
options
);
}
// Convenience functions for voice lead generation
export async function generateVoiceLeads(csvFilePath, options = {}) {
const { VoiceService } = await import('./src/voice-service.js');
const { processLeadsFromCSV } = await import('./src/csv-parser.js');
const { voiceTemplates, getVoiceTemplateById } = await import('./src/voice-templates/index.js');
const voiceService = new VoiceService(options.twilio || {});
const csvResults = await processLeadsFromCSV(csvFilePath);
let selectedTemplates = options.templates || voiceTemplates;
if (options.templateId) {
const template = getVoiceTemplateById(options.templateId);
if (!template) {
throw new Error(`Voice template not found: ${options.templateId}`);
}
selectedTemplates = [template];
}
return await voiceService.makeBatchCalls(
selectedTemplates,
csvResults.validLeads,
options
);
}
// Default export
export default {
LeadGenerator,
quickStart,
createSampleCSV,
templates: emailTemplates,
getTemplateById: getEmailTemplateById,
getAllTemplateIds: getAllEmailTemplateIds,
generateSmsLeads,
generateVoiceLeads
};