Files
coldemailer/src/scripts/send-emails-to-customers.ts
2025-04-01 18:05:54 +02:00

255 lines
10 KiB
TypeScript

import 'reflect-metadata';
import { getDataSource } from '../lib/database';
import { Customer } from '../lib/database/entities/Customer';
import { ContactRecord } from '../lib/database/entities/ContactRecord';
import { sendEmail } from '../lib/email';
import axios from 'axios';
// Get command line arguments
const args = process.argv.slice(2);
const emailCountArg = args.find(arg => arg.startsWith('--count='));
const lmStudioUrlArg = args.find(arg => arg.startsWith('--lmstudio-url='));
const modelArg = args.find(arg => arg.startsWith('--model='));
const temperatureArg = args.find(arg => arg.startsWith('--temperature='));
const dryRunArg = args.find(arg => arg.startsWith('--dry-run='));
// Parse arguments with defaults
const defaultEmailCount = 0;
const emailCount = emailCountArg
? parseInt(emailCountArg.split('=')[1], 10)
: defaultEmailCount;
// LMStudio API settings
const lmStudioUrl = lmStudioUrlArg
? lmStudioUrlArg.split('=')[1]
: 'http://localhost:1234/v1/chat/completions'; // Default LMStudio API endpoint
const model = modelArg
? modelArg.split('=')[1]
: 'local-model'; // Default model name
const temperature = temperatureArg
? parseFloat(temperatureArg.split('=')[1])
: 0.7; // Default temperature
// Dry run mode - generate content but don't send emails or create records
const dryRun = dryRunArg
? dryRunArg.split('=')[1].toLowerCase() === 'true'
: false;
if (isNaN(emailCount) || emailCount < 0) {
console.error('Error: Email count must be a non-negative number');
process.exit(1);
}
// Function to generate email content using LMStudio API
async function generateEmailContent(
customer: Customer,
emailCount: number
): Promise<{ subject: string; body: string }> {
try {
console.log(`Generating email content for ${customer.name} using LMStudio API...`);
// Create a prompt based on customer info and email count
let prompt = '';
if (emailCount === 0) {
prompt = `Generate a welcome email for a new customer named ${customer.name} from ${customer.city}.
This is the first contact with them. The email should be professional but friendly.
Return ONLY a JSON object with 'subject' and 'body' fields. The body should be in HTML format.
instructions
1. This email is proposal for custom AI video generation.
2. Please includde your prposal for the simple video for the museum.
3. Please use the language of the city ${customer.city}`;
}/* else if (emailCount === 1) {
prompt = `Generate a follow-up email for customer ${customer.name} from ${customer.url}.
This is the second contact with them. The email should reference a previous welcome email and provide more value.
Return ONLY a JSON object with 'subject' and 'body' fields. The body should be in HTML format.`;
} else {
prompt = `Generate a relationship-building email for a regular customer named ${customer.name} from ${customer.url}.
This is contact number ${emailCount + 1} with them. The email should be personalized and provide specific value.
Return ONLY a JSON object with 'subject' and 'body' fields. The body should be in HTML format.`;
} */
// Call LMStudio API
const response = await axios.post(
lmStudioUrl,
{
model: model,
messages: [
{ role: 'system', content: 'You are an expert email copywriter who creates engaging, professional emails.' },
{ role: 'user', content: prompt }
],
temperature: temperature,
max_tokens: 1000
},
{
headers: {
'Content-Type': 'application/json'
}
}
);
// Parse the response to extract the JSON
const assistantMessage = response.data.choices[0].message.content;
// Try to extract JSON from the response
try {
// Look for JSON object in the response
const jsonMatch = assistantMessage.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const jsonStr = jsonMatch[0];
const emailContent = JSON.parse(jsonStr);
if (emailContent.subject && emailContent.body) {
return {
subject: emailContent.subject,
body: emailContent.body
};
}
}
// If we couldn't parse JSON or it doesn't have required fields
throw new Error('Could not parse valid JSON from LMStudio response');
} catch (parseError) {
console.warn('Failed to parse JSON from LMStudio response, using fallback content');
console.warn('LMStudio response:', assistantMessage);
// Fallback content
return {
subject: `Update from Our Company - Contact #${emailCount + 1}`,
body: `<h1>Hello ${customer.name}!</h1><p>Thank you for your continued interest in our services. We appreciate your business.</p>`
};
}
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: 'Unknown error occurred';
console.error('Error calling LMStudio API:', errorMessage);
// Fallback content in case of API error
return {
subject: `Important Update - Contact #${emailCount + 1}`,
body: `<h1>Hello ${customer.name}!</h1><p>Thank you for your interest in our services. We look forward to working with you!</p>`
};
}
}
async function main() {
try {
console.log('Initializing database connection...');
const dataSource = await getDataSource();
console.log(`Finding customers with exactly ${emailCount} email contact records...`);
let customersQuery = dataSource
.getRepository(Customer)
.createQueryBuilder('customer');
if (emailCount === 0) {
// Find customers with no email contact records
customersQuery = customersQuery
.leftJoin(
ContactRecord,
'contact',
'contact.customerId = customer.id AND contact.contactType = :contactType',
{ contactType: 'EMAIL' }
)
.where('contact.id IS NULL');
} else {
// Find customers with exactly N email contact records
customersQuery = customersQuery
.leftJoin(
ContactRecord,
'contact',
'contact.customerId = customer.id AND contact.contactType = :contactType',
{ contactType: 'EMAIL' }
)
.groupBy('customer.id')
.having('COUNT(contact.id) = :count', { count: emailCount });
}
const filteredCustomers = await customersQuery.getMany();
console.log(`Found ${filteredCustomers.length} customers with exactly ${emailCount} email contact records.`);
// Exit if no customers found
if (filteredCustomers.length === 0) {
console.log(`No customers with exactly ${emailCount} email contact records found. Exiting.`);
await dataSource.destroy();
return;
}
// Ask for confirmation before sending emails
console.log('The following customers will receive emails:');
filteredCustomers.forEach((customer, index) => {
console.log(`${index + 1}. ${customer.name} (${customer.email})`);
});
console.log('\nSending emails to customers...');
// Send emails to each customer
for (const customer of filteredCustomers) {
console.log(`Processing email for ${customer.name} (${customer.email})...`);
try {
// Generate email content using LMStudio
const emailContent = await generateEmailContent(customer, emailCount);
console.log(`Generated subject: ${emailContent.subject}`);
if (dryRun) {
console.log(`[DRY RUN] Would send email to ${customer.name} (${customer.email})`);
console.log(`[DRY RUN] Subject: ${emailContent.subject}`);
console.log(`[DRY RUN] Body: ${emailContent.body.replace(/<[^>]*>/g, '')}`);
console.log(`[DRY RUN] No email sent and no contact record created.`);
} else {
console.log(`Sending email to ${customer.name}...`);
const result = await sendEmail(
customer.id,
emailContent.subject,
emailContent.body.replace(/<[^>]*>/g, ''), // Plain text version (strip HTML)
emailContent.body // HTML version
);
if (result.success) {
console.log(`✅ Email sent successfully to ${customer.email}`);
} else {
console.error(`❌ Failed to send email to ${customer.email}: ${result.error}`);
}
}
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: 'Unknown error occurred';
console.error(`❌ Error sending email to ${customer.email}:`, errorMessage);
}
// Add a small delay between emails to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('Email sending process completed.');
await dataSource.destroy();
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: 'Unknown error occurred';
console.error('An error occurred:', errorMessage);
process.exit(1);
}
}
// Run the main function
main().catch(error => {
const errorMessage = error instanceof Error
? error.message
: 'Unknown error occurred';
console.error('Unhandled error:', errorMessage);
process.exit(1);
});