diff --git a/app/api/auth/google/callback/route.ts b/app/api/auth/google/callback/route.ts
index 479f46e..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}@localloop.app`
+ 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 73a7d85..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@localloop.app',
+ 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 b4d4099..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@localloop.app',
+ 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 3058b7f..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@localloop.app',
+ 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 e1211c4..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@localloop.app',
+ 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 bdd3927..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@localloop.events
+
{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 533db65..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;
@@ -20,16 +21,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;
}
@@ -167,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,
@@ -273,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,
@@ -341,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@localloop.app
+Have questions? Contact us at ${EMAIL_ADDRESSES.SUPPORT}
---
This email was sent by LocalLoop. You're receiving this because you created an account.
@@ -380,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,
@@ -505,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,
@@ -589,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@localloop.app\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)}
@@ -617,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,
@@ -741,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,
@@ -755,7 +763,7 @@ export async function sendRefundConfirmationEmail(
{ name: 'order', value: props.orderId }
],
// Add reply-to support email for refund inquiries
- replyTo: 'support@localloop.app',
+ replyTo: EMAIL_ADDRESSES.SUPPORT,
});
console.log('Refund confirmation email sent successfully:', {
@@ -829,7 +837,7 @@ REFUND INFORMATION:
QUICK ACTIONS:
• View Event Details: ${eventUrl}
• Browse Other Events: ${baseUrl}/events
-• Contact Support: support@localloop.app
+• Contact Support: ${EMAIL_ADDRESSES.SUPPORT}
${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/send-ticket-confirmation.ts b/lib/emails/send-ticket-confirmation.ts
index 52beca3..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;
@@ -14,16 +15,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;
}
@@ -61,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 86daa2c..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@localloop.app
+ Have questions? Contact us at {EMAIL_ADDRESSES.SUPPORT}
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",
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