diff --git a/.vscode/launch.json b/.vscode/launch.json index b09ab45..16e8f4e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,44 +2,113 @@ "version": "0.2.0", "configurations": [ { - "name": "Next.js: debug server-side", - "type": "node-terminal", + "name": "Debug Send Emails (No Previous Emails)", + "type": "node", "request": "launch", - "command": "npm run dev", + "runtimeExecutable": "node", + "runtimeArgs": [ + "--require", + "ts-node/register" + ], + "args": [ + "${workspaceFolder}/src/scripts/send-emails-to-customers.ts", + "--count=0" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", "skipFiles": [ "/**" ], - "console": "integratedTerminal" - }, - { - "name": "Next.js: debug client-side", - "type": "chrome", - "request": "launch", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}", - "sourceMapPathOverrides": { - "webpack://_N_E/*": "${webRoot}/*" - } - }, - { - "name": "Next.js: debug full stack", - "type": "node-terminal", - "request": "launch", - "command": "npm run dev", - "serverReadyAction": { - "pattern": "started server on .+, url: (https?://.+)", - "uriFormat": "%s", - "action": "debugWithChrome" + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.scripts.json", + "NODE_ENV": "development" }, - "console": "integratedTerminal" + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] }, { - "name": "Next.js: attach to server", + "name": "Debug Send Emails (1 Previous Email)", "type": "node", - "request": "attach", - "port": 9229, + "request": "launch", + "runtimeExecutable": "node", + "runtimeArgs": [ + "--require", + "ts-node/register" + ], + "args": [ + "${workspaceFolder}/src/scripts/send-emails-to-customers.ts", + "--count=1" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", "skipFiles": [ "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.scripts.json", + "NODE_ENV": "development" + }, + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] + }, + { + "name": "Debug Send Emails (With LMStudio Config)", + "type": "node", + "request": "launch", + "runtimeExecutable": "node", + "runtimeArgs": [ + "--require", + "ts-node/register" + ], + "args": [ + "${workspaceFolder}/src/scripts/send-emails-to-customers.ts", + "--count=0", + "--lmstudio-url=http://localhost:1234/v1/chat/completions", + "--model=local-model", + "--temperature=0.7" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.scripts.json", + "NODE_ENV": "development", + "GMAIL_USER": "your.email@gmail.com", + "GMAIL_APP_PASSWORD": "your-app-password" + }, + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] + }, + { + "name": "Debug Send Emails (Dry Run)", + "type": "node", + "request": "launch", + "runtimeExecutable": "node", + "runtimeArgs": [ + "--require", + "ts-node/register" + ], + "args": [ + "${workspaceFolder}/src/scripts/send-emails-to-customers.ts", + "--count=0", + "--dry-run=true" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.scripts.json", + "NODE_ENV": "development" + }, + "outFiles": [ + "${workspaceFolder}/**/*.js" ] } ] diff --git a/package-lock.json b/package-lock.json index ec5e288..c0da8dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,9 @@ "@editorjs/marker": "^1.4.0", "@editorjs/paragraph": "^2.11.7", "@editorjs/quote": "^2.7.6", + "@types/axios": "^0.14.4", "@types/nodemailer": "^6.4.17", + "axios": "^1.8.4", "bcrypt": "^5.1.1", "csv-parse": "^5.6.0", "jsonwebtoken": "^9.0.2", @@ -1499,6 +1501,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/axios": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.4.tgz", + "integrity": "sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g==", + "deprecated": "This is a stub types definition. axios provides its own type definitions, so you do not need this installed.", + "dependencies": { + "axios": "*" + } + }, "node_modules/@types/bcrypt": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", @@ -2335,6 +2346,11 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2370,6 +2386,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2570,7 +2596,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2735,6 +2760,17 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2930,6 +2966,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -2992,7 +3036,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3144,7 +3187,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3154,7 +3196,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3192,7 +3233,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3205,7 +3245,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3836,6 +3875,25 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -3880,6 +3938,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3908,7 +3980,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3988,7 +4059,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4013,7 +4083,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -4128,7 +4197,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4207,7 +4275,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4220,7 +4287,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4242,7 +4308,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5493,7 +5558,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5523,6 +5587,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -6537,6 +6620,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", diff --git a/package.json b/package.json index e3f3bde..388d94f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "@editorjs/marker": "^1.4.0", "@editorjs/paragraph": "^2.11.7", "@editorjs/quote": "^2.7.6", + "@types/axios": "^0.14.4", "@types/nodemailer": "^6.4.17", + "axios": "^1.8.4", "bcrypt": "^5.1.1", "csv-parse": "^5.6.0", "jsonwebtoken": "^9.0.2", @@ -51,4 +53,4 @@ "ts-node": "^10.9.2", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/src/scripts/send-emails-to-customers.ts b/src/scripts/send-emails-to-customers.ts index 0cb2a50..de9732e 100644 --- a/src/scripts/send-emails-to-customers.ts +++ b/src/scripts/send-emails-to-customers.ts @@ -3,20 +3,132 @@ 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:3000/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.url}. + 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.`; + }/* 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: `

Hello ${customer.name}!

Thank you for your continued interest in our services. We appreciate your business.

` + }; + } + } 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: `

Hello ${customer.name}!

Thank you for your interest in our services. We look forward to working with you!

` + }; + } +} + async function main() { try { console.log('Initializing database connection...'); @@ -71,39 +183,42 @@ async function main() { 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})...`); + console.log(`Processing email for ${customer.name} (${customer.email})...`); try { - const result = await sendEmail( - customer.id, - emailSubject, - emailBody, - `

Hello ${customer.name}!

${emailBody}

` - ); + // Generate email content using LMStudio + const emailContent = await generateEmailContent(customer, emailCount); - if (result.success) { - console.log(`✅ Email sent successfully to ${customer.email}`); + 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.error(`❌ Failed to send email to ${customer.email}: ${result.error}`); + 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) { - console.error(`❌ Error sending email to ${customer.email}:`, 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 @@ -114,13 +229,21 @@ async function main() { await dataSource.destroy(); } catch (error) { - console.error('An error occurred:', 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 => { - console.error('Unhandled error:', error); + const errorMessage = error instanceof Error + ? error.message + : 'Unknown error occurred'; + + console.error('Unhandled error:', errorMessage); process.exit(1); });