From 597f7ddc5568a2ae72052bf883b7baf88f0d1849 Mon Sep 17 00:00:00 2001 From: Ken Yasue Date: Tue, 1 Apr 2025 16:19:00 +0200 Subject: [PATCH] send from gmail --- .gitignore | 4 +- doc/prompts/14. Gmail sender | 6 +++ package-lock.json | 20 +++++++- package.json | 4 +- src/lib/email.ts | 87 +++++++++++++++++++++++++++++++++ src/scripts/import-customers.ts | 18 ++++--- 6 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 doc/prompts/14. Gmail sender create mode 100644 src/lib/email.ts diff --git a/.gitignore b/.gitignore index 0c31be1..40665c7 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,6 @@ dist .yarn/install-state.gz .pnp.* -data \ No newline at end of file +data + +public/uploads \ No newline at end of file diff --git a/doc/prompts/14. Gmail sender b/doc/prompts/14. Gmail sender new file mode 100644 index 0000000..54cea17 --- /dev/null +++ b/doc/prompts/14. Gmail sender @@ -0,0 +1,6 @@ +Please do following +- checkout to new brach features/gmail_sender +- create email.ts +- create function to send email usimg gmail smtp +- creadentials are in .env +- the function receives CustomerId and save to the Record diff --git a/package-lock.json b/package-lock.json index 5af1f35..ec5e288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,11 +18,13 @@ "@editorjs/marker": "^1.4.0", "@editorjs/paragraph": "^2.11.7", "@editorjs/quote": "^2.7.6", + "@types/nodemailer": "^6.4.17", "bcrypt": "^5.1.1", "csv-parse": "^5.6.0", "jsonwebtoken": "^9.0.2", "mysql2": "^3.13.0", "next": "15.2.2", + "nodemailer": "^6.10.0", "pg": "^8.14.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -1550,12 +1552,19 @@ "version": "20.17.24", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/react": { "version": "19.0.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", @@ -5937,6 +5946,14 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/nodemailer": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -8133,7 +8150,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "devOptional": true, "license": "MIT" }, "node_modules/unique-filename": { diff --git a/package.json b/package.json index de2b705..34ad321 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,13 @@ "@editorjs/marker": "^1.4.0", "@editorjs/paragraph": "^2.11.7", "@editorjs/quote": "^2.7.6", + "@types/nodemailer": "^6.4.17", "bcrypt": "^5.1.1", "csv-parse": "^5.6.0", "jsonwebtoken": "^9.0.2", "mysql2": "^3.13.0", "next": "15.2.2", + "nodemailer": "^6.10.0", "pg": "^8.14.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -48,4 +50,4 @@ "ts-node": "^10.9.2", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/src/lib/email.ts b/src/lib/email.ts new file mode 100644 index 0000000..ae18c54 --- /dev/null +++ b/src/lib/email.ts @@ -0,0 +1,87 @@ +import nodemailer from 'nodemailer'; +import { ContactRecord } from './database/entities/ContactRecord'; +import { Customer } from './database/entities/Customer'; +import { getDataSource } from './database'; + +interface SendEmailResult { + success: boolean; + error?: string; +} + +export async function sendEmail( + customerId: string, + subject: string, + body: string, + htmlBody?: string +): Promise { + try { + // Get data source + const dataSource = await getDataSource(); + + // Get customer details + const customer = await dataSource.getRepository(Customer).findOne({ + where: { id: customerId } + }); + + if (!customer) { + throw new Error(`Customer with ID ${customerId} not found`); + } + + // Create Gmail transporter + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.GMAIL_USER, + pass: process.env.GMAIL_APP_PASSWORD // Using app-specific password + } + }); + + // Send email + const info = await transporter.sendMail({ + from: process.env.GMAIL_USER, + to: customer.email, + subject: subject, + text: body, + html: htmlBody || body + }); + + // Create contact record + const contactRecord = new ContactRecord(); + contactRecord.customer = customer; + contactRecord.customerId = customer.id; + contactRecord.contactType = 'EMAIL'; + contactRecord.notes = `Email sent successfully. Subject: ${subject}. Message ID: ${info.messageId}`; + + // Save contact record + await dataSource.getRepository(ContactRecord).save(contactRecord); + + return { success: true }; + } catch (error) { + // Create contact record for failed attempt + if (error instanceof Error) { + try { + const dataSource = await getDataSource(); + const customer = await dataSource.getRepository(Customer).findOne({ + where: { id: customerId } + }); + + if (customer) { + const contactRecord = new ContactRecord(); + contactRecord.customer = customer; + contactRecord.customerId = customer.id; + contactRecord.contactType = 'EMAIL'; + contactRecord.notes = `Failed to send email: ${error.message}`; + + await dataSource.getRepository(ContactRecord).save(contactRecord); + } + } catch (dbError) { + console.error('Failed to save error record:', dbError); + } + } + + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error occurred' + }; + } +} diff --git a/src/scripts/import-customers.ts b/src/scripts/import-customers.ts index 21dfd92..9c9c4b5 100644 --- a/src/scripts/import-customers.ts +++ b/src/scripts/import-customers.ts @@ -8,7 +8,7 @@ import { Customer } from '../lib/database/entities/Customer'; interface CustomerCSVRow { City: string; Name: string; - 'Website URL': string; + URL: string; Email: string; } @@ -66,14 +66,20 @@ async function importCustomers(csvFilePath: string): Promise { // Create new customer const customer = new Customer(); customer.name = name; - customer.url = record['Website URL'] === 'null' ? '' : record['Website URL']; + customer.url = record['URL'] === 'null' ? '' : record['URL']; customer.email = email; - // Save to database - await customerRepository.save(customer); - importedCount++; + try { + + // Save to database + await customerRepository.save(customer); + importedCount++; + + console.log(`Imported customer: ${name}`); + } catch (e) { + console.log(`Skipped: ${name}`); + } - console.log(`Imported customer: ${name}`); } console.log('Import summary:');