Skip to content

feat: add marketing email functionality for beta users#2

Merged
ramblingpm merged 3 commits intomainfrom
claude/beta-marketing-emails-H1jmQ
Jan 5, 2026
Merged

feat: add marketing email functionality for beta users#2
ramblingpm merged 3 commits intomainfrom
claude/beta-marketing-emails-H1jmQ

Conversation

@ramblingpm
Copy link
Owner

Add the ability to send marketing emails to beta users through Resend API:

  • New API endpoint /api/send-marketing-email for sending marketing emails
  • UI component in beta invites admin page with email form
  • Support for filtering recipients (all, approved, used, pending)
  • Email preview with recipient count
  • Confirmation dialog before sending
  • Success/error feedback to admin
  • Bilingual support (English and Swedish)

Features:

  • Compose custom subject and HTML content
  • Select target audience with recipient filters
  • Real-time recipient count display
  • BCC recipients for privacy
  • Admin-only access with authentication checks

Add the ability to send marketing emails to beta users through Resend API:
- New API endpoint /api/send-marketing-email for sending marketing emails
- UI component in beta invites admin page with email form
- Support for filtering recipients (all, approved, used, pending)
- Email preview with recipient count
- Confirmation dialog before sending
- Success/error feedback to admin
- Bilingual support (English and Swedish)

Features:
- Compose custom subject and HTML content
- Select target audience with recipient filters
- Real-time recipient count display
- BCC recipients for privacy
- Admin-only access with authentication checks
@vercel
Copy link

vercel bot commented Jan 4, 2026

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

Project Deployment Review Updated (UTC)
raceplanner-frontend Ready Ready Preview, Comment Jan 5, 2026 0:11am

@claude
Copy link

claude bot commented Jan 4, 2026

Pull Request Review: Marketing Email Functionality

Thank you for this contribution! I've reviewed the code and have the following feedback organized by category:

🔴 Critical Security Issues

1. Missing Authentication in API Route

Location: frontend/src/app/api/send-marketing-email/route.ts:23-26

The endpoint only validates the adminUserId from the request body, but does not verify the actual authenticated user making the request. This means anyone can send this request with any admin user ID.

Issue:

const { subject, html, recipientFilter, adminUserId } = await request.json();
// ... later
if (!adminUserId) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

Recommended Fix: Follow the pattern in frontend/src/app/api/admin/races/route.ts:21-50:

// Get and verify the authorization header
const authHeader = request.headers.get('authorization');
if (!authHeader) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const token = authHeader.replace('Bearer ', '');
const { data: { user }, error: userError } = await supabase.auth.getUser(token);

if (userError || !user) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

// Then verify this authenticated user is an admin
const { data: adminData, error: adminError } = await supabase
  .from('admin_users')
  .select('user_id')
  .eq('user_id', user.id)  // Use the authenticated user.id, not from request body
  .single();

2. Email Injection / XSS Risk

Location: frontend/src/app/api/send-marketing-email/route.ts:106-112

The subject and html parameters from user input are passed directly to the email service without sanitization.

Risks:

  • HTML injection: Malicious HTML/JavaScript in the email body
  • Header injection: Newlines in subject could inject email headers
  • Phishing: No validation of content

Recommended Fix:

// Validate and sanitize subject (no newlines)
if (!subject || subject.includes('\n') || subject.includes('\r')) {
  return NextResponse.json(
    { error: 'Invalid subject: newlines not allowed' },
    { status: 400 }
  );
}

// Consider adding a character limit
if (subject.length > 200 || html.length > 50000) {
  return NextResponse.json(
    { error: 'Subject or content too long' },
    { status: 400 }
  );
}

// For HTML: Consider using a library like DOMPurify or sanitize-html
// to prevent malicious scripts, or at minimum warn/log for review

3. Rate Limiting Missing

Location: frontend/src/app/api/send-marketing-email/route.ts

No rate limiting exists on this endpoint. An attacker (or accidental double-click) could send multiple bulk emails.

Recommended Fix: Add rate limiting middleware or check for recent sends:

// Check for recent marketing emails sent
const { data: recentEmails } = await supabase
  .from('marketing_email_log')  // You'd need to create this table
  .select('sent_at')
  .eq('sent_by', user.id)
  .gte('sent_at', new Date(Date.now() - 60000).toISOString())  // Last minute
  .single();

if (recentEmails) {
  return NextResponse.json(
    { error: 'Please wait before sending another email' },
    { status: 429 }
  );
}

🟡 Moderate Issues

4. Missing CSRF Protection

The frontend doesn't send any CSRF tokens. While Next.js API routes are generally protected by same-origin policy, consider adding explicit CSRF protection for admin actions.

5. No Audit Logging

Location: frontend/src/app/api/send-marketing-email/route.ts

There's no database record of who sent what emails to whom. This is critical for compliance (GDPR, CAN-SPAM) and debugging.

Recommended: Create a marketing_emails table to log:

  • Who sent it (admin user ID)
  • When it was sent
  • Subject and content (or hash)
  • Recipient filter used
  • Number of recipients
  • Resend email ID

6. BCC Limit Risk

Location: frontend/src/app/api/send-marketing-email/route.ts:106-112

Resend (and most email providers) have limits on BCC recipients (often 50-100). The code doesn't check or batch recipients.

Recommended Fix:

const BATCH_SIZE = 50;  // Check Resend's actual limit

if (recipients.length > BATCH_SIZE) {
  // Batch the sending
  for (let i = 0; i < recipients.length; i += BATCH_SIZE) {
    const batch = recipients.slice(i, i + BATCH_SIZE);
    await resend.emails.send({
      from: `${appName} <${fromEmail}>`,
      to: fromEmail,
      bcc: batch,
      subject: subject,
      html: html,
    });
  }
} else {
  // Send single batch
}

7. Error Handling Exposes Internal Details

Location: frontend/src/app/api/send-marketing-email/route.ts:89,117,135

Error responses include details with internal error messages which could leak sensitive information.

Recommended: Only expose detailed errors in development:

return NextResponse.json(
  { 
    error: 'Failed to send email',
    ...(process.env.NODE_ENV === 'development' && { details: emailResult.error })
  },
  { status: 500 }
);

🟢 Code Quality & Best Practices

8. Duplicate Locale Logic

Location: frontend/src/app/api/send-marketing-email/route.ts:42-43

This logic is duplicated from send-approval-email/route.ts. Consider extracting to a shared utility.

9. Inconsistent Variable Naming

Location: frontend/src/app/api/send-marketing-email/route.ts:6-8

Some variables use resend (instance) while resendApiKey is defined but never used (API key is read at line 6).

Fix: Remove unused variable:

const resend = new Resend(process.env.RESEND_API_KEY);
// Remove lines 48-56 as they're redundant - resend is already initialized

10. Unused Import

Location: frontend/src/app/api/send-marketing-email/route.ts:4

Locale is imported but never used.

11. Client-Side Recipient Count Calculation

Location: frontend/src/app/admin/beta-invites/page.tsx:118-134

The recipient count is calculated client-side, but the actual filtering happens server-side. These could get out of sync if invites state is stale.

Recommended: Add a preview/count endpoint or have the API return the count before confirmation.

12. Missing Loading State UI

Location: frontend/src/app/admin/beta-invites/page.tsx:244

While sendingEmail state exists and disables the button, there's no visual loading indicator (spinner) for better UX.

⚠️ Testing & Documentation

13. No Tests

No tests were added for this critical functionality. Consider adding:

  • Unit tests for the API route (mocking Resend and Supabase)
  • Integration tests for the UI component
  • E2E tests for the full flow

14. Missing Documentation

Consider documenting:

  • Email sending limits and best practices
  • Example email templates
  • How to handle bounces/complaints
  • GDPR/privacy considerations

📋 Minor Suggestions

15. Magic Strings

Consider extracting filter options to constants:

const RECIPIENT_FILTERS = {
  ALL: 'all',
  APPROVED: 'approved',
  APPROVED_NOT_USED: 'approved_not_used',
  USED: 'used',
  PENDING: 'pending',
} as const;

16. Confirmation Dialog Improvement

Location: frontend/src/app/admin/beta-invites/page.tsx:150

Using browser confirm() is functional but consider using a custom modal component for better UX and styling consistency.

✅ Positive Aspects

  • Good bilingual support (English/Swedish)
  • Clear user feedback with success/error messages
  • Privacy-conscious use of BCC
  • Reasonable admin-only access approach (once auth is fixed)
  • Good confirmation before sending
  • Proper TypeScript usage

🎯 Summary

Must Fix Before Merge:

  1. ✅ Implement proper authentication (verify JWT token)
  2. ✅ Add input validation and sanitization
  3. ✅ Implement audit logging
  4. ✅ Handle BCC limits with batching

Should Fix:
5. Add rate limiting
6. Add CSRF protection
7. Remove error detail leakage in production
8. Add tests

Nice to Have:
9. Extract shared utility functions
10. Add comprehensive documentation
11. Improve UI with loading states and custom modals

The core functionality is well-structured, but the security issues need to be addressed before this can be safely deployed to production.

Security fixes:
- CRITICAL: Fix admin authorization bypass by verifying session server-side
  instead of trusting client-provided user ID
- Add proper authentication using Supabase SSR auth cookies
- Sanitize HTML content with DOMPurify to prevent XSS attacks
- Remove detailed error messages that could aid attackers
- Add audit logging of admin actions

Changes:
- Use @supabase/ssr createServerClient to verify user session from cookies
- Extract user ID from verified session, not from client request
- Sanitize email HTML with DOMPurify before sending
- Remove sensitive error details from API responses
- Log admin user ID when marketing emails are sent
- Install required packages: @supabase/ssr, isomorphic-dompurify

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 4, 2026

PR Review: Marketing Email Functionality for Beta Users

Summary

This PR adds a marketing email feature allowing admins to send emails to beta users through the Resend API. The implementation includes proper authentication, HTML sanitization, and a user-friendly admin interface.


✅ Strengths

  1. Security Implementation

    • ✅ Proper admin authentication check using service role
    • ✅ HTML sanitization with DOMPurify to prevent XSS attacks
    • ✅ BCC recipients for privacy protection
    • ✅ Validation of recipient filters
  2. User Experience

    • ✅ Bilingual support (English & Swedish) with comprehensive translations
    • ✅ Recipient count preview before sending
    • ✅ Confirmation dialog before sending emails
    • ✅ Clear success/error feedback
    • ✅ Mobile-responsive design
  3. Code Quality

    • ✅ Consistent error handling patterns matching existing codebase
    • ✅ Good logging for debugging and audit trail
    • ✅ Type safety with TypeScript
    • ✅ Clean separation of concerns (API route vs UI component)

🔍 Issues & Recommendations

Critical Issues

1. Rate Limiting Missing 🔴
frontend/src/app/api/send-marketing-email/route.ts:10

  • The endpoint has no rate limiting, which could lead to email spam or abuse
  • Recommendation: Implement rate limiting (e.g., max 1 email blast per hour per admin)
// Consider adding rate limiting middleware or check
const lastSentKey = `marketing_email_sent_${user.id}`;
// Use Redis or database to track last send time

2. Missing Webhook Secret Variable 🔴
frontend/.env.example:40

  • The new API route uses admin authentication but .env.example doesn't include SUPABASE_WEBHOOK_SECRET referenced in other routes
  • Recommendation: Ensure all environment variables are documented in .env.example

Performance Concerns

3. Potential Email Sending Limits 🟡
frontend/src/app/api/send-marketing-email/route.ts:135-141

  • Sending all emails in a single BCC could hit Resend API limits
  • Resend free tier has limits (100 emails/day), and BCC limits may apply
  • Recommendation:
    • Add pagination/batching for large recipient lists
    • Document expected batch size limits
    • Handle partial failures gracefully

4. Missing Error Recovery 🟡
frontend/src/app/api/send-marketing-email/route.ts:143-149

  • If email sending fails, there's no way to retry or track which emails were sent
  • Recommendation: Consider logging sent emails to a database table for audit trail and retry capability

Security Concerns

5. DOMPurify Configuration Could Be Stricter 🟡
frontend/src/app/api/send-marketing-email/route.ts:65-68

  • Allows style and href attributes which could be vectors for attacks
  • Recommendation: Consider restricting further or implementing CSP
const sanitizedHtml = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'img', 'div', 'span', 'table', 'tr', 'td', 'th', 'thead', 'tbody'],
  ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'target'],
  ALLOWED_URI_REGEXP: /^https?:\/\//, // Only allow http/https links
});

6. No Email Content Validation 🟡
frontend/src/app/api/send-marketing-email/route.ts:15-20

  • No checks for email content length or forbidden content
  • Recommendation: Add content length limits and basic validation
if (subject.length > 200) {
  return NextResponse.json({ error: 'Subject too long (max 200 chars)' }, { status: 400 });
}
if (html.length > 100000) {
  return NextResponse.json({ error: 'Email content too long' }, { status: 400 });
}

Code Quality

7. Duplicate Recipient Count Logic 🟡
frontend/src/app/admin/beta-invites/page.tsx:125-143 and page.tsx:391-395

  • Recipient count calculation is duplicated between confirmation logic and display
  • Recommendation: Extract to a helper function
const getRecipientCount = (filter: string, invites: BetaInvite[]) => {
  switch (filter) {
    case 'all': return invites.length;
    case 'approved': return invites.filter(i => i.approved).length;
    // ... etc
  }
};

8. Magic Strings for Recipient Filters 🟡
frontend/src/app/api/send-marketing-email/route.ts:90-111

  • Recipient filter strings are hardcoded in multiple places
  • Recommendation: Use enums or constants
export const RECIPIENT_FILTERS = {
  ALL: 'all',
  APPROVED: 'approved',
  APPROVED_NOT_USED: 'approved_not_used',
  USED: 'used',
  PENDING: 'pending',
} as const;

Missing Features

9. No Audit Trail 🟡

  • No database logging of marketing emails sent
  • Recommendation: Create a marketing_emails table to track:
    • Who sent the email
    • When it was sent
    • Subject and content hash
    • Recipient count and filter used
    • Email delivery status

10. No Email Preview 🟢
frontend/src/app/admin/beta-invites/page.tsx:399-415

  • Users enter raw HTML without preview
  • Recommendation: Add a live preview pane showing rendered HTML (in future PR)

🧪 Testing Concerns

Missing Test Coverage 🔴

  • No unit tests for the API route
  • No integration tests for email sending
  • No tests for the UI component

Recommendations:

// Example test structure needed:
describe('POST /api/send-marketing-email', () => {
  it('should reject unauthenticated requests', async () => { ... });
  it('should reject non-admin users', async () => { ... });
  it('should sanitize HTML content', async () => { ... });
  it('should validate recipient filters', async () => { ... });
  it('should send emails to correct recipients', async () => { ... });
});

📋 Additional Recommendations

  1. Documentation: Add a markdown doc explaining:

    • How to use the marketing email feature
    • Best practices for email content
    • Resend API limits and considerations
    • Rate limiting policies
  2. Email Templates: Consider adding pre-defined email templates for common use cases

  3. Email Scheduling: Consider adding ability to schedule emails for later

  4. Unsubscribe Mechanism: Add unsubscribe functionality for compliance with email regulations (CAN-SPAM, GDPR)

  5. Email Analytics: Track email open rates and clicks (if using Resend webhooks)


🎯 Action Items Before Merge

Must Fix (Blocking):

  • Add rate limiting to prevent abuse
  • Add unit tests for the API route
  • Document the new environment variables

Should Fix (Recommended):

  • Add email content length validation
  • Extract duplicate recipient count logic
  • Add audit trail (database logging)
  • Handle batch sending for large recipient lists

Nice to Have (Future PRs):

  • Email preview functionality
  • Email templates
  • Unsubscribe mechanism
  • Email analytics

📊 Overall Assessment

Code Quality: 7/10
Security: 7/10
Performance: 6/10
Testing: 2/10

This is a solid foundation for the marketing email feature with good security practices (admin auth, XSS prevention, BCC privacy). However, it needs rate limiting, better error handling, and test coverage before production deployment.

The code follows existing patterns in the codebase well and integrates nicely with the bilingual support and admin UI. With the recommended improvements, this will be a robust feature.

Great work on the security-first approach with DOMPurify and admin authentication! 🎉

The SSR cookie-based auth was failing in API routes with "Auth session missing" error.
Switched to using Authorization header with Bearer token instead.

Changes:
- API route now expects Authorization header with Bearer token
- Client-side sends session access_token in Authorization header
- Removed @supabase/ssr dependency in favor of standard supabase-js client
- Added session check on client before making API call

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ramblingpm ramblingpm merged commit f2429b7 into main Jan 5, 2026
3 of 4 checks passed
@ramblingpm ramblingpm deleted the claude/beta-marketing-emails-H1jmQ branch January 5, 2026 00:10
@claude
Copy link

claude bot commented Jan 5, 2026

PR Review: Marketing Email Functionality for Beta Users

Summary

This PR adds marketing email functionality for beta users through the Resend API. The implementation includes proper authentication, HTML sanitization, recipient filtering, and a clean UI. Overall, this is a solid implementation with good security practices.

✅ Strengths

Security

  • Excellent HTML sanitization using DOMPurify with a well-defined allowlist (route.ts:75-78)
  • Proper admin authentication with both token verification and admin role check (route.ts:22-70)
  • BCC for privacy - recipients are hidden from each other (route.ts:148)
  • Authorization header validation on both client and server side

Code Quality

  • Good error handling with descriptive console logs using emojis for easy scanning
  • Bilingual support with comprehensive i18n messages for both English and Swedish
  • Clear confirmation dialogs before sending emails to prevent accidents
  • Proper TypeScript usage throughout

UX

  • Real-time recipient count display helps admins understand impact before sending
  • Multiple filter options for targeting specific user segments
  • Clear success/error feedback to the admin
  • Toggle buttons to switch between forms cleanly

⚠️ Issues & Suggestions

🔴 Critical Issues

1. Information Disclosure in Error Responses

Location: frontend/src/app/api/send-marketing-email/route.ts:25, 41, 47, 64, 69

The API returns debug fields in error responses:

return NextResponse.json({ error: 'Unauthorized', debug: 'Missing auth token' }, { status: 401 });

Issue: Debug information should not be exposed in production as it aids attackers.

Recommendation: Remove debug fields or conditionally include them only in development:

const isDev = process.env.NODE_ENV === 'development';
return NextResponse.json({ 
  error: 'Unauthorized',
  ...(isDev && { debug: 'Missing auth token' })
}, { status: 401 });

2. Resend API Rate Limiting

Location: frontend/src/app/api/send-marketing-email/route.ts:145-151

There's no rate limiting or batch size control when sending emails.

Issues:

  • Could hit Resend API rate limits
  • All emails sent in BCC might be flagged as spam by some providers
  • No handling of partial failures

Recommendations:

  • Add rate limiting middleware or check
  • Consider batch sending for large recipient lists
  • Add retry logic for transient failures
  • Document BCC limits in Resend's documentation

3. Environment Variable Validation

Location: frontend/src/app/api/send-marketing-email/route.ts:86-95

The code checks for RESEND_API_KEY at runtime, but not for SUPABASE_SERVICE_ROLE_KEY.

Issue: If SUPABASE_SERVICE_ROLE_KEY is missing, the error will be cryptic (line 53 uses ! assertion).

Recommendation: Add validation:

if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
  console.error('❌ SUPABASE_SERVICE_ROLE_KEY is not configured!');
  return NextResponse.json({ error: 'Service not configured' }, { status: 500 });
}

🟡 Medium Priority Issues

4. Missing Audit Trail

Location: frontend/src/app/api/send-marketing-email/route.ts:145-169

Marketing emails are sent without creating an audit record in the database.

Recommendations:

  • Create a marketing_emails table to track:
    • Who sent it (admin_user_id)
    • When it was sent
    • Subject and content hash
    • Recipient filter used
    • Number of recipients
    • Resend email ID
    • Success/failure status
  • This is important for compliance, debugging, and preventing abuse

5. No Email Content Validation

Location: frontend/src/app/api/send-marketing-email/route.ts:11-19

Only checks if subject/html exist, not their quality or length.

Recommendations:

  • Add length limits (subject max 255 chars, HTML max 100KB)
  • Validate HTML is not empty after sanitization
  • Check for potentially problematic content

6. Client-Side Import of Supabase Client

Location: frontend/src/app/admin/beta-invites/page.tsx:14

import { supabase } from '@/lib/supabase';

Issue: This appears to be a client-side page importing a supabase client. The new code adds getSession() call which is good, but verify the imported supabase client is properly configured for client-side use.

Recommendation: Ensure you're using @supabase/ssr properly for client-side session management. Consider using the createBrowserClient pattern.

7. Race Condition in Recipient Count

Location: frontend/src/app/admin/beta-invites/page.tsx:126-144

The client calculates recipient count from local state, but the server fetches fresh data.

Issue: If invites change between when the page loads and when the email is sent, the counts could mismatch.

Recommendations:

  • Return actual recipient count from the API for confirmation message
  • Or fetch fresh count before showing confirmation dialog

🟢 Minor Issues / Enhancements

8. Hardcoded Locale

Location: frontend/src/app/api/send-marketing-email/route.ts:81

const locale = defaultLocale;

Suggestion: The app name is fetched but always in default locale. Consider:

  • Getting admin's preferred locale
  • Or making the email language match the recipient's preferred language (would require storing this)

9. Missing Input Sanitization on Client Side

Location: frontend/src/app/admin/beta-invites/page.tsx:113-124

Client validates fields exist but doesn't trim or sanitize.

Suggestion: Add .trim() to subject and HTML to prevent accidental whitespace-only submissions.

10. Unused Locale Import

Location: frontend/src/app/api/send-marketing-email/route.ts:4

import { defaultLocale, Locale, locales } from '@/i18n/config';

The Locale type is imported but not used.

Suggestion: Remove unused import.

11. DOMPurify Configuration Could Be More Restrictive

Location: frontend/src/app/api/send-marketing-email/route.ts:75-78

Allowing style attribute could be risky.

Suggestions:

  • Consider removing style from ALLOWED_ATTR unless absolutely needed
  • Add FORBID_TAGS: ['script', 'object', 'embed', 'iframe'] for extra safety
  • Consider using SAFE_FOR_TEMPLATES: true

12. No Dry-Run/Preview Feature

Enhancement: Consider adding a "Preview" or "Send Test Email" feature that sends to only the admin's email address for testing before sending to all recipients.

📋 Testing Recommendations

The repository has no test files. Consider adding tests for:

  1. API Route Tests:

    • Unauthorized access attempts
    • Invalid recipient filters
    • HTML sanitization edge cases
    • Missing environment variables
    • Resend API failures
  2. Component Tests:

    • Form validation
    • Recipient count calculations
    • Toggle between forms
    • Error/success message display
  3. Integration Tests:

    • Full email sending flow
    • Authentication flow
    • Recipient filtering accuracy

🎯 Performance Considerations

  1. Email Sending: Currently synchronous - consider making it async with job queue for large recipient lists
  2. Recipient Filtering: Filtering happens in database which is good, but consider pagination for very large lists
  3. Client-Side Filtering: Recipient count is calculated client-side by filtering all invites - this is fine for small lists but could be optimized with a count endpoint

📝 Documentation Needs

Consider documenting:

  1. Resend API setup and configuration
  2. BCC limitations and best practices
  3. HTML email template guidelines
  4. Marketing email sending workflow for admins
  5. Environment variables needed (add to .env.example)

🔐 Security Checklist Review

  • ✅ Authentication implemented
  • ✅ Authorization (admin check) implemented
  • ✅ Input sanitization (DOMPurify)
  • ✅ No SQL injection risk (using Supabase client)
  • ⚠️ Information disclosure (debug fields)
  • ⚠️ No rate limiting
  • ⚠️ No audit logging
  • ✅ HTTPS enforced (Next.js default)
  • ✅ Environment variables for secrets

📦 Dependencies

New dependencies added:

  • @supabase/ssr: Good addition for proper SSR support
  • isomorphic-dompurify: Excellent choice for HTML sanitization

Both are well-maintained and appropriate.

🎨 Code Style

Follows existing patterns well:

  • Consistent error logging with emojis
  • Matches authentication pattern from other routes
  • Good TypeScript usage
  • Follows Next.js App Router conventions

Final Verdict

This is a good implementation that demonstrates security awareness and solid engineering. The main concerns are around production hardening (removing debug info, adding rate limiting, and audit logging).

Before Merging:

  1. Remove or conditionally include debug fields in error responses
  2. Add environment variable validation for SUPABASE_SERVICE_ROLE_KEY
  3. Consider implementing audit logging

Post-Merge Improvements:

  1. Add rate limiting
  2. Implement test coverage
  3. Add preview/test email feature
  4. Create audit trail table

Great work overall! 🚀


Review generated by Claude Code

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