From ff2e18b6c31966ae2cbf1ecb8ea7e195799a54f6 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 19 Jun 2025 05:59:59 +0100 Subject: [PATCH 1/4] feat(email): fix recipient forwarding with robust environment detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace simple NODE_ENV check with more reliable environment detection - Add OVERRIDE_EMAILS_TO_DEV environment variable for explicit control - Use dual check: OVERRIDE_EMAILS_TO_DEV=true AND local development - Prevent email redirection in production even if NODE_ENV is misconfigured - Update both email-service.ts and send-ticket-confirmation.ts This ensures emails always go to the intended recipient in production while maintaining the ability to override for local development testing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/email-service.ts | 13 ++++++++++--- lib/emails/send-ticket-confirmation.ts | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/email-service.ts b/lib/email-service.ts index 533db65..f9b8778 100644 --- a/lib/email-service.ts +++ b/lib/email-service.ts @@ -20,16 +20,23 @@ function getResendInstance(): Resend { return resendInstance; } -// ✨ DEVELOPMENT MODE: Override email for testing with Resend free tier -const isDevelopment = process.env.NODE_ENV === 'development'; +// ✨ EMAIL OVERRIDE CONFIGURATION +// Use dedicated environment variable for email override control +const shouldOverrideEmails = process.env.OVERRIDE_EMAILS_TO_DEV === 'true'; +const isLocalDevelopment = process.env.NODE_ENV === 'development' && process.env.VERCEL_ENV !== 'production'; const devOverrideEmail = 'jackson_rhoden@outlook.com'; // Your verified email // Helper function to get the actual recipient email function getRecipientEmail(originalEmail: string): string { - if (isDevelopment && originalEmail !== devOverrideEmail) { + // Only override if explicitly enabled AND we're in local development + const shouldRedirect = shouldOverrideEmails && isLocalDevelopment; + + if (shouldRedirect && originalEmail !== devOverrideEmail) { console.log(`🔧 DEV MODE: Redirecting email from ${originalEmail} to ${devOverrideEmail}`); return devOverrideEmail; } + + // Always use original email in production or when override is disabled return originalEmail; } diff --git a/lib/emails/send-ticket-confirmation.ts b/lib/emails/send-ticket-confirmation.ts index 52beca3..a5cbe01 100644 --- a/lib/emails/send-ticket-confirmation.ts +++ b/lib/emails/send-ticket-confirmation.ts @@ -14,16 +14,23 @@ function getResendInstance(): Resend { return resendInstance; } -// ✨ DEVELOPMENT MODE: Override email for testing with Resend free tier -const isDevelopment = process.env.NODE_ENV === 'development'; +// ✨ EMAIL OVERRIDE CONFIGURATION +// Use dedicated environment variable for email override control +const shouldOverrideEmails = process.env.OVERRIDE_EMAILS_TO_DEV === 'true'; +const isLocalDevelopment = process.env.NODE_ENV === 'development' && process.env.VERCEL_ENV !== 'production'; const devOverrideEmail = 'jackson_rhoden@outlook.com'; // Your verified email // Helper function to get the actual recipient email function getRecipientEmail(originalEmail: string): string { - if (isDevelopment && originalEmail !== devOverrideEmail) { + // Only override if explicitly enabled AND we're in local development + const shouldRedirect = shouldOverrideEmails && isLocalDevelopment; + + if (shouldRedirect && originalEmail !== devOverrideEmail) { console.log(`🔧 DEV MODE: Redirecting email from ${originalEmail} to ${devOverrideEmail}`); return devOverrideEmail; } + + // Always use original email in production or when override is disabled return originalEmail; } From 13d9f809d20a8f013780ff67f516332281e48648 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 19 Jun 2025 10:44:10 +0100 Subject: [PATCH 2/4] feat(email): configure custom domain for email sending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update RESEND_FROM_EMAIL to use custom domain noreply@localloopevents.xyz - Update NEXT_PUBLIC_APP_URL to use new production domain - Remove email override for proper user email delivery - Clean up test scripts This enables free email sending to any recipient using verified domain. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- next.config.ts | 2 +- package-lock.json | 3 ++- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/next.config.ts b/next.config.ts index e1d08f9..2c2ec5a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -104,7 +104,7 @@ const nextConfig: NextConfig = { // Force consistent port and set dynamic environment env: { NEXT_PUBLIC_APP_URL: process.env.NODE_ENV === 'production' - ? 'https://local-loop-qa.vercel.app' + ? 'https://localloopevents.xyz' : 'http://localhost:3000', }, diff --git a/package-lock.json b/package-lock.json index f6e512a..c3a681a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "dotenv": "^16.5.0", "google-auth-library": "^9.15.1", "googleapis": "^149.0.0", "lucide-react": "^0.511.0", @@ -50,6 +49,7 @@ "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", + "dotenv": "^16.5.0", "eslint": "^9.16.0", "eslint-config-next": "15.3.2", "husky": "^9.1.7", @@ -5820,6 +5820,7 @@ "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 532d25a..862a636 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "dotenv": "^16.5.0", "google-auth-library": "^9.15.1", "googleapis": "^149.0.0", "lucide-react": "^0.511.0", @@ -91,6 +90,7 @@ "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", + "dotenv": "^16.5.0", "eslint": "^9.16.0", "eslint-config-next": "15.3.2", "husky": "^9.1.7", From 3e0674c387b58597fa439c4ba1f9da2af61a2068 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 19 Jun 2025 10:53:34 +0100 Subject: [PATCH 3/4] feat(branding): update all contact emails to localloopevents.xyz domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update contact page email: hello@localloop.events → hello@localloopevents.xyz - Update all support emails: support@localloop.app → support@localloopevents.xyz - Update system emails: noreply@localloop.app → noreply@localloopevents.xyz - Update organizer emails: organizer@localloop.app → organizer@localloopevents.xyz - Update from email configuration to use hello@localloopevents.xyz - Update email templates, API routes, and documentation - Ensure consistent branding across all email communications This provides a professional, consistent email experience using the verified custom domain. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/api/auth/google/callback/route.ts | 2 +- app/api/events/cancellation/route.ts | 2 +- app/api/events/reminders/route.ts | 2 +- app/api/rsvps/[id]/route.ts | 2 +- app/api/rsvps/route.ts | 2 +- app/contact/page.tsx | 2 +- lib/email-service.ts | 20 ++++++++++---------- lib/emails/event-cancellation.tsx | 2 +- lib/emails/welcome-email.tsx | 2 +- tests/load/README.md | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/api/auth/google/callback/route.ts b/app/api/auth/google/callback/route.ts index 479f46e..f6515df 100644 --- a/app/api/auth/google/callback/route.ts +++ b/app/api/auth/google/callback/route.ts @@ -223,7 +223,7 @@ async function ensureUserRecord(userId: string) { .eq('id', userId) .single() - const userEmail = authUser?.email || `user-${userId}@localloop.app` + const userEmail = authUser?.email || `user-${userId}@localloopevents.xyz` const userName = authUser?.raw_user_meta_data?.full_name || authUser?.raw_user_meta_data?.name || 'User' console.log(`[DEBUG] Retrieved user data: email=${userEmail}, name=${userName}`) diff --git a/app/api/events/cancellation/route.ts b/app/api/events/cancellation/route.ts index 73a7d85..b5b66ab 100644 --- a/app/api/events/cancellation/route.ts +++ b/app/api/events/cancellation/route.ts @@ -207,7 +207,7 @@ export async function POST(request: NextRequest) { eventLocation: event.location, eventAddress: event.location_details || event.location, organizerName: organizerData?.display_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloop.app', + organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', cancellationReason: cancellation_reason, refundAmount: 0, // Assuming refundAmount is not provided in the attendees refundTimeframe: refund_timeframe, diff --git a/app/api/events/reminders/route.ts b/app/api/events/reminders/route.ts index b4d4099..500d9ad 100644 --- a/app/api/events/reminders/route.ts +++ b/app/api/events/reminders/route.ts @@ -241,7 +241,7 @@ export async function POST(request: NextRequest) { eventLocation: event.location, eventAddress: event.location_details || event.location, organizerName: organizerData?.display_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloop.app', + organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', rsvpId: attendee.rsvp_id, isTicketHolder: attendee.type === 'ticket', ticketCount: attendee.ticket_count, diff --git a/app/api/rsvps/[id]/route.ts b/app/api/rsvps/[id]/route.ts index 3058b7f..b8f03e1 100644 --- a/app/api/rsvps/[id]/route.ts +++ b/app/api/rsvps/[id]/route.ts @@ -205,7 +205,7 @@ export async function PATCH( eventLocation: eventDetails.location, eventAddress: eventDetails.address || eventDetails.location, organizerName: organizerData?.full_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloop.app', + organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', rsvpId: updatedRsvp.id, cancellationReason: updateData.notes || undefined, eventSlug: eventDetails.slug diff --git a/app/api/rsvps/route.ts b/app/api/rsvps/route.ts index e1211c4..1eee4a2 100644 --- a/app/api/rsvps/route.ts +++ b/app/api/rsvps/route.ts @@ -382,7 +382,7 @@ export async function POST(request: NextRequest) { eventLocation: event.location, eventAddress: event.location_details || event.location, organizerName: organizerData?.display_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloop.app', + organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', rsvpId: newRsvp.id, guestCount: 1, isAuthenticated: !!user, diff --git a/app/contact/page.tsx b/app/contact/page.tsx index bdd3927..b3ea94c 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -26,7 +26,7 @@ export default function ContactPage() {

Email

-

hello@localloop.events

+

hello@localloopevents.xyz

diff --git a/lib/email-service.ts b/lib/email-service.ts index f9b8778..cd04b43 100644 --- a/lib/email-service.ts +++ b/lib/email-service.ts @@ -174,7 +174,7 @@ export async function sendRSVPConfirmationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', to: getRecipientEmail(props.to), subject: `RSVP Confirmed: ${props.eventTitle}`, html: emailHtml, @@ -280,7 +280,7 @@ export async function sendWelcomeEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', to: getRecipientEmail(props.to), subject: 'Welcome to LocalLoop! 🎉', html: emailHtml, @@ -348,7 +348,7 @@ QUICK ACTIONS: NEED HELP GETTING STARTED? Visit your My Events page to manage your RSVPs and created events: ${baseUrl}/my-events -Have questions? Contact us at support@localloop.app +Have questions? Contact us at support@localloopevents.xyz --- This email was sent by LocalLoop. You're receiving this because you created an account. @@ -387,7 +387,7 @@ export async function sendEventReminderEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', to: getRecipientEmail(props.to), subject: getReminderSubject(), html: emailHtml, @@ -512,7 +512,7 @@ export async function sendEventCancellationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', to: getRecipientEmail(props.to), subject: `Event Cancelled: ${props.eventTitle}`, html: emailHtml, @@ -596,7 +596,7 @@ QUICK ACTIONS: • Contact Organizer: ${props.organizerEmail} Questions about this cancellation? Contact the event organizer: ${props.organizerName} at ${props.organizerEmail} -${props.isTicketHolder ? `For refund inquiries, please contact: support@localloop.app\n` : ''} +${props.isTicketHolder ? `For refund inquiries, please contact: support@localloopevents.xyz\n` : ''} --- This cancellation notice was sent by LocalLoop on behalf of ${props.organizerName}. Unsubscribe: ${baseUrl}/unsubscribe?email=${encodeURIComponent(props.userEmail)} @@ -624,7 +624,7 @@ export async function sendRSVPCancellationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', to: getRecipientEmail(props.to), subject: `RSVP Cancelled: ${props.eventTitle}`, html: emailHtml, @@ -748,7 +748,7 @@ export async function sendRefundConfirmationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', to: getRecipientEmail(props.to), subject: subject, html: emailHtml, @@ -762,7 +762,7 @@ export async function sendRefundConfirmationEmail( { name: 'order', value: props.orderId } ], // Add reply-to support email for refund inquiries - replyTo: 'support@localloop.app', + replyTo: 'support@localloopevents.xyz', }); console.log('Refund confirmation email sent successfully:', { @@ -836,7 +836,7 @@ REFUND INFORMATION: QUICK ACTIONS: • View Event Details: ${eventUrl} • Browse Other Events: ${baseUrl}/events -• Contact Support: support@localloop.app +• Contact Support: support@localloopevents.xyz ${isEventCancellation ? 'We apologize for any inconvenience caused by the event cancellation.' diff --git a/lib/emails/event-cancellation.tsx b/lib/emails/event-cancellation.tsx index cb1d7dc..01b88c3 100644 --- a/lib/emails/event-cancellation.tsx +++ b/lib/emails/event-cancellation.tsx @@ -216,7 +216,7 @@ export const EventCancellationEmail = ({ {isTicketHolder && ( - For refund inquiries, please contact: support@localloop.app + For refund inquiries, please contact: support@localloopevents.xyz )} diff --git a/lib/emails/welcome-email.tsx b/lib/emails/welcome-email.tsx index 86daa2c..79444d5 100644 --- a/lib/emails/welcome-email.tsx +++ b/lib/emails/welcome-email.tsx @@ -122,7 +122,7 @@ export const WelcomeEmail = ({ - Have questions? Contact us at support@localloop.app + Have questions? Contact us at support@localloopevents.xyz
diff --git a/tests/load/README.md b/tests/load/README.md index a9088dd..6602cd0 100644 --- a/tests/load/README.md +++ b/tests/load/README.md @@ -78,10 +78,10 @@ Set the base URL for different environments: BASE_URL=http://localhost:3000 npm run load-test # Staging environment -BASE_URL=https://staging.localloop.app npm run load-test +BASE_URL=https://staging.localloopevents.xyz npm run load-test # Production (use with extreme caution!) -BASE_URL=https://localloop.app npm run load-test +BASE_URL=https://localloopevents.xyz npm run load-test ``` ## 📈 Understanding Results From 9435b1cbda4c88278bbf5616d4df25993c5f5248 Mon Sep 17 00:00:00 2001 From: Jackson Date: Thu, 19 Jun 2025 11:07:07 +0100 Subject: [PATCH 4/4] refactor(email): centralize email address configuration for maintainability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create centralized email configuration in lib/config/email-addresses.ts - Define all email addresses as constants with proper subdomain structure: * SYSTEM_FROM: noreply@localloopevents.xyz (for automated emails) * CONTACT: hello@localloopevents.xyz (for customer contact) * SUPPORT: support@localloopevents.xyz (for support inquiries) * ORGANIZER: organizer@localloopevents.xyz (for event organizers) - Restore RESEND_FROM_EMAIL to noreply@localloopevents.xyz for system messages - Update all files to import and use centralized EMAIL_ADDRESSES: * Contact page now uses EMAIL_ADDRESSES.CONTACT * Email service uses EMAIL_ADDRESSES for all email types * Email templates use centralized support addresses * API routes use EMAIL_ADDRESSES.ORGANIZER and generateUserEmail() - Maintain proper subdomain separation: * noreply@ for system/automated emails * hello@ for customer contact * support@ for customer support * organizer@ for event organizer fallbacks This provides a single source of truth for all email addresses, making domain changes and email management much easier to maintain. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/api/auth/google/callback/route.ts | 3 ++- app/api/events/cancellation/route.ts | 3 ++- app/api/events/reminders/route.ts | 3 ++- app/api/rsvps/[id]/route.ts | 7 ++++--- app/api/rsvps/route.ts | 3 ++- app/contact/page.tsx | 3 ++- lib/config/email-addresses.ts | 28 ++++++++++++++++++++++++++ lib/email-service.ts | 21 ++++++++++--------- lib/emails/send-ticket-confirmation.ts | 3 ++- lib/emails/welcome-email.tsx | 3 ++- 10 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 lib/config/email-addresses.ts diff --git a/app/api/auth/google/callback/route.ts b/app/api/auth/google/callback/route.ts index f6515df..b0e7ca8 100644 --- a/app/api/auth/google/callback/route.ts +++ b/app/api/auth/google/callback/route.ts @@ -5,6 +5,7 @@ import { parseOAuthState, } from '@/lib/google-auth' import { createGoogleCalendarService, GoogleCalendarTokens } from '@/lib/google-calendar' +import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses' /** * API Route: /api/auth/google/callback @@ -223,7 +224,7 @@ async function ensureUserRecord(userId: string) { .eq('id', userId) .single() - const userEmail = authUser?.email || `user-${userId}@localloopevents.xyz` + const userEmail = authUser?.email || EMAIL_ADDRESSES.generateUserEmail(userId) const userName = authUser?.raw_user_meta_data?.full_name || authUser?.raw_user_meta_data?.name || 'User' console.log(`[DEBUG] Retrieved user data: email=${userEmail}, name=${userName}`) diff --git a/app/api/events/cancellation/route.ts b/app/api/events/cancellation/route.ts index b5b66ab..1514da5 100644 --- a/app/api/events/cancellation/route.ts +++ b/app/api/events/cancellation/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { createServerSupabaseClient } from '@/lib/supabase-server' import { sendEventCancellationEmail } from '@/lib/email-service' +import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses' import { z } from 'zod' // Event cancellation request schema @@ -207,7 +208,7 @@ export async function POST(request: NextRequest) { eventLocation: event.location, eventAddress: event.location_details || event.location, organizerName: organizerData?.display_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', + organizerEmail: organizerData?.email || EMAIL_ADDRESSES.ORGANIZER, cancellationReason: cancellation_reason, refundAmount: 0, // Assuming refundAmount is not provided in the attendees refundTimeframe: refund_timeframe, diff --git a/app/api/events/reminders/route.ts b/app/api/events/reminders/route.ts index 500d9ad..aac3bd5 100644 --- a/app/api/events/reminders/route.ts +++ b/app/api/events/reminders/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { createServerSupabaseClient } from '@/lib/supabase-server' import { sendEventReminderEmail } from '@/lib/email-service' +import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses' import { z } from 'zod' // Event reminder request schema @@ -241,7 +242,7 @@ export async function POST(request: NextRequest) { eventLocation: event.location, eventAddress: event.location_details || event.location, organizerName: organizerData?.display_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', + organizerEmail: organizerData?.email || EMAIL_ADDRESSES.ORGANIZER, rsvpId: attendee.rsvp_id, isTicketHolder: attendee.type === 'ticket', ticketCount: attendee.ticket_count, diff --git a/app/api/rsvps/[id]/route.ts b/app/api/rsvps/[id]/route.ts index b8f03e1..0df75aa 100644 --- a/app/api/rsvps/[id]/route.ts +++ b/app/api/rsvps/[id]/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { createServerSupabaseClient } from '@/lib/supabase-server' import { sendRSVPCancellationEmail } from '@/lib/email-service' +import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses' import { z } from 'zod' // RSVP update schema @@ -173,8 +174,8 @@ export async function PATCH( : existingRsvp.guest_name || 'Guest'; const userEmail = existingRsvp.user_id - ? user?.email || 'unknown@email.com' - : existingRsvp.guest_email || 'unknown@email.com'; + ? user?.email || EMAIL_ADDRESSES.SYSTEM_FROM + : existingRsvp.guest_email || EMAIL_ADDRESSES.SYSTEM_FROM; // Handle organizer data from Supabase join const organizer = Array.isArray(eventDetails.users) ? eventDetails.users[0] : eventDetails.users; @@ -205,7 +206,7 @@ export async function PATCH( eventLocation: eventDetails.location, eventAddress: eventDetails.address || eventDetails.location, organizerName: organizerData?.full_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', + organizerEmail: organizerData?.email || EMAIL_ADDRESSES.ORGANIZER, rsvpId: updatedRsvp.id, cancellationReason: updateData.notes || undefined, eventSlug: eventDetails.slug diff --git a/app/api/rsvps/route.ts b/app/api/rsvps/route.ts index 1eee4a2..c080e8b 100644 --- a/app/api/rsvps/route.ts +++ b/app/api/rsvps/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { createServerSupabaseClient } from '@/lib/supabase-server' import { sendRSVPConfirmationEmail } from '@/lib/email-service' +import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses' import { z } from 'zod' // Performance optimization: Simple in-memory cache for RSVP checks (5 minutes) @@ -382,7 +383,7 @@ export async function POST(request: NextRequest) { eventLocation: event.location, eventAddress: event.location_details || event.location, organizerName: organizerData?.display_name || 'Event Organizer', - organizerEmail: organizerData?.email || 'organizer@localloopevents.xyz', + organizerEmail: organizerData?.email || EMAIL_ADDRESSES.ORGANIZER, rsvpId: newRsvp.id, guestCount: 1, isAuthenticated: !!user, diff --git a/app/contact/page.tsx b/app/contact/page.tsx index b3ea94c..c3a8af6 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -1,5 +1,6 @@ import { Footer } from '@/components/ui/Footer'; import { Mail, Phone, MessageCircle } from 'lucide-react'; +import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses'; export default function ContactPage() { return ( @@ -26,7 +27,7 @@ export default function ContactPage() {

Email

-

hello@localloopevents.xyz

+

{EMAIL_ADDRESSES.CONTACT}

diff --git a/lib/config/email-addresses.ts b/lib/config/email-addresses.ts new file mode 100644 index 0000000..62ad53d --- /dev/null +++ b/lib/config/email-addresses.ts @@ -0,0 +1,28 @@ +// Email address configuration - single source of truth for all LocalLoop emails +// Update the domain here to change all email addresses across the application + +const EMAIL_DOMAIN = 'localloopevents.xyz'; + +export const EMAIL_ADDRESSES = { + // System emails (automated messages) + SYSTEM_FROM: `noreply@${EMAIL_DOMAIN}`, + + // Contact and support emails + CONTACT: `hello@${EMAIL_DOMAIN}`, + SUPPORT: `support@${EMAIL_DOMAIN}`, + + // Organizational emails + ORGANIZER: `organizer@${EMAIL_DOMAIN}`, + + // Utility function to generate user emails + generateUserEmail: (userId: string) => `user-${userId}@${EMAIL_DOMAIN}`, +} as const; + +// Legacy aliases for backwards compatibility (if needed) +export const LEGACY_EMAIL_ADDRESSES = { + HELLO: EMAIL_ADDRESSES.CONTACT, + NOREPLY: EMAIL_ADDRESSES.SYSTEM_FROM, +} as const; + +// Export domain for other configurations +export const EMAIL_DOMAIN_CONFIG = EMAIL_DOMAIN; \ No newline at end of file diff --git a/lib/email-service.ts b/lib/email-service.ts index cd04b43..f0f098b 100644 --- a/lib/email-service.ts +++ b/lib/email-service.ts @@ -6,6 +6,7 @@ import WelcomeEmail from './emails/welcome-email'; import EventReminderEmail from './emails/event-reminder'; import EventCancellationEmail from './emails/event-cancellation'; import RefundConfirmationEmail from './emails/templates/RefundConfirmationEmail'; +import { EMAIL_ADDRESSES } from './config/email-addresses'; // Lazy-initialize Resend to prevent build-time failures let resendInstance: Resend | null = null; @@ -174,7 +175,7 @@ export async function sendRSVPConfirmationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: getRecipientEmail(props.to), subject: `RSVP Confirmed: ${props.eventTitle}`, html: emailHtml, @@ -280,7 +281,7 @@ export async function sendWelcomeEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: getRecipientEmail(props.to), subject: 'Welcome to LocalLoop! 🎉', html: emailHtml, @@ -348,7 +349,7 @@ QUICK ACTIONS: NEED HELP GETTING STARTED? Visit your My Events page to manage your RSVPs and created events: ${baseUrl}/my-events -Have questions? Contact us at support@localloopevents.xyz +Have questions? Contact us at ${EMAIL_ADDRESSES.SUPPORT} --- This email was sent by LocalLoop. You're receiving this because you created an account. @@ -387,7 +388,7 @@ export async function sendEventReminderEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: getRecipientEmail(props.to), subject: getReminderSubject(), html: emailHtml, @@ -512,7 +513,7 @@ export async function sendEventCancellationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: getRecipientEmail(props.to), subject: `Event Cancelled: ${props.eventTitle}`, html: emailHtml, @@ -596,7 +597,7 @@ QUICK ACTIONS: • Contact Organizer: ${props.organizerEmail} Questions about this cancellation? Contact the event organizer: ${props.organizerName} at ${props.organizerEmail} -${props.isTicketHolder ? `For refund inquiries, please contact: support@localloopevents.xyz\n` : ''} +${props.isTicketHolder ? `For refund inquiries, please contact: ${EMAIL_ADDRESSES.SUPPORT}\n` : ''} --- This cancellation notice was sent by LocalLoop on behalf of ${props.organizerName}. Unsubscribe: ${baseUrl}/unsubscribe?email=${encodeURIComponent(props.userEmail)} @@ -624,7 +625,7 @@ export async function sendRSVPCancellationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: getRecipientEmail(props.to), subject: `RSVP Cancelled: ${props.eventTitle}`, html: emailHtml, @@ -748,7 +749,7 @@ export async function sendRefundConfirmationEmail( // Send the email const response = await getResendInstance().emails.send({ - from: process.env.RESEND_FROM_EMAIL || 'LocalLoop ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: getRecipientEmail(props.to), subject: subject, html: emailHtml, @@ -762,7 +763,7 @@ export async function sendRefundConfirmationEmail( { name: 'order', value: props.orderId } ], // Add reply-to support email for refund inquiries - replyTo: 'support@localloopevents.xyz', + replyTo: EMAIL_ADDRESSES.SUPPORT, }); console.log('Refund confirmation email sent successfully:', { @@ -836,7 +837,7 @@ REFUND INFORMATION: QUICK ACTIONS: • View Event Details: ${eventUrl} • Browse Other Events: ${baseUrl}/events -• Contact Support: support@localloopevents.xyz +• Contact Support: ${EMAIL_ADDRESSES.SUPPORT} ${isEventCancellation ? 'We apologize for any inconvenience caused by the event cancellation.' diff --git a/lib/emails/send-ticket-confirmation.ts b/lib/emails/send-ticket-confirmation.ts index a5cbe01..054fe87 100644 --- a/lib/emails/send-ticket-confirmation.ts +++ b/lib/emails/send-ticket-confirmation.ts @@ -1,5 +1,6 @@ import { Resend } from 'resend'; import { TicketConfirmationEmail } from './templates/TicketConfirmationEmail'; +import { EMAIL_ADDRESSES } from '../config/email-addresses'; // Lazy-initialize Resend to prevent build-time failures let resendInstance: Resend | null = null; @@ -68,7 +69,7 @@ export async function sendTicketConfirmationEmail({ try { const resend = getResendInstance(); const { data, error } = await resend.emails.send({ - from: 'LocalLoop Events ', + from: process.env.RESEND_FROM_EMAIL || `LocalLoop <${EMAIL_ADDRESSES.SYSTEM_FROM}>`, to: [getRecipientEmail(to)], subject: `Ticket Confirmation - ${eventTitle}`, react: TicketConfirmationEmail({ diff --git a/lib/emails/welcome-email.tsx b/lib/emails/welcome-email.tsx index 79444d5..733dcea 100644 --- a/lib/emails/welcome-email.tsx +++ b/lib/emails/welcome-email.tsx @@ -13,6 +13,7 @@ import { Heading, } from '@react-email/components'; import * as React from 'react'; +import { EMAIL_ADDRESSES } from '../config/email-addresses'; interface WelcomeEmailProps { userName: string; @@ -122,7 +123,7 @@ export const WelcomeEmail = ({ - Have questions? Contact us at support@localloopevents.xyz + Have questions? Contact us at {EMAIL_ADDRESSES.SUPPORT}