Initial commit
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
const config = require("../config");
|
||||
const nodemailer = require("nodemailer");
|
||||
const delay = require("delay");
|
||||
const fs = require("fs");
|
||||
const templateEngine = require("../lib/templateEngine");
|
||||
const attachmentHandler = require("../lib/attachmentHandler");
|
||||
const rateLimiter = require("../lib/rateLimiter");
|
||||
const errorHandler = require("../lib/errorHandler");
|
||||
const logger = require("../lib/logger");
|
||||
const database = require("../lib/database");
|
||||
const trackingServer = require("../lib/trackingServer");
|
||||
|
||||
// Extract email sending logic for reuse
|
||||
async function sendSingleEmail(mailOptions, recipient, transporter) {
|
||||
if (config.logging.debug) {
|
||||
console.log(`🐛 DEBUG: [Campaign] Sending email to ${recipient}`);
|
||||
console.log(`🐛 DEBUG: [Campaign] Subject: ${mailOptions.subject}`);
|
||||
console.log(`🐛 DEBUG: [Campaign] Firm: ${mailOptions.firmName}`);
|
||||
console.log(`🐛 DEBUG: [Campaign] Template: ${mailOptions.templateName}`);
|
||||
console.log(`🐛 DEBUG: [Campaign] Tracking ID: ${mailOptions.trackingId}`);
|
||||
}
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
// Log successful email
|
||||
logger.emailSent(
|
||||
recipient,
|
||||
mailOptions.subject,
|
||||
mailOptions.firmName,
|
||||
config.email.testMode
|
||||
);
|
||||
|
||||
console.log(
|
||||
`✅ Email sent to ${recipient}${
|
||||
config.email.testMode ? " (TEST MODE)" : ""
|
||||
}`
|
||||
);
|
||||
|
||||
if (config.logging.debug) {
|
||||
console.log(
|
||||
`🐛 DEBUG: [Campaign] Email successfully delivered to ${recipient}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate test recipients automatically based on campaign count
|
||||
function generateCampaignTestRecipients(campaignCount) {
|
||||
const baseEmail = config.email.user; // Use your own email as base
|
||||
const [localPart, domain] = baseEmail.split("@");
|
||||
|
||||
const recipients = [];
|
||||
for (let i = 1; i <= campaignCount; i++) {
|
||||
// Create test recipients like: yourname+campaign1@gmail.com, yourname+campaign2@gmail.com
|
||||
recipients.push(`${localPart}+campaign${i}@${domain}`);
|
||||
}
|
||||
|
||||
if (config.logging.debug) {
|
||||
console.log(
|
||||
`🐛 DEBUG: Generated ${campaignCount} campaign test recipients:`
|
||||
);
|
||||
recipients.forEach((email, index) => {
|
||||
console.log(`🐛 DEBUG: Campaign ${index + 1}: ${email}`);
|
||||
});
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
|
||||
async function runCampaigns() {
|
||||
try {
|
||||
// Initialize database
|
||||
await database.init();
|
||||
|
||||
// Load campaign test data
|
||||
const testDataFile = config.campaigns.testDataFile;
|
||||
if (!fs.existsSync(testDataFile)) {
|
||||
throw new Error(`Campaign test data file not found: ${testDataFile}`);
|
||||
}
|
||||
|
||||
const testData = JSON.parse(fs.readFileSync(testDataFile, "utf8"));
|
||||
let campaigns = testData.TestCampaigns || [];
|
||||
|
||||
if (campaigns.length === 0) {
|
||||
throw new Error("No campaigns found in test data file");
|
||||
}
|
||||
|
||||
// Generate test recipients if in test mode
|
||||
if (config.email.testMode) {
|
||||
const testRecipients = generateCampaignTestRecipients(campaigns.length);
|
||||
|
||||
// Update campaigns with generated test recipients
|
||||
campaigns = campaigns.map((campaign, index) => ({
|
||||
...campaign,
|
||||
contactEmail: testRecipients[index] || campaign.contactEmail,
|
||||
}));
|
||||
|
||||
console.log(
|
||||
`🧪 TEST MODE: Updated ${campaigns.length} campaigns with auto-generated recipients`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`🚀 Running ${campaigns.length} different campaigns`);
|
||||
|
||||
if (config.logging.debug) {
|
||||
console.log(
|
||||
`🐛 DEBUG: [Campaign] Loaded campaigns from: ${testDataFile}`
|
||||
);
|
||||
campaigns.forEach((campaign, index) => {
|
||||
console.log(
|
||||
`🐛 DEBUG: [Campaign ${index + 1}] ${campaign.campaign} → ${
|
||||
campaign.firmName
|
||||
} → ${campaign.contactEmail}`
|
||||
);
|
||||
console.log(
|
||||
`🐛 DEBUG: [Campaign ${index + 1}] Subject: ${campaign.subject}`
|
||||
);
|
||||
console.log(
|
||||
`🐛 DEBUG: [Campaign ${index + 1}] Template: ${campaign.campaign}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Start tracking server if enabled and not skipping tracking
|
||||
if (config.tracking.enabled && !config.tracking.skipTracking) {
|
||||
try {
|
||||
await trackingServer.start();
|
||||
} catch (error) {
|
||||
logger.error("Failed to start tracking server", {
|
||||
error: error.message,
|
||||
});
|
||||
console.warn(
|
||||
"⚠️ Tracking server failed to start. Continuing without tracking."
|
||||
);
|
||||
}
|
||||
} else if (config.tracking.skipTracking) {
|
||||
console.log("📊 Tracking disabled - SKIP_TRACKING enabled");
|
||||
}
|
||||
|
||||
// Setup email transporter
|
||||
const transportConfig = {
|
||||
auth: {
|
||||
user: config.smtp.user || config.email.user,
|
||||
pass: config.smtp.pass || config.email.pass,
|
||||
},
|
||||
};
|
||||
|
||||
if (config.smtp.host) {
|
||||
transportConfig.host = config.smtp.host;
|
||||
transportConfig.port = config.smtp.port;
|
||||
transportConfig.secure = config.smtp.secure;
|
||||
} else {
|
||||
transportConfig.service = "gmail";
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport(transportConfig);
|
||||
|
||||
// Get attachments once at start
|
||||
const attachments = await attachmentHandler.getAttachments();
|
||||
if (attachments.length > 0) {
|
||||
console.log(`📎 Attaching ${attachments.length} file(s) to emails`);
|
||||
}
|
||||
|
||||
// Process each campaign
|
||||
for (let i = 0; i < campaigns.length; i++) {
|
||||
const campaign = campaigns[i];
|
||||
console.log(
|
||||
`\n📧 Campaign ${i + 1}/${campaigns.length}: ${campaign.campaign}`
|
||||
);
|
||||
console.log(` Firm: ${campaign.firmName}`);
|
||||
console.log(` Template: ${campaign.campaign}`);
|
||||
console.log(` Subject: ${campaign.subject}`);
|
||||
|
||||
// Create campaign in database
|
||||
const campaignId = await database.createCampaign({
|
||||
name: `${campaign.campaign} - ${
|
||||
new Date().toISOString().split("T")[0]
|
||||
}`,
|
||||
subject: campaign.subject,
|
||||
templateName: campaign.campaign,
|
||||
testMode: config.email.testMode,
|
||||
});
|
||||
|
||||
await database.startCampaign(campaignId, 1);
|
||||
|
||||
// Format firm data for template
|
||||
const templateData = templateEngine.formatFirmData({
|
||||
firmName: campaign.firmName,
|
||||
location: campaign.location,
|
||||
website: campaign.website,
|
||||
contactEmail: campaign.contactEmail,
|
||||
email: campaign.contactEmail,
|
||||
state: campaign.state,
|
||||
});
|
||||
|
||||
// Use campaign contact email directly (contains generated test recipient in test mode)
|
||||
const recipient = campaign.contactEmail;
|
||||
|
||||
// Double check: Ensure we have a valid recipient (auto-generated in test mode)
|
||||
if (config.email.testMode && !recipient) {
|
||||
throw new Error("Test mode error: No recipient generated for campaign");
|
||||
}
|
||||
|
||||
// Log what we're doing
|
||||
if (config.email.testMode) {
|
||||
if (config.logging.debug) {
|
||||
console.log(`🐛 DEBUG: [Campaign] Campaign ${i + 1} → ${recipient}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`🧪 TEST MODE: Email for ${campaign.firmName} → ${recipient}`
|
||||
);
|
||||
}
|
||||
|
||||
// Generate unique tracking ID
|
||||
const trackingId = `${campaignId}_${campaign.contactEmail}_${Date.now()}`;
|
||||
|
||||
// Render email using template
|
||||
const emailContent = await templateEngine.render(campaign.campaign, {
|
||||
...templateData,
|
||||
subject: campaign.subject,
|
||||
});
|
||||
|
||||
// Add tracking to HTML content (unless skip tracking is enabled)
|
||||
const trackedHtmlContent = trackingServer.addEmailTracking(
|
||||
emailContent.html,
|
||||
trackingId,
|
||||
config.tracking.skipTracking
|
||||
);
|
||||
|
||||
const mailOptions = {
|
||||
from: config.email.user,
|
||||
to: recipient,
|
||||
subject: campaign.subject,
|
||||
text: emailContent.text,
|
||||
html: trackedHtmlContent,
|
||||
attachments: attachments,
|
||||
firmName: campaign.firmName,
|
||||
trackingId: trackingId,
|
||||
};
|
||||
|
||||
try {
|
||||
await sendSingleEmail(mailOptions, recipient, transporter);
|
||||
|
||||
// Record success
|
||||
rateLimiter.recordSuccess();
|
||||
|
||||
// Show progress
|
||||
const stats = rateLimiter.getStats();
|
||||
console.log(
|
||||
`📊 Progress: ${stats.sentCount} sent, ${stats.averageRate} emails/hour`
|
||||
);
|
||||
|
||||
// Complete campaign
|
||||
await database.completeCampaign(campaignId, {
|
||||
sentEmails: 1,
|
||||
failedEmails: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ Failed to send campaign ${campaign.campaign}:`,
|
||||
error.message
|
||||
);
|
||||
|
||||
// Complete campaign with failure
|
||||
await database.completeCampaign(campaignId, {
|
||||
sentEmails: 0,
|
||||
failedEmails: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Add delay between campaigns (except for the last one)
|
||||
if (i < campaigns.length - 1) {
|
||||
const delayMs = rateLimiter.getRandomDelay();
|
||||
console.log(
|
||||
`⏱️ Next campaign in: ${rateLimiter.formatDelay(delayMs)}`
|
||||
);
|
||||
|
||||
if (config.logging.debug) {
|
||||
console.log(
|
||||
`🐛 DEBUG: [Campaign] Delaying ${delayMs}ms before next campaign`
|
||||
);
|
||||
}
|
||||
|
||||
// Use setTimeout wrapped in Promise instead of delay package
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
|
||||
// Final stats
|
||||
const finalStats = rateLimiter.getStats();
|
||||
console.log(`\n📈 All campaigns complete! Final stats:`, finalStats);
|
||||
|
||||
// Stop tracking server if it was started
|
||||
if (config.tracking.enabled && !config.tracking.skipTracking) {
|
||||
try {
|
||||
await trackingServer.stop();
|
||||
} catch (error) {
|
||||
logger.error("Failed to stop tracking server", {
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Campaign runner failed:", error);
|
||||
|
||||
// Stop tracking server on error
|
||||
if (process.env.TRACKING_ENABLED === "true") {
|
||||
try {
|
||||
await trackingServer.stop();
|
||||
} catch (stopError) {
|
||||
logger.error("Failed to stop tracking server after error", {
|
||||
error: stopError.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on("SIGINT", async () => {
|
||||
console.log("\n🛑 Received SIGINT. Gracefully shutting down...");
|
||||
|
||||
if (process.env.TRACKING_ENABLED === "true") {
|
||||
try {
|
||||
await trackingServer.stop();
|
||||
} catch (error) {
|
||||
logger.error("Failed to stop tracking server during shutdown", {
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
runCampaigns().catch(console.error);
|
||||
Reference in New Issue
Block a user