filter museums with email
This commit is contained in:
@ -27,22 +27,26 @@ export async function sendEmail(
|
|||||||
throw new Error(`Customer with ID ${customerId} not found`);
|
throw new Error(`Customer with ID ${customerId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Gmail transporter
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
service: 'gmail',
|
host: 'smtp.gmail.com',
|
||||||
|
port: 587, // TLS port
|
||||||
|
secure: false, // true for 465 (SSL), false for other ports like 587 (TLS)
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.GMAIL_USER,
|
user: process.env.GMAIL_USER || 'ken@catsai-agency.com',
|
||||||
pass: process.env.GMAIL_APP_PASSWORD // Using app-specific password
|
pass: process.env.GMAIL_APP_PASSWORD || 'xkptiigonuhxkgma', // "xkpt iigo nuhx kgma"
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
// Do not fail on invalid certificates
|
||||||
|
rejectUnauthorized: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
const info = await transporter.sendMail({
|
const info = await transporter.sendMail({
|
||||||
from: process.env.GMAIL_USER,
|
from: process.env.GMAIL_USER || 'ken@catsai-agency.com',
|
||||||
to: customer.email,
|
to: customer.email,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: body,
|
text: body
|
||||||
html: htmlBody || body
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create contact record
|
// Create contact record
|
||||||
@ -50,7 +54,7 @@ export async function sendEmail(
|
|||||||
contactRecord.customer = customer;
|
contactRecord.customer = customer;
|
||||||
contactRecord.customerId = customer.id;
|
contactRecord.customerId = customer.id;
|
||||||
contactRecord.contactType = 'EMAIL';
|
contactRecord.contactType = 'EMAIL';
|
||||||
contactRecord.notes = `Email sent successfully. Subject: ${subject}. Message ID: ${info.messageId}`;
|
contactRecord.notes = `Email sent successfully. Subject: ${subject}. Body: ${body}`;
|
||||||
|
|
||||||
// Save contact record
|
// Save contact record
|
||||||
await dataSource.getRepository(ContactRecord).save(contactRecord);
|
await dataSource.getRepository(ContactRecord).save(contactRecord);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ const model = modelArg
|
|||||||
? modelArg.split('=')[1]
|
? modelArg.split('=')[1]
|
||||||
: 'local-model'; // Default model name
|
: 'local-model'; // Default model name
|
||||||
const temperature = temperatureArg
|
const temperature = temperatureArg
|
||||||
? parseFloat(temperatureArg.split('=')[1])
|
? parseFloat(temperatureArg.split('=')[0.3])
|
||||||
: 0.7; // Default temperature
|
: 0.7; // Default temperature
|
||||||
|
|
||||||
// Dry run mode - generate content but don't send emails or create records
|
// Dry run mode - generate content but don't send emails or create records
|
||||||
@ -51,14 +51,25 @@ async function generateEmailContent(
|
|||||||
// Create a prompt based on customer info and email count
|
// Create a prompt based on customer info and email count
|
||||||
let prompt = '';
|
let prompt = '';
|
||||||
if (emailCount === 0) {
|
if (emailCount === 0) {
|
||||||
prompt = `Generate a welcome email for a new customer named ${customer.name} from ${customer.city}.
|
prompt = `Please generate cold email to promote custom generative AI video service.
|
||||||
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
|
Customer name : ${customer.name}
|
||||||
1. This email is proposal for custom AI video generation.
|
Customer city: ${customer.city}
|
||||||
2. Please includde your prposal for the simple video for the museum.
|
|
||||||
3. Please use the language of the city ${customer.city}`;
|
Give me in following format { subject: "Short subject in local language" body: "Short email body in local language in plain text format" }
|
||||||
|
|
||||||
|
Please start with like "Hi I'm Ken from Cat's AI Ltd"
|
||||||
|
Please make short proposal how generative AI video can help to the museum
|
||||||
|
Please say something specific for the museum or the city
|
||||||
|
Subject should be something like "AI video for museums". 10 words max.
|
||||||
|
Please add in the end this email is generated by AI so please forgive me if something is wrong.
|
||||||
|
|
||||||
|
Please be short and clear
|
||||||
|
Please translate to local language.
|
||||||
|
Please include these sample videos to the email
|
||||||
|
https://www.youtube.com/watch?v=bfYzRBJEI7A
|
||||||
|
https://www.youtube.com/watch?v=XLI1oyy3TF0
|
||||||
|
https://www.youtube.com/watch?v=fX4BnZaDCxg`;
|
||||||
}/* else if (emailCount === 1) {
|
}/* else if (emailCount === 1) {
|
||||||
prompt = `Generate a follow-up email for customer ${customer.name} from ${customer.url}.
|
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.
|
This is the second contact with them. The email should reference a previous welcome email and provide more value.
|
||||||
@ -75,11 +86,11 @@ async function generateEmailContent(
|
|||||||
{
|
{
|
||||||
model: model,
|
model: model,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: 'system', content: 'You are an expert email copywriter who creates engaging, professional emails.' },
|
{ role: 'system', content: 'You are an expert email copywriter who creates engaging, professional emails. Output should be only JSON' },
|
||||||
{ role: 'user', content: prompt }
|
{ role: 'user', content: prompt }
|
||||||
],
|
],
|
||||||
temperature: temperature,
|
temperature: temperature,
|
||||||
max_tokens: 1000
|
max_tokens: 500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@ -115,8 +126,8 @@ async function generateEmailContent(
|
|||||||
|
|
||||||
// Fallback content
|
// Fallback content
|
||||||
return {
|
return {
|
||||||
subject: `Update from Our Company - Contact #${emailCount + 1}`,
|
subject: `Enhance Engagement with AI-Powered Video`,
|
||||||
body: `<h1>Hello ${customer.name}!</h1><p>Thank you for your continued interest in our services. We appreciate your business.</p>`
|
body: `Hi\n\nI'm Ken from Cat's AI Ltd.\n\nWe help museums like yours create engaging video content using cutting-edge generative AI. Imagine bringing historical artifacts to life, creating immersive virtual tours, or developing captivating educational videos – all without extensive production costs.\n\nGiven the rich cultural landscape of the USA and Default Museum’s important role within it, we believe AI video could significantly boost visitor engagement and online reach.\n\nHere are a few examples of what's possible:\nhttps://www.youtube.com/watch?v=bfYzRBJEI7A\nhttps://www.youtube.com/watch?v=XLI1oyy3TF0\nhttps://www.youtube.com/watch?v=fX4BnZaDCxg\n\nWould you be open to a quick chat about how AI video can benefit Default Museum?\n\nBest regards,\nKen (Cat's AI Ltd)`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -128,8 +139,8 @@ async function generateEmailContent(
|
|||||||
|
|
||||||
// Fallback content in case of API error
|
// Fallback content in case of API error
|
||||||
return {
|
return {
|
||||||
subject: `Important Update - Contact #${emailCount + 1}`,
|
subject: `Enhance Engagement with AI-Powered Video`,
|
||||||
body: `<h1>Hello ${customer.name}!</h1><p>Thank you for your interest in our services. We look forward to working with you!</p>`
|
body: `Hi\n\nI'm Ken from Cat's AI Ltd.\n\nWe help museums like yours create engaging video content using cutting-edge generative AI. Imagine bringing historical artifacts to life, creating immersive virtual tours, or developing captivating educational videos – all without extensive production costs.\n\nGiven the rich cultural landscape of the USA and Default Museum’s important role within it, we believe AI video could significantly boost visitor engagement and online reach.\n\nHere are a few examples of what's possible:\nhttps://www.youtube.com/watch?v=bfYzRBJEI7A\nhttps://www.youtube.com/watch?v=XLI1oyy3TF0\nhttps://www.youtube.com/watch?v=fX4BnZaDCxg\n\nWould you be open to a quick chat about how AI video can benefit Default Museum?\n\nBest regards,\nKen (Cat's AI Ltd)`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +165,9 @@ async function main() {
|
|||||||
'contact.customerId = customer.id AND contact.contactType = :contactType',
|
'contact.customerId = customer.id AND contact.contactType = :contactType',
|
||||||
{ contactType: 'EMAIL' }
|
{ contactType: 'EMAIL' }
|
||||||
)
|
)
|
||||||
.where('contact.id IS NULL');
|
.where('contact.id IS NULL')
|
||||||
|
.andWhere('customer.email IS NOT NULL')
|
||||||
|
.andWhere('customer.email != :emptyEmail', { emptyEmail: '' });
|
||||||
} else {
|
} else {
|
||||||
// Find customers with exactly N email contact records
|
// Find customers with exactly N email contact records
|
||||||
customersQuery = customersQuery
|
customersQuery = customersQuery
|
||||||
@ -164,6 +177,8 @@ async function main() {
|
|||||||
'contact.customerId = customer.id AND contact.contactType = :contactType',
|
'contact.customerId = customer.id AND contact.contactType = :contactType',
|
||||||
{ contactType: 'EMAIL' }
|
{ contactType: 'EMAIL' }
|
||||||
)
|
)
|
||||||
|
.where('customer.email IS NOT NULL')
|
||||||
|
.andWhere('customer.email != :emptyEmail', { emptyEmail: '' })
|
||||||
.groupBy('customer.id')
|
.groupBy('customer.id')
|
||||||
.having('COUNT(contact.id) = :count', { count: emailCount });
|
.having('COUNT(contact.id) = :count', { count: emailCount });
|
||||||
}
|
}
|
||||||
@ -209,7 +224,6 @@ async function main() {
|
|||||||
customer.id,
|
customer.id,
|
||||||
emailContent.subject,
|
emailContent.subject,
|
||||||
emailContent.body.replace(/<[^>]*>/g, ''), // Plain text version (strip HTML)
|
emailContent.body.replace(/<[^>]*>/g, ''), // Plain text version (strip HTML)
|
||||||
emailContent.body // HTML version
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@ -1,126 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
// Get command line arguments
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
const emailCountArg = args.find(arg => arg.startsWith('--count='));
|
|
||||||
const defaultEmailCount = 0;
|
|
||||||
const emailCount = emailCountArg
|
|
||||||
? parseInt(emailCountArg.split('=')[1], 10)
|
|
||||||
: defaultEmailCount;
|
|
||||||
|
|
||||||
if (isNaN(emailCount) || emailCount < 0) {
|
|
||||||
console.error('Error: Email count must be a non-negative number');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
let emailSubject = 'Welcome to Our Service';
|
|
||||||
let emailBody = 'Thank you for your interest in our services. We look forward to working with you!';
|
|
||||||
|
|
||||||
// Customize email based on email count
|
|
||||||
if (emailCount === 0) {
|
|
||||||
emailSubject = 'Welcome to Our Service';
|
|
||||||
emailBody = 'Thank you for your interest in our services. We look forward to working with you!';
|
|
||||||
} else if (emailCount === 1) {
|
|
||||||
emailSubject = 'Follow-up on Our Services';
|
|
||||||
emailBody = 'We hope you found our previous information helpful. Here are some additional details about our services.';
|
|
||||||
} else {
|
|
||||||
emailSubject = `Important Update - Contact #${emailCount + 1}`;
|
|
||||||
emailBody = 'We wanted to share some important updates about our services that might interest you.';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const customer of filteredCustomers) {
|
|
||||||
console.log(`Sending email to ${customer.name} (${customer.email})...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await sendEmail(
|
|
||||||
customer.id,
|
|
||||||
emailSubject,
|
|
||||||
emailBody,
|
|
||||||
`<h1>Welcome ${customer.name}!</h1><p>${emailBody}</p>`
|
|
||||||
);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
console.error(`❌ Error sending email to ${customer.email}:`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
console.error('An error occurred:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the main function
|
|
||||||
main().catch(error => {
|
|
||||||
console.error('Unhandled error:', error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user