Skip to content

concurrent emailing#12

Open
djdiptayan1 wants to merge 8 commits intodevelopmentfrom
concurancy
Open

concurrent emailing#12
djdiptayan1 wants to merge 8 commits intodevelopmentfrom
concurancy

Conversation

@djdiptayan1
Copy link
Owner

@djdiptayan1 djdiptayan1 commented Sep 17, 2025

Summary by CodeRabbit

  • New Features

    • Faster bulk email sending via controlled concurrency.
    • Four new responsive email templates: RSVP, onboarding, postpone, and update.
  • Refactor

    • Preserves per-recipient reporting (success, error, skipped); overall summary retained.
    • Placeholder replacement streamlined: recipient name then remaining fields filled.
    • Keeps validations for missing Email/Name and AWS sandbox notice.
    • Results may arrive out of original order; provider error messages appear in results.

@vercel
Copy link

vercel bot commented Sep 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
beacon Ready Ready Preview Comment Oct 13, 2025 0:16am

@coderabbitai
Copy link

coderabbitai bot commented Sep 17, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds concurrency-limited parallel email sending using p-limit (limit 12), replacing sequential processing; preserves per-row validation, result schema and summary computation. Adds new HTML email templates: RSVP.html, onboarding.html, postpone.html, and update.html. Error mapping computed but raw error messages are returned.

Changes

Cohort / File(s) Summary
Email sending route
beacon/app/api/SendEmails/route.js
Replaces serial loop with p-limit(12) + Promise.all for concurrent sends; keeps per-row validation and result format; refactors placeholder replacement; sends SES SendEmailCommand per task; logs errors and computes mapped messages but returns raw error.message; preserves summary counts, statusCode derivation, and sandbox-note logic.
New email templates
beacon/templates/RSVP.html, beacon/templates/onboarding.html, beacon/templates/postpone.html, beacon/templates/update.html
Adds four standalone HTML email templates with responsive/email-client compatible markup and placeholders (e.g., {{Recipient_name}}). No runtime logic or exported API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Route as SendEmails Route
  participant Parser as CSV/HTML Parser
  participant Limiter as p-limit (max 12)
  participant SES as AWS SES
  participant Agg as Result Aggregator

  Client->>Route: POST /SendEmails (CSV + template)
  Route->>Parser: parse CSV, validate required fields
  Parser-->>Route: rows + template
  Note over Route,Limiter: Serial -> concurrent scheduling (limit 12)

  loop per row (max 12 concurrent)
    Route->>Limiter: schedule task
    Limiter->>Route: run task
    Route->>Route: validate row, replace placeholders
    Route->>SES: SendEmailCommand
    alt success
      SES-->>Route: messageId
      Route->>Agg: push {status: success, messageId, ...}
    else error
      SES-->>Route: error
      Route->>Route: compute mapped errorMessage (not used)
      Route->>Agg: push {status: error, message: raw error.message}
    end
  end

  Route->>Route: compute counts, statusCode, sandbox note
  Route-->>Client: JSON {results[], summary}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble code and count to twelve,
Tasks hop forth, a busy delve.
Names tucked in, the sends depart,
Some skip, some cheer, some miss the mark.
A rabbit’s batch — swift, neat, and smart. 🐰✉️

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title “concurrent emailing” clearly summarizes the main change of introducing concurrency into the email sending flow and is concise and directly related to the core update. It avoids vague or unrelated terms while remaining brief and understandable for teammates.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch concurancy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
beacon/app/api/SendEmails/route.js (1)

92-108: Fix required-column check (you’re validating first row values, not header presence).

If the first row has empty Name/Email, you 400 even when columns exist. Check header keys instead.

-        if (!firstRow.Name || !firstRow.Email) {
-            return NextResponse.json(
-                {
-                    success: false,
-                    message: 'Excel file must contain "Name" and "Email" columns',
-                    availableColumns: availableColumns,
-                    hasName: !!firstRow.Name,
-                    hasEmail: !!firstRow.Email
-                },
-                { status: 400 }
-            );
-        }
+        const hasNameCol = availableColumns.some(c => c.trim().toLowerCase() === 'name');
+        const hasEmailCol = availableColumns.some(c => c.trim().toLowerCase() === 'email');
+        if (!hasNameCol || !hasEmailCol) {
+            return NextResponse.json(
+                {
+                    success: false,
+                    message: 'Excel file must contain "Name" and "Email" columns',
+                    availableColumns,
+                    hasName: hasNameCol,
+                    hasEmail: hasEmailCol
+                },
+                { status: 400 }
+            );
+        }
🧹 Nitpick comments (3)
beacon/app/api/SendEmails/route.js (3)

113-115: Preserve input order in results; avoid push from concurrent tasks.

Concurrent pushes make order nondeterministic. Index into a pre-sized array.

-        const results = [];
+        const results = new Array(data.length);
-                            results.push({
+                            results[idx] = {
                                 email: row.Email || 'Unknown',
                                 name: row.Name || 'Unknown',
                                 status: 'skipped',
                                 message: 'Missing email or name'
-                            });
+                            };
-                        results.push({
+                        results[idx] = {
                             email: row.Email,
                             name: row.Name,
                             status: 'success',
                             message: 'Email sent successfully',
                             messageId: response.MessageId
-                        });
+                        };
-                        results.push({
+                        results[idx] = {
                             email: row.Email,
                             name: row.Name,
                             status: 'error',
-                            message: error.message || 'Unknown error occurred'
-                        });
+                            message: errorMessage
+                        };

Also applies to: 216-223, 244-251, 262-267


311-314: Make awsNote detection robust (case‑insensitive and based on error results).

Brittle substring match may miss variants; also constrain to error entries.

-            awsNote: errorCount > 0 && results.some(r => r.message && r.message.includes('not verified'))
+            awsNote: errorCount > 0 && results.some(
+                r => r?.status === 'error' && typeof r?.message === 'string' && /not verified/i.test(r.message)
+            )
                 ? 'AWS SES is in sandbox mode. Only verified email addresses can receive emails. Please verify recipient emails in AWS SES Console or request production access.'
                 : null

208-271: Add basic retry/backoff for transient SES errors (Throttling/5xx).

Reduce flakiness under load with bounded retries + jitter.

Apply minimal call-site change:

-                        const response = await sesClient.send(command);
+                        const response = await withRetry(() => sesClient.send(command));

Add this helper near the top of the file (outside the handler):

// Simple bounded retry with exponential backoff + jitter for transient errors
const withRetry = async (fn, { retries = 3, baseMs = 250 } = {}) => {
  let err;
  for (let attempt = 0; attempt <= retries; attempt++) {
    try { return await fn(); } catch (e) {
      const code = String(e?.name || e?.Code || '').toLowerCase();
      const msg  = String(e?.message || '').toLowerCase();
      const retriable = code.includes('throttl') || code.includes('timeout') || code.includes('temporar')
        || (e?.$metadata?.httpStatusCode >= 500);
      if (!retriable || attempt === retries) { err = e; break; }
      const sleep = baseMs * 2 ** attempt + Math.floor(Math.random() * 100);
      await new Promise(r => setTimeout(r, sleep));
    }
  }
  throw err;
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 545bc9e and 9a28261.

📒 Files selected for processing (1)
  • beacon/app/api/SendEmails/route.js (2 hunks)
🔇 Additional comments (1)
beacon/app/api/SendEmails/route.js (1)

117-117: Make concurrency configurable; don't hardcode "12".

SES send rates vary by account/region — use an env var (validate >0) with a sane default and confirm your account's MaxSendRate via aws ses get-send-quota (MaxSendRate) or the SES console.

-        const limit = pLimit(12); // 12 concurrent sends (SES default rate)
+        const configured = Number(process.env.EMAIL_SEND_CONCURRENCY ?? 5);
+        const concurrency = Number.isFinite(configured) && configured > 0 ? configured : 5;
+        const limit = pLimit(concurrency);

import * as XLSX from 'xlsx';
import { promises as fs } from 'fs';
import path from 'path';
import pLimit from 'p-limit';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Confirm p-limit is declared and compatible with ESM/Next runtime.

Ensure dependency is present (v4+ is ESM) and not duplicated via subdeps.


🏁 Script executed:

#!/bin/bash
# Verify dependency and version
rg -n '"p-limit"' package.json
jq -r '.dependencies["p-limit"] // .devDependencies["p-limit"]' package.json

Length of output: 123


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo
echo "---- find package.json files ----"
find . -name package.json -print || true

echo
echo "---- search for occurrences of 'p-limit' ----"
rg -n --hidden -S "p-limit" || true

echo
echo "---- show beacon/app/api/SendEmails/route.js (if present) ----"
if [ -f "beacon/app/api/SendEmails/route.js" ]; then
  sed -n '1,240p' beacon/app/api/SendEmails/route.js
else
  echo "beacon/app/api/SendEmails/route.js not found"
fi

echo
echo "---- inspect any package.json found for p-limit ----"
for f in $(find . -name package.json -print); do
  echo "---- $f ----"
  if command -v jq >/dev/null 2>&1; then
    jq -r '.name // "<no name>" as $n | ($n + " -> p-limit: " + (.dependencies["p-limit"] // .devDependencies["p-limit"] // "<not declared>"))' "$f" || cat "$f"
  else
    grep -n '"p-limit"' "$f" || echo "<jq missing; grep ran>"
  fi
done

echo
echo "---- search common lockfiles for p-limit versions ----"
rg -n --hidden -S "p-limit" package-lock.json yarn.lock pnpm-lock.yaml pnpm-lock.json || true

Length of output: 10533


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo

echo "---- beacon/package.json (full) ----"
if [ -f "beacon/package.json" ]; then
  sed -n '1,240p' beacon/package.json
else
  echo "beacon/package.json not found"
fi

echo
echo "---- beacon/package-lock.json: p-limit matches (show surrounding lines) ----"
if [ -f "beacon/package-lock.json" ]; then
  rg -n --hidden -S "p-limit" beacon/package-lock.json -n -C3 || true
  echo
  echo "---- show node_modules/p-limit block if present ----"
  rg -n --hidden -S '"node_modules/p-limit":' -n -C6 beacon/package-lock.json || true
else
  echo "beacon/package-lock.json not found"
fi

echo
echo "---- beacon/app/api/SendEmails/route.js (search for runtime/export) ----"
if [ -f "beacon/app/api/SendEmails/route.js" ]; then
  rg -n "export const runtime|export const config|export const dynamic" beacon/app/api/SendEmails/route.js || echo "no explicit runtime/config exports found in route.js"
  echo
  sed -n '1,400p' beacon/app/api/SendEmails/route.js
else
  echo "beacon/app/api/SendEmails/route.js not found"
fi

Length of output: 16037


Declare p-limit in package.json and choose an ESM-compatible version

  • beacon/app/api/SendEmails/route.js imports p-limit (line 6) but beacon/package.json does not declare it; package-lock shows node_modules/p-limit@3.1.0 as a transitive dev-only dependency (risk: omitted in production installs).
  • p-limit v3 is CommonJS; v4+ is ESM-only. Action: add p-limit to beacon/package.json dependencies and pin the major you need — use ^4.x if this route may run in Edge/ESM, or ^3.x if you must remain CJS — then run install to update the lockfile.
🤖 Prompt for AI Agents
In beacon/app/api/SendEmails/route.js around line 6, p-limit is imported but not
declared in beacon/package.json and the transitive node_modules/p-limit@3.1.0 is
a dev-only dep; add p-limit to beacon/package.json "dependencies" (not
devDependencies) and pin the major you need: use "^4.x" if this route must run
in ESM/Edge environments or stay with "^3.x" if you must remain CommonJS, then
run npm/yarn install to update the lockfile and ensure it’s installed in
production installs.

Comment on lines +119 to +206
// 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
// });
// }
// }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented‑out legacy loop.

Large commented blocks add noise and risk divergence.

Delete this block entirely from the PR.

🤖 Prompt for AI Agents
In beacon/app/api/SendEmails/route.js around lines 119 to 206, remove the large
commented-out legacy for-loop block (the entire commented section that processes
rows, sends SES emails, and handles errors) to eliminate dead code and noise;
ensure no dependent references remain elsewhere in the file and run
tests/linting to verify nothing breaks after deletion.

limit(async () => {
try {
processedCount++;
console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t log full recipient emails (PII). Mask before logging.

Avoid emitting raw addresses to logs.

-                        console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`);
+                        const maskedEmail = typeof row.Email === 'string'
+                          ? row.Email.replace(/(.{2}).+(@.+)/, '$1***$2')
+                          : 'Unknown';
+                        console.log(`Processing email ${processedCount}/${data.length} for ${maskedEmail}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`);
const maskedEmail = typeof row.Email === 'string'
? row.Email.replace(/(.{2}).+(@.+)/, '$1***$2')
: 'Unknown';
console.log(`Processing email ${processedCount}/${data.length} for ${maskedEmail}`);
🤖 Prompt for AI Agents
In beacon/app/api/SendEmails/route.js around line 213 the code logs full
recipient emails (PII) with console.log(`Processing email
${processedCount}/${data.length} for ${row.Email}`); — replace this with a
masked form of the email before logging (e.g., keep first character and domain,
replace intervening characters with asterisks or show only the domain and a
short hashed/tokenized prefix), and ensure no other logs emit the raw row.Email;
update the logging statement to use the masked value and add a small helper or
inline logic to perform the masking consistently.

Comment on lines +225 to +231
// 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]);
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Escape placeholder keys and avoid inserting "undefined" into the template.

Unescaped regex keys can mis‑match; undefined values render as the string "undefined".

-                        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 safe = v => (v == null ? '' : String(v));
+                        let personalizedTemplate = template.replace(/{{Recipient_name}}/g, safe(row.Name));
+                        for (const [key, value] of Object.entries(row)) {
+                            const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+                            personalizedTemplate = personalizedTemplate
+                                .replace(new RegExp(`{{${escaped}}}`, 'g'), safe(value));
+                        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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]);
});
// Replace placeholders
const safe = v => (v == null ? '' : String(v));
let personalizedTemplate = template.replace(/{{Recipient_name}}/g, safe(row.Name));
for (const [key, value] of Object.entries(row)) {
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
personalizedTemplate = personalizedTemplate
.replace(new RegExp(`{{${escaped}}}`, 'g'), safe(value));
}
🤖 Prompt for AI Agents
In beacon/app/api/SendEmails/route.js around lines 225 to 231, the loop builds a
RegExp from object keys without escaping special characters and blindly inserts
row[key] which can become the string "undefined"; update it to escape regex
metacharacters in the key before constructing the RegExp (so keys like "a.b" or
"name?" match literally) and only replace when row[key] is not null/undefined
(use an empty string or a safe default for falsy values), converting non-string
values to string explicitly before replacement.

Comment on lines +252 to +267
} 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'
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the mapped errorMessage and guard when error.message is undefined; also normalize matching.

Current code computes errorMessage but returns raw error.message and can throw on .includes when message is absent.

-                        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')) {
+                        console.error(`Error sending email to ${row.Email}:`, error);
+                        const rawMsg = String(error?.message ?? '');
+                        const msg = rawMsg.toLowerCase();
+                        let errorMessage = rawMsg || 'Unknown error occurred';
+                        if (msg.includes('not verified')) {
                             errorMessage = 'Email address not verified in AWS SES';
-                        } else if (error.message.includes('AccessDenied')) {
+                        } else if (msg.includes('accessdenied')) {
                             errorMessage = 'AWS SES permission denied - check IAM permissions';
-                        } else if (error.message.includes('InvalidParameterValue')) {
+                        } else if (msg.includes('invalidparametervalue')) {
                             errorMessage = 'Invalid email format or parameters';
+                        } else if (msg.includes('throttl')) {
+                            errorMessage = 'AWS SES rate limit exceeded - reduce concurrency or request a higher limit';
                         }
-                        results.push({
-                            email: row.Email,
-                            name: row.Name,
-                            status: 'error',
-                            message: error.message || 'Unknown error occurred'
-                        });
+                        results[idx] = {
+                            email: row.Email,
+                            name: row.Name,
+                            status: 'error',
+                            message: errorMessage
+                        };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In beacon/app/api/SendEmails/route.js around lines 252-267, the code computes a
mapped errorMessage but still uses error.message and calls .includes on it which
can throw if undefined; fix by first extracting a safe string like const rawMsg
= (error && error.message) ? String(error.message) : '' , normalize it to
lowerCase for matching, run includes checks against the normalized string to set
errorMessage cases, then use the computed errorMessage when pushing results and
in the console.error (falling back to a generic message if empty).

@djdiptayan1 djdiptayan1 requested a review from Copilot September 17, 2025 19:36
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements concurrent emailing functionality to improve bulk email sending performance by replacing sequential processing with controlled concurrency using the p-limit library.

Key changes:

  • Introduces concurrent email processing with a limit of 12 simultaneous sends
  • Replaces sequential for-loop with Promise.all and p-limit for controlled concurrency
  • Removes artificial delays between email sends that were previously used for rate limiting

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

email: row.Email,
name: row.Name,
status: 'error',
message: error.message || 'Unknown error occurred'
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The errorMessage variable that was constructed with specific error handling logic is being ignored. Line 266 should use errorMessage instead of error.message || 'Unknown error occurred' to preserve the enhanced error messages for AWS SES issues.

Suggested change
message: error.message || 'Unknown error occurred'
message: errorMessage

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +207
// } 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
// });
// }
// }

Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large blocks of commented-out code should be removed instead of left in the codebase. This creates clutter and makes the code harder to maintain. The old implementation can be retrieved from version control if needed.

Suggested change
// } 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
// });
// }
// }

Copilot uses AI. Check for mistakes.
Comment on lines +212 to +213
processedCount++;
console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`);
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The processedCount variable is being incremented concurrently without synchronization, which can lead to race conditions and inaccurate counts. Consider using an atomic counter or removing this logging since the concurrent processing makes the count unreliable for progress tracking.

Suggested change
processedCount++;
console.log(`Processing email ${processedCount}/${data.length} for ${row.Email}`);

Copilot uses AI. Check for mistakes.
@djdiptayan1
Copy link
Owner Author

🤖 PR Review Agent Report

Review completed at: 2025-09-21 23:34:34 UTC

📊 Summary

  • Total Issues Found: 6
  • Static Analysis Issues: 0
  • AI Feedback Items: 6

Issue Severity Breakdown

  • 💡 Suggestion: 4
  • ⚠️ Warning: 1
  • ℹ️ Info: 1

AI Feedback Categories

  • Performance: 2
  • Security: 1
  • Maintainability: 1
  • Style: 1
  • Architecture: 1

🚨 High Priority Issues

  • beacon/app/api/SendEmails/route.js (line 167): Directly embedding user-provided data into email templates using string replacement is vulnerable to Cross-Site Scripting (XSS) attacks. Sanitize user inputs before inserting them into the email template.

💡 Recommendations

  • Review security-related feedback carefully

AI Review Confidence: 86%


This review was generated automatically. Please use your judgment for final decisions.

Tools used: Static analysis + AI-powered review

@djdiptayan1
Copy link
Owner Author

🤖 PR Review Agent Report

Review completed at: 2025-09-21 23:35:56 UTC

📊 Summary

  • Total Issues Found: 6
  • Static Analysis Issues: 0
  • AI Feedback Items: 6

Issue Severity Breakdown

  • 💡 Suggestion: 4
  • ⚠️ Warning: 1
  • ℹ️ Info: 1

AI Feedback Categories

  • Performance: 2
  • Security: 1
  • Maintainability: 1
  • Style: 1
  • Architecture: 1

🚨 High Priority Issues

  • beacon/app/api/SendEmails/route.js (line 167): Directly embedding user-provided data into email templates using string replacement is vulnerable to Cross-Site Scripting (XSS) attacks. Sanitize user inputs before inserting them into the template.

💡 Recommendations

  • Review security-related feedback carefully

AI Review Confidence: 86%


This review was generated automatically. Please use your judgment for final decisions.

Tools used: Static analysis + AI-powered review

@djdiptayan1
Copy link
Owner Author

PR Review Agent Report

Review completed at: 2025-09-21 23:51:47 UTC

Code Quality Score

Overall Score: 75/100

Detailed Breakdown:

  • Security: 80/100
  • Performance: 85/100
  • Maintainability: 70/100
  • Architecture: 90/100
  • Testing: 60/100
  • Documentation: 0/100

Executive Summary

The code introduces concurrency for faster email sending, which is a positive improvement. However, there are significant security concerns related to input validation and error handling. The code also lacks comprehensive testing and could benefit from refactoring for improved maintainability. Addressing the security vulnerabilities and adding thorough testing should be prioritized.

Summary

  • Total Issues Found: 5
  • Static Analysis Issues: 0
  • AI Feedback Items: 5

Issue Severity Breakdown

  • Suggestion: 3
  • Warning: 2

AI Feedback Categories

  • Security: 2
  • Performance: 1
  • Maintainability: 1
  • Testing: 1

High Priority Issues

  • beacon/app/api/SendEmails/route.js (line 186): The code performs basic checks for missing Email and Name fields. However, it lacks comprehensive input validation for email format, potential injection attacks (especially if placeholders are user-supplied), and other malicious content.

Recommendations

  • Review security-related feedback carefully

AI Review Confidence: 86%


This review was generated automatically. Please use your judgment for final decisions.

Tools used: Static analysis + AI-powered review

@djdiptayan1
Copy link
Owner Author

PR Review Agent Report

Review completed at: 2025-09-21 23:52:44 UTC

Code Quality Score

Overall Score: 75/100

Detailed Breakdown:

  • Security: 80/100
  • Performance: 85/100
  • Maintainability: 70/100
  • Architecture: 90/100
  • Testing: 60/100
  • Documentation: 95/100

Executive Summary

The code introduces concurrency for faster email sending, which is a positive improvement. However, there are some critical security and maintainability concerns that need to be addressed. Input validation is missing, error handling is insufficient, and test coverage is low. Addressing these issues is crucial before deploying to production. Improving placeholder replacement efficiency and refining concurrency control will further enhance performance and maintainability.

Summary

  • Total Issues Found: 5
  • Static Analysis Issues: 0
  • AI Feedback Items: 5

Issue Severity Breakdown

  • Suggestion: 3
  • Warning: 2

AI Feedback Categories

  • Security: 2
  • Performance: 1
  • Maintainability: 1
  • Testing: 1

High Priority Issues

  • beacon/app/api/SendEmails/route.js (line 186): The error handling is too generic. It doesn't provide enough context for debugging and lacks detailed logging.
  • beacon/app/api/SendEmails/route.js: The code lacks robust input validation for the email addresses and other data received from the request.

Recommendations

  • Review security-related feedback carefully

AI Review Confidence: 82%


This review was generated automatically. Please use your judgment for final decisions.

Tools used: Static analysis + AI-powered review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
beacon/templates/onboarding.html (1)

137-145: Add rel="noopener noreferrer" to external links; consider templating the WhatsApp URL.

Mitigates reverse‑tabnabbing and makes the link configurable across environments.

-                            <a href="https://chat.whatsapp.com/ITYlpxzhcM374c6jo1SAGU?mode=ems_copy_t" target="_blank"
+                            <a href="{{whatsapp_link}}" target="_blank" rel="noopener noreferrer"
                                 style="background:linear-gradient(90deg, #16a34a 0%, #22c55e 100%); border-radius:10px; display:inline-block; line-height:48px; padding:0 24px; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size:16px; color:#ffffff; font-weight:600; text-align:center; box-shadow:0 2px 4px rgba(0,0,0,0.1);">
                                 Join Our WhatsApp Group
                             </a>
@@
-                                <a href="https://chat.whatsapp.com/ITYlpxzhcM374c6jo1SAGU?mode=ems_copy_t"
-                                    style="color:#1f2a44; word-break:break-all;">https://chat.whatsapp.com/ITYlpxzhcM374c6jo1SAGU?mode=ems_copy_t</a>
+                                <a href="{{whatsapp_link}}" rel="noopener noreferrer"
+                                    style="color:#1f2a44; word-break:break-all;">{{whatsapp_link}}</a>
beacon/templates/RSVP.html (2)

146-149: Parameterize event details and RSVP URL for reuse.

Improves maintainability and reduces future edits.

-                    <strong>Date:</strong> 14th October<br>
-                    <strong>Time:</strong> 8:30 AM - 5:00 PM<br>
-                    <strong>Venue:</strong> iMac Lab C, Tech Park 1, SRM Institute of Science and Technology
+                    <strong>Date:</strong> {{Event_date}}<br>
+                    <strong>Time:</strong> {{Event_time}}<br>
+                    <strong>Venue:</strong> {{Event_venue}}
@@
-                    <a class="btn" href="https://luma.com/9y9irgqf" target="_blank">RSVP Now</a>
+                    <a class="btn" href="{{RSVP_url}}" target="_blank" rel="noopener noreferrer">RSVP Now</a>

Also applies to: 161-161


129-135: Add rel="noopener noreferrer" to external poster link.

Prevents reverse‑tabnabbing and is a general hardening.

-            <a href="https://res.cloudinary.com/doslhy0tq/image/upload/v1759901984/posterfiesta_hdxi6y.png"
-                target="_blank" style="display:block;">
+            <a href="https://res.cloudinary.com/doslhy0tq/image/upload/v1759901984/posterfiesta_hdxi6y.png"
+                target="_blank" rel="noopener noreferrer" style="display:block;">
beacon/templates/postpone.html (2)

176-188: Parameterize dates, venue, and timing for reuse and easier updates.

Reduces hard‑coded content and future edit risk.

-                                    <strong>Monday & Tuesday</strong><br>
-                                    September 15th & 16th, 2025
+                                    <strong>{{New_days_label}}</strong><br>
+                                    {{New_dates}}
@@
-                                <strong style="color: #dc2626;">today and tomorrow (11th and 12th September)</strong>
+                                <strong style="color: #dc2626;">{{Original_days_label}} ({{Original_dates}})</strong>
@@
-                                                TP 301, TP 302
+                                                {{Interview_venue}}
@@
-                                                From 5:20 PM
+                                                {{Interview_time}}

Also applies to: 204-205, 231-233, 240-241


255-275: Confirm consent and consider centralizing contact numbers.

Phone numbers are PII; ensure consent and store in config/env to avoid hard‑coding.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a28261 and 62386ad.

⛔ Files ignored due to path filters (1)
  • beacon/public/sample-data.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (4)
  • beacon/templates/RSVP.html (1 hunks)
  • beacon/templates/onboarding.html (1 hunks)
  • beacon/templates/postpone.html (1 hunks)
  • beacon/templates/update.html (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • beacon/templates/update.html

Comment on lines +138 to +146
<table role="presentation" aria-hidden="true" cellpadding="0" cellspacing="0" border="0" width="100%"
style=" min-height: 100vh;">
<tr>
<td align="center" style="padding:32px 16px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" class="container"
style="width:600px; max-width:600px; background:#ffffff; border-radius:20px; box-shadow:0 20px 40px rgba(0,0,0,0.15); overflow: hidden;">

<!-- Header Section -->
<tr>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove aria-hidden from main content table; it hides the email from screen readers.

This is an accessibility blocker.

-    <table role="presentation" aria-hidden="true" cellpadding="0" cellspacing="0" border="0" width="100%"
+    <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
         style=" min-height: 100vh;">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<table role="presentation" aria-hidden="true" cellpadding="0" cellspacing="0" border="0" width="100%"
style=" min-height: 100vh;">
<tr>
<td align="center" style="padding:32px 16px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" class="container"
style="width:600px; max-width:600px; background:#ffffff; border-radius:20px; box-shadow:0 20px 40px rgba(0,0,0,0.15); overflow: hidden;">
<!-- Header Section -->
<tr>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
style=" min-height: 100vh;">
<tr>
<td align="center" style="padding:32px 16px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" class="container"
style="width:600px; max-width:600px; background:#ffffff; border-radius:20px; box-shadow:0 20px 40px rgba(0,0,0,0.15); overflow: hidden;">
<!-- Header Section -->
<tr>
🤖 Prompt for AI Agents
In beacon/templates/postpone.html around lines 138 to 146 the main content table
has aria-hidden="true", which hides the email content from screen readers;
remove the aria-hidden attribute (or set it to false/not present) on this
primary table so assistive technologies can access the content, ensuring the
attribute is only used on purely decorative elements if needed.

Comment on lines +167 to +169
style="margin:0 0 24px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial; font-size:18px; line-height:1.6; color:#374151;">
Dear {{Recipient_name}},
</p>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure Recipient_name is HTML‑escaped at injection time.

Avoid raw replacement; sanitize/escape before inserting.

Use the same verification script provided in RSVP.html to confirm escaping in the SendEmails route.


#!/bin/bash
echo "Locating RSVP.html template"
find . -type f -iname "RSVP.html"
echo
echo "Showing first 200 lines of RSVP.html"
find . -type f -iname "RSVP.html" -exec sed -n '1,200p' {} \;
echo
echo "Searching for 'escape' or filter usage in RSVP.html"
find . -type f -iname "RSVP.html" -exec rg -n -C3 "escape" {} \;
echo
echo "Finding SendEmails route in code"
rg -n -i "SendEmails" -C3 .

#!/bin/bash
# Locate RSVP.html template
RSVP=$(find . -type f -path '*/templates/RSVP.html' | head -n1)
echo "RSVP.html path: $RSVP"
# Print first 200 lines of RSVP.html
[ -n "$RSVP" ] && sed -n '1,200p' "$RSVP"
# Search for Recipient_name usage and escaping in RSVP.html
[ -n "$RSVP" ] && rg -n -C2 "{{\s*Recipient_name" "$RSVP"
[ -n "$RSVP" ] && rg -n "|e\>" "$RSVP"
# Locate SendEmails route definition and rendering calls
rg -n "SendEmails" -C3 .
rg -n "render_template" -C3 beacon/

🏁 Script executed:

#!/usr/bin/env bash
# 1. Locate RSVP.html template
RSVP=$(find . -type f -path "*beacon/templates/RSVP.html" | head -n1)
echo "RSVP.html path: $RSVP"
# 2. Show lines around Recipient_name usage
if [ -f "$RSVP" ]; then
  echo "Occurrences of Recipient_name in RSVP.html:"
  rg -n -C2 "{{\s*Recipient_name" "$RSVP"
  echo "Escaping/filter patterns in RSVP.html:"
  rg -n -C2 "\|e\>" "$RSVP"
else
  echo "RSVP.html not found"
fi

# 3. Locate SendEmails route and rendering calls
echo "Searching for SendEmails handler:"
rg -n "SendEmails" -C3 .
echo "Searching for render_template usage in beacon/:"
rg -n "render_template" -C3 beacon/

Length of output: 3012


#!/bin/bash
# Show context around Recipient_name in RSVP.html
echo "RSVP.html snippet:"
rg -n "Recipient_name" -C3 beacon/templates/RSVP.html || echo "No matches"
echo
# Show beginning of SendEmails route.js
echo "SendEmails route.js header:"
sed -n '1,200p' beacon/app/api/SendEmails/route.js || echo "Cannot open route.js"
echo
# Search for template placeholder replacements in route.js
echo "Searching for .replace calls in route.js:"
rg -n "replace" -C3 beacon/app/api/SendEmails/route.js || echo "No replace calls found"

🏁 Script executed:

#!/usr/bin/env bash
echo "Locating RSVP.html template..."
RSVP=$(find beacon/templates -type f -iname "RSVP.html" | head -n1)
echo "RSVP.html path: $RSVP"
echo
if [ -f "$RSVP" ]; then
  echo "Lines 150-200 around Recipient_name in RSVP.html:"
  sed -n '150,200p' "$RSVP"
  echo
  echo "Occurrences of Recipient_name:"
  grep -n '{{Recipient_name' "$RSVP"
else
  echo "RSVP.html not found"
fi
echo
echo "Inspecting SendEmails route handler..."
ROUTE="beacon/app/api/SendEmails/route.js"
echo "Route file: $ROUTE"
echo
if [ -f "$ROUTE" ]; then
  echo "First 200 lines of route.js:"
  sed -n '1,200p' "$ROUTE"
  echo
  echo "Searching for placeholder replacement calls:"
  grep -n '.replace' "$ROUTE"
else
  echo "route.js not found"
fi

Length of output: 9952


Escape Recipient_name before template substitution
In beacon/app/api/SendEmails/route.js (around line 226), wrap row.Name with an HTML-escape utility (e.g. escapeHtml(row.Name)) before calling .replace(/{{Recipient_name}}/g, …) to prevent HTML injection.

🤖 Prompt for AI Agents
In beacon/templates/postpone.html around lines 167 to 169, the template inserts
{{Recipient_name}} without escaping which allows HTML injection; before calling
.replace(/{{Recipient_name}}/g, …) in beacon/app/api/SendEmails/route.js (around
line 226) ensure you HTML-escape the source value (e.g. use existing escapeHtml
utility or add one) and pass escapeHtml(row.Name) into the replace call,
importing/require-ing the utility if needed and using the escaped string for all
substitutions.

Comment on lines +138 to +139
<p class="greet">Hello {{Recipient_name}},</p>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure Recipient_name is HTML‑escaped at injection time.

If server does raw string replace, this is XSS‑prone. Escape/sanitize before substitution.

Run to verify escaping/sanitization in SendEmails route:


🏁 Script executed:

#!/bin/bash
# Look for placeholder replacement and any escaping/sanitization usage.
rg -n -C2 -g '!**/dist/**' -g '!**/build/**' -P 'Recipient_name|sanitize|escape|dompurify|xss|he\(|escapeHtml|validator\.escape'
rg -n -C3 -P 'replace\(\s*["\']\{\{Recipient_name\}\}["\']'
rg -n -C3 -P 'SendEmails/route\.js|send.*SES|p-limit'

Length of output: 11296


Escape Recipient_name during template substitution
In beacon/app/api/SendEmails/route.js:226, you’re replacing {{Recipient_name}} with raw row.Name, which permits XSS. Apply HTML escaping (e.g., he.encode(row.Name)) or sanitize the value before insertion.

🤖 Prompt for AI Agents
In beacon/templates/RSVP.html around lines 138-139, the template inserts
{{Recipient_name}} without escaping which allows XSS when route.js replaces it
with raw row.Name (see route.js line 226); fix by HTML-escaping or sanitizing
the name before substitution (e.g., use a library like he.encode or a
sanitizer), import/require that encoder in beacon/app/api/SendEmails/route.js,
replace the raw insertion with the escaped value (and provide a safe fallback if
Name is missing), and add/update tests to assert that HTML in names is rendered
escaped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants