-
Notifications
You must be signed in to change notification settings - Fork 4
add #695: Email notification for drawing events #701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughDraw now returns members with recipient assignments and gift suggestions; the UI posts that payload to a new send-email API which authenticates, constructs per-member recommendations, and sends personalized emails via Resend using a new React Email template. Dependencies for email rendering and delivery were added. Changes
Sequence DiagramsequenceDiagram
participant User as User (Browser)
participant UI as GiftExchangeHeader (Client)
participant DrawAPI as /api/gift-exchanges/{id}/draw
participant EmailAPI as /api/gift-exchanges/{id}/send-email
participant Resend as Resend (Email Service)
participant Recipient as Recipient Email
User->>UI: Click "Draw"
UI->>DrawAPI: POST /draw
DrawAPI->>DrawAPI: Run draw logic
DrawAPI->>DrawAPI: Fetch members & suggestions
DrawAPI-->>UI: { members, suggestions }
UI->>EmailAPI: POST /send-email (members, suggestions)
EmailAPI->>EmailAPI: Authenticate & verify ownership
EmailAPI->>EmailAPI: For each member -> build giftRecommendations
loop per member
EmailAPI->>Resend: Send DrawingEmail (personalized)
Resend-->>EmailAPI: delivery response
Resend->>Recipient: Email delivered
end
EmailAPI-->>UI: { success: true }
UI->>User: Reload page
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (4)
📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
🧰 Additional context used🧬 Code graph analysis (1)app/api/gift-exchanges/[id]/draw/route.ts (1)
🔇 Additional comments (1)
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. Comment |
There was a problem hiding this 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 email notifications for Secret Santa drawing events using Resend and React Email. When a drawing is completed, all participants receive an email with their assigned recipient and AI-generated gift suggestions including Amazon affiliate links.
Key Changes:
- Added email notification system using Resend API and React Email templates
- Modified drawing API to return member and suggestion data needed for emails
- Created reusable email template component with gift recommendations
- Integrated email sending into the drawing workflow
Reviewed changes
Copilot reviewed 5 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Added react-email, resend, and @react-email packages for email functionality |
| pnpm-lock.yaml | Lock file updates for new email-related dependencies |
| emails/DrawingEmail.tsx | React Email template displaying match results and gift suggestions |
| app/api/gift-exchanges/[id]/send-email/route.ts | API endpoint to send emails to all exchange members |
| app/api/gift-exchanges/[id]/draw/route.ts | Modified to return member/suggestion data after drawing |
| components/GiftExchangeHeader/GiftExchangeHeader.tsx | Client integration to trigger email sending after drawing |
| public/secret_santa_exchange_logo_beta.png | PNG logo for email template (SVG not supported in emails) |
| emails/static/secret_santa_exchange_logo_beta.png | Duplicate logo file for email preview server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🧹 Nitpick comments (3)
app/api/gift-exchanges/[id]/draw/route.ts (1)
38-91: Consider adding validation after draw completion.The endpoint immediately fetches members and suggestions after
drawGiftExchangecompletes, but doesn't verify the draw was successful. Consider checking that all members haverecipient_idassigned before returning.Example validation to add after line 62:
// Verify all members have recipients assigned const unassignedMembers = membersData?.filter(m => !m.recipient_id) || []; if (unassignedMembers.length > 0) { throw new SupabaseError( 'Draw incomplete: some members were not assigned recipients', 500, { unassignedCount: unassignedMembers.length } ); }components/GiftExchangeHeader/GiftExchangeHeader.tsx (2)
164-174: Validate send-email endpoint response.The code doesn't check if the email request succeeded (
response.ok). Consider validating the response to detect partial failures.Apply this diff:
try { - await fetch(`/api/gift-exchanges/${id}/send-email`, { + const emailResponse = await fetch(`/api/gift-exchanges/${id}/send-email`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ exchangeId: id, members: data.members, suggestions: data.suggestions }), }); + if (!emailResponse.ok) { + throw new Error(`Email sending failed with status ${emailResponse.status}`); + } } catch (emailError) {
161-179: Consider adding timeout for email request.The email sending request has no timeout, which could cause the UI to hang if the email service is unresponsive. Consider adding a timeout or using AbortController.
Example implementation:
const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout try { const emailResponse = await fetch(`/api/gift-exchanges/${id}/send-email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ exchangeId: id, members: data.members, suggestions: data.suggestions }), signal: controller.signal }); clearTimeout(timeoutId); // ... rest of logic } catch (emailError) { clearTimeout(timeoutId); if (emailError.name === 'AbortError') { console.error('Email request timed out'); } // ... rest of error handling }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
emails/static/secret_santa_exchange_logo_beta.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpublic/secret_santa_exchange_logo_beta.pngis excluded by!**/*.png
📒 Files selected for processing (5)
app/api/gift-exchanges/[id]/draw/route.ts(1 hunks)app/api/gift-exchanges/[id]/send-email/route.ts(1 hunks)components/GiftExchangeHeader/GiftExchangeHeader.tsx(1 hunks)emails/DrawingEmail.tsx(1 hunks)package.json(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/api/gift-exchanges/[id]/send-email/route.ts (1)
emails/DrawingEmail.tsx (1)
DrawingEmail(40-211)
app/api/gift-exchanges/[id]/draw/route.ts (1)
lib/errors/CustomErrors.ts (1)
SupabaseError(57-77)
emails/DrawingEmail.tsx (1)
app/types/giftSuggestion.ts (1)
IGiftSuggestion(4-12)
🪛 Biome (2.1.2)
emails/DrawingEmail.tsx
[error] 65-65: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🪛 ESLint
app/api/gift-exchanges/[id]/send-email/route.ts
[error] 23-23: 'recommendedGifts' is never reassigned. Use 'const' instead.
(prefer-const)
[error] 37-37: 'result' is assigned a value but never used.
(@typescript-eslint/no-unused-vars)
emails/DrawingEmail.tsx
[error] 1-248: missing header
(header/header)
[error] 24-25: Missing JSDoc comment.
(jsdoc/require-jsdoc)
[error] 24-24: Missing return type on function.
(@typescript-eslint/explicit-function-return-type)
[error] 40-41: Missing JSDoc comment.
(jsdoc/require-jsdoc)
[error] 44-44: Missing return type on function.
(@typescript-eslint/explicit-function-return-type)
[error] 47-47: Expected indentation of 6 spaces but found 4.
(indent)
[error] 47-47: Expected indentation of 6 space characters but found 4.
(react/jsx-indent)
[error] 50-50: A space is required before '}'.
(object-curly-spacing)
[error] 63-63: Expected indentation of 10 spaces but found 12.
(indent)
[error] 63-192: Expected indentation of 10 space characters but found 12.
(react/jsx-indent)
[error] 65-190: Missing "key" prop for element in iterator
(react/jsx-key)
[error] 169-169: Do not use Array index in keys
(react/no-array-index-key)
[error] 192-192: Expected indentation of 10 spaces but found 12.
(indent)
[error] 193-193: Expected indentation of 10 spaces but found 12.
(indent)
[error] 193-193: Expected indentation of 10 space characters but found 12.
(react/jsx-indent)
[error] 193-193: A space is required after '{'.
(object-curly-spacing)
[error] 193-193: A space is required before '}'.
(object-curly-spacing)
[error] 194-194: Expected indentation of 12 spaces but found 14.
(indent)
[error] 195-195: Expected indentation of 14 spaces but found 16.
(indent)
[error] 196-196: Expected indentation of 14 spaces but found 16.
(indent)
[error] 197-197: Expected indentation of 12 spaces but found 14.
(indent)
[error] 199-199: Expected indentation of 12 spaces but found 14.
(indent)
[error] 200-200: Expected indentation of 10 spaces but found 12.
(indent)
🔇 Additional comments (3)
package.json (2)
7-7: LGTM! Email development script added.The
email devscript enables local preview of React Email templates, which aligns with the PR objectives for testing email notifications.
30-30: Dependency versions are current and secure.The email-related dependencies (@react-email/components@^1.0.1, react-email@^5.0.4, resend@^6.5.2, and @react-email/preview-server@5.0.4) have no known public security vulnerabilities. All packages are actively maintained with recent releases and healthy maintenance scores. Versions use appropriate semantic versioning ranges.
emails/DrawingEmail.tsx (1)
51-55: Both static assets (secret_santa_exchange_logo_beta.pngandgift100px.png) exist in thepublicfolder as expected. The code correctly references them using thebaseUrlenvironment variable, which is appropriate for email rendering.
| if (suggestionsError) { | ||
| console.error('membersError detail:', membersError); | ||
| throw new SupabaseError( | ||
| 'Failed to fetch updated members', | ||
| suggestionsError.code, | ||
| suggestionsError, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix copy-paste error in error handling.
Line 80 incorrectly logs membersError instead of suggestionsError, and line 82 has the wrong error message.
Apply this diff:
if (suggestionsError) {
- console.error('membersError detail:', membersError);
+ console.error('suggestionsError detail:', suggestionsError);
throw new SupabaseError(
- 'Failed to fetch updated members',
+ 'Failed to fetch gift suggestions',
suggestionsError.code,
suggestionsError,
);
}📝 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.
| if (suggestionsError) { | |
| console.error('membersError detail:', membersError); | |
| throw new SupabaseError( | |
| 'Failed to fetch updated members', | |
| suggestionsError.code, | |
| suggestionsError, | |
| ); | |
| if (suggestionsError) { | |
| console.error('suggestionsError detail:', suggestionsError); | |
| throw new SupabaseError( | |
| 'Failed to fetch gift suggestions', | |
| suggestionsError.code, | |
| suggestionsError, | |
| ); |
🤖 Prompt for AI Agents
In app/api/gift-exchanges/[id]/draw/route.ts around lines 79 to 85, there's a
copy-paste mistake: the console.error call logs membersError and the thrown
SupabaseError message refers to fetching updated members while the branch is
handling suggestionsError. Update the console.error to log 'suggestionsError
detail:' with suggestionsError, and change the SupabaseError message to 'Failed
to fetch suggestions' while passing suggestionsError.code and suggestionsError
as before.
| const body = await req.json(); | ||
| const { members, suggestions } = body; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add request body validation.
The endpoint doesn't validate the structure of members and suggestions, which could lead to runtime errors.
Add validation after parsing the body:
const body = await req.json();
const { members, suggestions } = body;
// Validate input
if (!Array.isArray(members) || members.length === 0) {
return NextResponse.json(
{ error: 'Invalid members array' },
{ status: 400 }
);
}
if (!Array.isArray(suggestions)) {
return NextResponse.json(
{ error: 'Invalid suggestions array' },
{ status: 400 }
);
}🤖 Prompt for AI Agents
In app/api/gift-exchanges/[id]/send-email/route.ts around lines 18 to 19, the
parsed request body is used without validation; add input checks immediately
after parsing to ensure members is a non-empty array and suggestions is an
array, and return a 400 NextResponse.json with an appropriate error message when
validation fails (e.g., "Invalid members array" for missing/empty members and
"Invalid suggestions array" when suggestions is not an array).
| const handleAmazonLink = ({ searchTerm }: { searchTerm: string }) => { | ||
| const encodedSearch = encodeURIComponent(searchTerm).replace(/%20/g, '+'); | ||
|
|
||
| return `https://www.amazon.com/s?k=${encodedSearch}&tag=${process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG}`; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate environment variable before use.
If NEXT_PUBLIC_AMAZON_AFFILIATE_TAG is undefined, the Amazon link will include &tag=undefined, creating malformed URLs.
Apply this diff to add validation:
const handleAmazonLink = ({ searchTerm }: { searchTerm: string }) => {
const encodedSearch = encodeURIComponent(searchTerm).replace(/%20/g, '+');
-
- return `https://www.amazon.com/s?k=${encodedSearch}&tag=${process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG}`;
+ const tag = process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG;
+ const tagParam = tag ? `&tag=${tag}` : '';
+ return `https://www.amazon.com/s?k=${encodedSearch}${tagParam}`;
};🧰 Tools
🪛 ESLint
[error] 24-25: Missing JSDoc comment.
(jsdoc/require-jsdoc)
[error] 24-24: Missing return type on function.
(@typescript-eslint/explicit-function-return-type)
🤖 Prompt for AI Agents
In emails/DrawingEmail.tsx around lines 24 to 28, validate
NEXT_PUBLIC_AMAZON_AFFILIATE_TAG before interpolating it into the URL: if the
env var is present and non-empty, append &tag=VALUE; if it's missing/empty, omit
the tag parameter entirely (or use a configured safe fallback) to avoid
producing &tag=undefined; implement this by reading the env var into a const,
checking truthiness, and conditionally building the query suffix so the returned
URL is always well-formed.
| const baseUrl = process.env.VERCEL_URL | ||
| ? `https://${process.env.VERCEL_URL}` | ||
| : 'http://localhost:3000/static'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix baseUrl fallback logic for email rendering.
The fallback http://localhost:3000/static is incorrect for email rendering, which occurs server-side in production. When VERCEL_URL is undefined (local dev), emails won't render images properly. Additionally, VERCEL_URL doesn't include the protocol.
Apply this diff:
-const baseUrl = process.env.VERCEL_URL
- ? `https://${process.env.VERCEL_URL}`
- : 'http://localhost:3000/static';
+const baseUrl = process.env.VERCEL_URL
+ ? `https://${process.env.VERCEL_URL}`
+ : process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:4000';Ensure static assets are placed in the public folder (not public/static) so they're accessible at /secret_santa_exchange_logo_beta.png and /gift100px.png.
📝 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.
| const baseUrl = process.env.VERCEL_URL | |
| ? `https://${process.env.VERCEL_URL}` | |
| : 'http://localhost:3000/static'; | |
| const baseUrl = process.env.VERCEL_URL | |
| ? `https://${process.env.VERCEL_URL}` | |
| : process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:4000'; |
🤖 Prompt for AI Agents
In emails/DrawingEmail.tsx around lines 30-32, the baseUrl fallback is
incorrect: it sets a dev URL including /static and assumes VERCEL_URL includes
protocol. Change the logic to build a proper origin (prepend https:// when
VERCEL_URL exists), and when VERCEL_URL is undefined use http://localhost:3000
(no /static) so server-side email rendering can resolve assets as
/secret_santa_exchange_logo_beta.png and /gift100px.png; also ensure those image
files live directly in the public folder (not public/static) so they are served
at the root paths used in the email.
emails/DrawingEmail.tsx
Outdated
| <Section style={{width: '50%', marginBottom: '15px'}}> | ||
| <Button | ||
| className="bg-[#0D352E] rounded-[3px] text-white text-[16px] no-underline text-center block p-3" | ||
| href="https://secretsanta-exchange.com" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "secretsanta-exchange\.com" --type ts --type tsxRepository: LetsGetTechnical/elecretanta
Length of output: 99
🏁 Script executed:
# Also search for environment variable patterns and other URL configurations
rg -n "href=" emails/DrawingEmail.tsx -A 1 -B 1Repository: LetsGetTechnical/elecretanta
Length of output: 420
🏁 Script executed:
# Check if there's any environment configuration or .env files
find . -maxdepth 3 -type f \( -name ".env*" -o -name "config.*" -o -name "*config*" \)Repository: LetsGetTechnical/elecretanta
Length of output: 301
🏁 Script executed:
# Search for other hardcoded URLs or domains in the codebase
rg "https://|http://" --type ts --type tsx | head -20Repository: LetsGetTechnical/elecretanta
Length of output: 99
🏁 Script executed:
cat .env.exampleRepository: LetsGetTechnical/elecretanta
Length of output: 225
🏁 Script executed:
# Search for other hardcoded URLs without tsx type restriction
rg "https://|http://" -A 1 -B 1 | head -40Repository: LetsGetTechnical/elecretanta
Length of output: 3602
🏁 Script executed:
# Look for constants or config files
fd -e "constants" -e "config" -e "constants.ts" -o -e "config.ts" | head -20Repository: LetsGetTechnical/elecretanta
Length of output: 188
🏁 Script executed:
# Check the full context of the DrawingEmail file around line 196
head -200 emails/DrawingEmail.tsx | tail -30Repository: LetsGetTechnical/elecretanta
Length of output: 1197
🏁 Script executed:
# Search for any other references to the domain or similar patterns
rg "secretsanta" . --type tsRepository: LetsGetTechnical/elecretanta
Length of output: 153
🏁 Script executed:
# Check if there's any domain/URL configuration in environment or next.config
head -50 next.config.tsRepository: LetsGetTechnical/elecretanta
Length of output: 280
🏁 Script executed:
# Look for any deployment or environment-specific configuration
rg "NEXT_PUBLIC" . --type ts --type js | head -20Repository: LetsGetTechnical/elecretanta
Length of output: 1715
Move the application domain to an environment variable.
The https://secretsanta-exchange.com URL is hardcoded, while the project already uses environment variables for other external URLs (e.g., NEXT_PUBLIC_AMAZON_AFFILIATE_TAG, NEXT_PUBLIC_GOOGLE_ANALYTICS_ID). Add NEXT_PUBLIC_APP_DOMAIN to .env.example and use it here to support different environments (development, staging, production).
🧰 Tools
🪛 ESLint
[error] 196-196: Expected indentation of 14 spaces but found 16.
(indent)
🤖 Prompt for AI Agents
In emails/DrawingEmail.tsx around line 196, the application domain is hardcoded
as https://secretsanta-exchange.com; replace that literal with a public env var
(NEXT_PUBLIC_APP_DOMAIN) so different environments can be used. Add
NEXT_PUBLIC_APP_DOMAIN to .env.example with a sensible default (e.g.,
https://secretsanta-exchange.com), update the TSX to read from
process.env.NEXT_PUBLIC_APP_DOMAIN (or equivalent client-side env access) when
building the href, and ensure you fallback to the hardcoded URL only if the env
var is missing to avoid breaking existing builds.
Description
Before:
No email notifications had been setup to inform users when their group's drawing occurred.
After:
Adding email notifications to send once a drawing has completed using Resend and React Email.
Closes #695
Testing instructions
Run
pnpm emailto start the local email server. This will load the react email dashboard where you can view the created email templatesAdditional information
Per react email documentation, "All email clients can display .png, .gif, and .jpg images. Unfortunately, .svg images are not well supported, regardless of how they’re referenced, so avoid using these." I've added a .png version of our logo to use in the email template.
Screenshot of the email notification
Pre-submission checklist
test #001: created unit test for __ component)Peer Code ReviewersandSenior+ Code Reviewersgroupsgis-code-questionsSummary by CodeRabbit
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.