diff --git a/beacon/app/api/SendEmails/route.js b/beacon/app/api/SendEmails/route.js index 047fe9e..27ebba3 100644 --- a/beacon/app/api/SendEmails/route.js +++ b/beacon/app/api/SendEmails/route.js @@ -3,6 +3,7 @@ import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'; import * as XLSX from 'xlsx'; import { promises as fs } from 'fs'; import path from 'path'; +import pLimit from 'p-limit'; export async function POST(request) { // No tempDir needed; use in-memory buffers @@ -112,94 +113,162 @@ export async function POST(request) { const results = []; let processedCount = 0; - for (const row of data) { - try { - processedCount++; - console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`); - - // Skip rows with missing email or name - if (!row.Email || !row.Name) { - results.push({ - email: row.Email || 'Unknown', - name: row.Name || 'Unknown', - status: 'skipped', - message: 'Missing email or name' - }); - continue; - } - - // Replace placeholders in template - let personalizedTemplate = template; - - // Replace {{Recipient_name}} with the actual name - personalizedTemplate = personalizedTemplate.replace(/{{Recipient_name}}/g, row.Name); - - // Replace any other placeholders that might exist - Object.keys(row).forEach(key => { - const placeholder = new RegExp(`{{${key}}}`, 'g'); - personalizedTemplate = personalizedTemplate.replace(placeholder, row[key]); - }); - - const isPlainTextOnly = false; // We always send as HTML now since rich text editor produces HTML - - // Create SES email parameters - const emailParams = { - Source: senderEmail, - Destination: { - ToAddresses: [row.Email], - }, - Message: { - Subject: { - Data: subject, - Charset: 'UTF-8', - }, - Body: { - Html: { - Data: personalizedTemplate, - Charset: 'UTF-8', + // Instead of for...of + const limit = pLimit(12); // 12 concurrent sends (SES default rate) + + // for (const row of data) { + // try { + // processedCount++; + // console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`); + + // // Skip rows with missing email or name + // if (!row.Email || !row.Name) { + // results.push({ + // email: row.Email || 'Unknown', + // name: row.Name || 'Unknown', + // status: 'skipped', + // message: 'Missing email or name' + // }); + // continue; + // } + + // // Replace placeholders in template + // let personalizedTemplate = template; + + // // Replace {{Recipient_name}} with the actual name + // personalizedTemplate = personalizedTemplate.replace(/{{Recipient_name}}/g, row.Name); + + // // Replace any other placeholders that might exist + // Object.keys(row).forEach(key => { + // const placeholder = new RegExp(`{{${key}}}`, 'g'); + // personalizedTemplate = personalizedTemplate.replace(placeholder, row[key]); + // }); + + // const isPlainTextOnly = false; // We always send as HTML now since rich text editor produces HTML + + // // Create SES email parameters + // const emailParams = { + // Source: senderEmail, + // Destination: { + // ToAddresses: [row.Email], + // }, + // Message: { + // Subject: { + // Data: subject, + // Charset: 'UTF-8', + // }, + // Body: { + // Html: { + // Data: personalizedTemplate, + // Charset: 'UTF-8', + // }, + // }, + // }, + // }; + + // // Send email + // const command = new SendEmailCommand(emailParams); + // const response = await sesClient.send(command); + + // results.push({ + // email: row.Email, + // name: row.Name, + // status: 'success', + // message: 'Email sent successfully', + // messageId: response.MessageId + // }); + + // // Add a small delay between emails to avoid rate limiting + // if (processedCount < data.length) { + // await new Promise(resolve => setTimeout(resolve, 100)); + // } + + // } catch (error) { + // console.error(`Error sending email to ${row.Email}:`, error); + + // // Provide specific error messages + // let errorMessage = error.message || 'Unknown error occurred'; + // if (error.message.includes('Email address is not verified')) { + // errorMessage = 'Email address not verified in AWS SES'; + // } else if (error.message.includes('AccessDenied')) { + // errorMessage = 'AWS SES permission denied - check IAM permissions'; + // } else if (error.message.includes('InvalidParameterValue')) { + // errorMessage = 'Invalid email format or parameters'; + // } + + // results.push({ + // email: row.Email, + // name: row.Name, + // status: 'error', + // message: errorMessage + // }); + // } + // } + + await Promise.all( + data.map((row, idx) => + limit(async () => { + try { + processedCount++; + console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`); + + if (!row.Email || !row.Name) { + results.push({ + email: row.Email || 'Unknown', + name: row.Name || 'Unknown', + status: 'skipped', + message: 'Missing email or name' + }); + return; + } + + // Replace placeholders + let personalizedTemplate = template.replace(/{{Recipient_name}}/g, row.Name); + Object.keys(row).forEach(key => { + const placeholder = new RegExp(`{{${key}}}`, 'g'); + personalizedTemplate = personalizedTemplate.replace(placeholder, row[key]); + }); + + const emailParams = { + Source: senderEmail, + Destination: { ToAddresses: [row.Email] }, + Message: { + Subject: { Data: subject, Charset: 'UTF-8' }, + Body: { Html: { Data: personalizedTemplate, Charset: 'UTF-8' } } }, - }, - }, - }; - - // Send email - const command = new SendEmailCommand(emailParams); - const response = await sesClient.send(command); - - results.push({ - email: row.Email, - name: row.Name, - status: 'success', - message: 'Email sent successfully', - messageId: response.MessageId - }); - - // Add a small delay between emails to avoid rate limiting - if (processedCount < data.length) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - - } catch (error) { - console.error(`Error sending email to ${row.Email}:`, error); - - // Provide specific error messages - let errorMessage = error.message || 'Unknown error occurred'; - if (error.message.includes('Email address is not verified')) { - errorMessage = 'Email address not verified in AWS SES'; - } else if (error.message.includes('AccessDenied')) { - errorMessage = 'AWS SES permission denied - check IAM permissions'; - } else if (error.message.includes('InvalidParameterValue')) { - errorMessage = 'Invalid email format or parameters'; - } - - results.push({ - email: row.Email, - name: row.Name, - status: 'error', - message: errorMessage - }); - } - } + }; + + const command = new SendEmailCommand(emailParams); + const response = await sesClient.send(command); + + results.push({ + email: row.Email, + name: row.Name, + status: 'success', + message: 'Email sent successfully', + messageId: response.MessageId + }); + + } catch (error) { + console.error(`Error sending email to ${row.Email}:`, error); + let errorMessage = error.message || 'Unknown error occurred'; + if (error.message.includes('Email address is not verified')) { + errorMessage = 'Email address not verified in AWS SES'; + } else if (error.message.includes('AccessDenied')) { + errorMessage = 'AWS SES permission denied - check IAM permissions'; + } else if (error.message.includes('InvalidParameterValue')) { + errorMessage = 'Invalid email format or parameters'; + } + results.push({ + email: row.Email, + name: row.Name, + status: 'error', + message: error.message || 'Unknown error occurred' + }); + } + }) + ) + ); // Calculate summary const successCount = results.filter(r => r.status === 'success').length; diff --git a/beacon/public/sample-data.xlsx b/beacon/public/sample-data.xlsx index b82cfe6..451439d 100644 Binary files a/beacon/public/sample-data.xlsx and b/beacon/public/sample-data.xlsx differ diff --git a/beacon/templates/RSVP.html b/beacon/templates/RSVP.html new file mode 100644 index 0000000..6a629c6 --- /dev/null +++ b/beacon/templates/RSVP.html @@ -0,0 +1,176 @@ + + + + + + + + Figma Fiesta 2.0 — RSVP + + + + +
+
+
+
GitHub Community SRM presents
+

Figma Fiesta 2.0

+
+ + + + Figma Fiesta 2.0 — event poster + + +
+

Hello {{Recipient_name}},

+ +

You’re invited to Figma Fiesta 2.0 — a creative design event hosted by + GitHub Community SRM. Join us for an exciting session filled with design insights + and fun! +

+ +
+ Date: 14th October
+ Time: 8:30 AM - 5:00 PM
+ Venue: iMac Lab C, Tech Park 1, SRM Institute of Science and Technology +
+ + +

+ RSVP before 8 PM tonight!
+ Only 60 seats are available. Entry is strictly limited to RSVP’d participants.
+ Upon registration, you will receive a QR ticket via email. +

+ + +

+ RSVP Now +

+ +

RSVP quickly to secure your spot! Only registered attendees with a QR ticket will be + allowed entry.

+
+ + +
+
+ + + \ No newline at end of file diff --git a/beacon/templates/onboarding.html b/beacon/templates/onboarding.html new file mode 100644 index 0000000..605ebeb --- /dev/null +++ b/beacon/templates/onboarding.html @@ -0,0 +1,169 @@ + + + + + + + + + + 🎉 Congratulations – You’re Selected for GitHub Community SRM! + + + + + +
Congratulations! You’ve been selected for GitHub Community SRM. Join our WhatsApp group for + next steps.
+ + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/beacon/templates/postpone.html b/beacon/templates/postpone.html new file mode 100644 index 0000000..6e3ae91 --- /dev/null +++ b/beacon/templates/postpone.html @@ -0,0 +1,311 @@ + + + + + + + + + + Important Update: Interviews Postponed + + + + + +
Interviews postponed to Monday & Tuesday (15th and 16th Sep). If you missed your slot, you + can come on these days.
+ + + + + + + + + \ No newline at end of file diff --git a/beacon/templates/task-invite-fixed.html b/beacon/templates/task-invite-fixed.html deleted file mode 100644 index e69de29..0000000 diff --git a/beacon/templates/update.html b/beacon/templates/update.html new file mode 100644 index 0000000..fc5e780 --- /dev/null +++ b/beacon/templates/update.html @@ -0,0 +1,320 @@ + + + + + + + + + + Follow-up: Interview Postponement — Walk-in Details + + + + + +
Follow-up: Walk-in on 15–16 Sep from 5:20 PM. If you’ve already interviewed, no need to come. +
+ + + + + + + + + \ No newline at end of file