// Event-wide bulk email sending via the background queue. import { db, dbGet, dbAll, events, tickets } from '../../db/index.js'; import { eq, and } from 'drizzle-orm'; import { enqueueBulkEmails, type TemplateEmailJobParams } from '../emailQueue.js'; import { getTemplate } from './templateService.js'; import { formatDate, formatTime, getSiteTimezone } from './formatting.js'; /** * Queue emails for event attendees (non-blocking). * Adds all matching recipients to the background email queue and returns immediately. * Rate limiting and actual sending is handled by the email queue. */ export async function queueEventEmails(params: { eventId: string; templateSlug: string; customVariables?: Record; recipientFilter?: 'all' | 'confirmed' | 'pending' | 'checked_in'; sentBy: string; }): Promise<{ success: boolean; queuedCount: number; error?: string }> { const { eventId, templateSlug, customVariables = {}, recipientFilter = 'confirmed', sentBy } = params; // Validate event exists const event = await dbGet( (db as any) .select() .from(events) .where(eq((events as any).id, eventId)) ); if (!event) { return { success: false, queuedCount: 0, error: 'Event not found' }; } // Validate template exists const template = await getTemplate(templateSlug); if (!template) { return { success: false, queuedCount: 0, error: `Template "${templateSlug}" not found` }; } // Get tickets based on filter let ticketQuery = (db as any) .select() .from(tickets) .where(eq((tickets as any).eventId, eventId)); if (recipientFilter !== 'all') { ticketQuery = ticketQuery.where( and( eq((tickets as any).eventId, eventId), eq((tickets as any).status, recipientFilter) ) ); } const eventTickets = await dbAll(ticketQuery); if (eventTickets.length === 0) { return { success: true, queuedCount: 0, error: 'No recipients found' }; } // Get site timezone for proper date/time formatting const timezone = await getSiteTimezone(); // Build individual email jobs for the queue const jobs: TemplateEmailJobParams[] = eventTickets.map((ticket: any) => { const locale = ticket.preferredLanguage || 'en'; const eventTitle = locale === 'es' && event.titleEs ? event.titleEs : event.title; const fullName = `${ticket.attendeeFirstName} ${ticket.attendeeLastName || ''}`.trim(); return { templateSlug, to: ticket.attendeeEmail, toName: fullName, locale, eventId: event.id, sentBy, variables: { attendeeName: fullName, attendeeEmail: ticket.attendeeEmail, ticketId: ticket.id, eventTitle, eventDate: formatDate(event.startDatetime, locale, timezone), eventTime: formatTime(event.startDatetime, locale, timezone), eventLocation: event.location, eventLocationUrl: event.locationUrl || '', ...customVariables, }, }; }); // Enqueue all emails for background processing enqueueBulkEmails(jobs); console.log(`[Email] Queued ${jobs.length} emails for event "${event.title}" (filter: ${recipientFilter})`); return { success: true, queuedCount: jobs.length, }; }