From b7b5a599964587931f26db70330a3f204f96e219 Mon Sep 17 00:00:00 2001 From: Akshay Date: Fri, 7 Nov 2025 11:21:58 +0530 Subject: [PATCH] feat(support): Enhance support ticket email notifications and workflows - Add comprehensive email notification system for support tickets - Implement user confirmation emails for bug reports and contact requests - Add support team notification emails for new tickets - Extend ticket status update email notifications - Improve error handling and logging for email sending processes - Utilize new support email utility functions for consistent communication - Add dynamic email generation based on ticket type and status Enhances user communication and provides better transparency in the support ticket lifecycle by implementing robust email notification mechanisms. --- app/api/admin/support/tickets/[id]/route.ts | 34 +++ app/api/support/bug-report/route.ts | 59 ++++- app/api/support/contact/route.ts | 59 ++++- lib/email/support-emails.ts | 280 ++++++++++++++++++++ 4 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 lib/email/support-emails.ts diff --git a/app/api/admin/support/tickets/[id]/route.ts b/app/api/admin/support/tickets/[id]/route.ts index 75c253d0..fc870151 100644 --- a/app/api/admin/support/tickets/[id]/route.ts +++ b/app/api/admin/support/tickets/[id]/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { createClient } from '@/lib/supabase/server' +import { getStatusUpdateEmail, sendEmail } from '@/lib/email/support-emails' export async function GET( request: NextRequest, @@ -88,6 +89,19 @@ export async function PATCH( return NextResponse.json({ error: 'Invalid status' }, { status: 400 }) } + // Get current ticket to check old status + const { data: currentTicket } = await supabase + .from('support_tickets') + .select('*, user:profiles!user_id(email, first_name, last_name)') + .eq('id', id) + .single() + + if (!currentTicket) { + return NextResponse.json({ error: 'Ticket not found' }, { status: 404 }) + } + + const oldStatus = currentTicket.status + // Update ticket status const { data: ticket, error } = await supabase .from('support_tickets') @@ -101,6 +115,26 @@ export async function PATCH( return NextResponse.json({ error: 'Failed to update ticket' }, { status: 500 }) } + // Send status update email to user (only if status actually changed) + if (oldStatus !== status && currentTicket.user) { + const userName = currentTicket.user.first_name || currentTicket.user.email?.split('@')[0] || 'User' + const userEmail = currentTicket.user.email || '' + + const statusEmail = getStatusUpdateEmail({ + userName, + ticketId: ticket.id, + subject: ticket.subject, + oldStatus, + newStatus: status + }) + + await sendEmail({ + to: userEmail, + subject: statusEmail.subject, + html: statusEmail.html + }) + } + return NextResponse.json({ ticket }) } catch (error) { console.error('Error in ticket update API:', error) diff --git a/app/api/support/bug-report/route.ts b/app/api/support/bug-report/route.ts index 8e32dc5c..cfd41ac6 100644 --- a/app/api/support/bug-report/route.ts +++ b/app/api/support/bug-report/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { createClient } from '@/lib/supabase/server' +import { getUserConfirmationEmail, getSupportTeamNotificationEmail, sendEmail } from '@/lib/email/support-emails' export async function POST(request: NextRequest) { try { @@ -18,8 +19,15 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'Title and description are required' }, { status: 400 }) } + // Get user profile + const { data: profile } = await supabase + .from('profiles') + .select('email, first_name, last_name') + .eq('id', user.id) + .single() + // Insert bug report into database - const { error: insertError } = await supabase + const { data: ticket, error: insertError } = await supabase .from('support_tickets') .insert({ user_id: user.id, @@ -28,12 +36,61 @@ export async function POST(request: NextRequest) { message: description, status: 'open', }) + .select() + .single() if (insertError) { console.error('Error creating bug report:', insertError) return NextResponse.json({ error: 'Failed to submit bug report' }, { status: 500 }) } + // Send confirmation email to user + const userName = profile?.first_name || profile?.email?.split('@')[0] || 'User' + const userEmail = profile?.email || user.email || '' + + const confirmationEmail = getUserConfirmationEmail({ + userName, + ticketId: ticket.id, + ticketType: 'bug', + subject: title, + message: description + }) + + await sendEmail({ + to: userEmail, + subject: confirmationEmail.subject, + html: confirmationEmail.html + }) + + // Send notification to support team + const supportEmail = getSupportTeamNotificationEmail({ + ticketId: ticket.id, + ticketType: 'bug', + subject: title, + message: description, + userEmail, + userName: `${profile?.first_name || ''} ${profile?.last_name || ''}`.trim() || userName + }) + + // Get support email(s) - can be comma-separated for multiple recipients + const supportEmailAddress = process.env.SUPPORT_EMAIL + + if (!supportEmailAddress) { + console.warn('⚠️ SUPPORT_EMAIL not configured in environment variables') + } else { + console.log('📧 Sending support team notification to:', supportEmailAddress) + + const supportEmailResult = await sendEmail({ + to: supportEmailAddress, + subject: supportEmail.subject, + html: supportEmail.html + }) + + if (!supportEmailResult.success) { + console.error('⚠️ Failed to send support team notification:', supportEmailResult.error) + } + } + return NextResponse.json({ success: true, message: 'Bug report submitted successfully' }) } catch (error) { console.error('Error in bug report API:', error) diff --git a/app/api/support/contact/route.ts b/app/api/support/contact/route.ts index 34f94240..a29418eb 100644 --- a/app/api/support/contact/route.ts +++ b/app/api/support/contact/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { createClient } from '@/lib/supabase/server' +import { getUserConfirmationEmail, getSupportTeamNotificationEmail, sendEmail } from '@/lib/email/support-emails' export async function POST(request: NextRequest) { try { @@ -18,8 +19,15 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'Subject and message are required' }, { status: 400 }) } + // Get user profile + const { data: profile } = await supabase + .from('profiles') + .select('email, first_name, last_name') + .eq('id', user.id) + .single() + // Insert contact request into database - const { error: insertError } = await supabase + const { data: ticket, error: insertError } = await supabase .from('support_tickets') .insert({ user_id: user.id, @@ -28,12 +36,61 @@ export async function POST(request: NextRequest) { message, status: 'open', }) + .select() + .single() if (insertError) { console.error('Error creating support ticket:', insertError) return NextResponse.json({ error: 'Failed to submit contact request' }, { status: 500 }) } + // Send confirmation email to user + const userName = profile?.first_name || profile?.email?.split('@')[0] || 'User' + const userEmail = profile?.email || user.email || '' + + const confirmationEmail = getUserConfirmationEmail({ + userName, + ticketId: ticket.id, + ticketType: 'contact', + subject, + message + }) + + await sendEmail({ + to: userEmail, + subject: confirmationEmail.subject, + html: confirmationEmail.html + }) + + // Send notification to support team + const supportEmail = getSupportTeamNotificationEmail({ + ticketId: ticket.id, + ticketType: 'contact', + subject, + message, + userEmail, + userName: `${profile?.first_name || ''} ${profile?.last_name || ''}`.trim() || userName + }) + + // Get support email(s) - can be comma-separated for multiple recipients + const supportEmailAddress = process.env.SUPPORT_EMAIL + + if (!supportEmailAddress) { + console.warn('⚠️ SUPPORT_EMAIL not configured in environment variables') + } else { + console.log('📧 Sending support team notification to:', supportEmailAddress) + + const supportEmailResult = await sendEmail({ + to: supportEmailAddress, + subject: supportEmail.subject, + html: supportEmail.html + }) + + if (!supportEmailResult.success) { + console.error('⚠️ Failed to send support team notification:', supportEmailResult.error) + } + } + return NextResponse.json({ success: true, message: 'Contact request submitted successfully' }) } catch (error) { console.error('Error in contact API:', error) diff --git a/lib/email/support-emails.ts b/lib/email/support-emails.ts new file mode 100644 index 00000000..ea9b03dd --- /dev/null +++ b/lib/email/support-emails.ts @@ -0,0 +1,280 @@ +// Email templates and sending functions for support tickets + +interface EmailParams { + to: string + subject: string + html: string +} + +// Base email template +const getEmailTemplate = (content: string) => ` + + + + + + Codeunia Support + + + + + + +
+ + + + + + + + + + + + + + + +
+

Codeunia Support

+
+ ${content} +
+

+ Need help? Reply to this email or visit our Help Center +

+

+ © ${new Date().getFullYear()} Codeunia. All rights reserved. +

+
+
+ + +` + +// 1. User confirmation email +export const getUserConfirmationEmail = (params: { + userName: string + ticketId: string + ticketType: 'contact' | 'bug' + subject: string + message: string +}) => { + const content = ` +

+ We've received your ${params.ticketType === 'bug' ? 'bug report' : 'support request'} +

+ +

+ Hi ${params.userName}, +

+ +

+ Thank you for contacting Codeunia Support. We've received your ${params.ticketType === 'bug' ? 'bug report' : 'message'} and our team will review it shortly. +

+ +
+

+ Ticket ID: ${params.ticketId} +

+

+ ${params.subject} +

+

+ ${params.message.substring(0, 200)}${params.message.length > 200 ? '...' : ''} +

+
+ +

+ What happens next? +

+ + + +

+ You can track your ticket status in your Help Center. +

+ ` + + return { + subject: `[Ticket #${params.ticketId.substring(0, 8)}] We've received your ${params.ticketType === 'bug' ? 'bug report' : 'support request'}`, + html: getEmailTemplate(content) + } +} + +// 2. Support team notification email +export const getSupportTeamNotificationEmail = (params: { + ticketId: string + ticketType: 'contact' | 'bug' + subject: string + message: string + userEmail: string + userName: string +}) => { + const content = ` +

+ New ${params.ticketType === 'bug' ? 'Bug Report' : 'Support Ticket'} +

+ +
+

+ Ticket ID: ${params.ticketId} +

+

+ ${params.subject} +

+

+ ${params.message} +

+
+ + + + + + + + + + + + + + +
+ From: + + ${params.userName} (${params.userEmail}) +
+ Type: + + ${params.ticketType === 'bug' ? 'Bug Report' : 'Support Request'} +
+ Status: + + OPEN +
+ + + View Ticket in Admin Panel + + ` + + return { + subject: `[New Ticket] ${params.ticketType === 'bug' ? '🐛 Bug Report' : '💬 Support Request'}: ${params.subject}`, + html: getEmailTemplate(content) + } +} + +// 3. Status update email to user +export const getStatusUpdateEmail = (params: { + userName: string + ticketId: string + subject: string + oldStatus: string + newStatus: string +}) => { + const statusColors: Record = { + open: { bg: '#fef2f2', text: '#ef4444' }, + in_progress: { bg: '#fef3c7', text: '#f59e0b' }, + resolved: { bg: '#d1fae5', text: '#10b981' }, + closed: { bg: '#f3f4f6', text: '#6b7280' } + } + + const statusColor = statusColors[params.newStatus] || statusColors.open + + const content = ` +

+ Your ticket status has been updated +

+ +

+ Hi ${params.userName}, +

+ +

+ We wanted to let you know that your support ticket has been updated. +

+ +
+

+ Ticket ID: ${params.ticketId} +

+

+ ${params.subject} +

+

+ Status changed from: +

+

+ + ${params.oldStatus.replace('_', ' ')} + + + + ${params.newStatus.replace('_', ' ')} + +

+
+ + ${params.newStatus === 'resolved' || params.newStatus === 'closed' ? ` +

+ ${params.newStatus === 'resolved' ? 'We believe this issue has been resolved. If you still need assistance, please reply to this email.' : 'This ticket has been closed. If you need further assistance, feel free to create a new ticket.'} +

+ ` : ''} + + + View Ticket Details + + ` + + return { + subject: `[Ticket #${params.ticketId.substring(0, 8)}] Status Updated: ${params.newStatus.replace('_', ' ')}`, + html: getEmailTemplate(content) + } +} + +// Send email function using Resend +export async function sendEmail(params: EmailParams) { + try { + // Check if Resend is configured + if (!process.env.RESEND_API_KEY) { + console.warn('⚠️ RESEND_API_KEY not configured. Email not sent:', { + to: params.to, + subject: params.subject, + }) + return { success: false, error: 'Email service not configured' } + } + + const { Resend } = await import('resend') + const resend = new Resend(process.env.RESEND_API_KEY) + + const { data, error } = await resend.emails.send({ + from: process.env.SUPPORT_FROM_EMAIL || 'Codeunia Support ', + to: params.to, + subject: params.subject, + html: params.html, + }) + + if (error) { + console.error('❌ Failed to send email:', error) + return { success: false, error: error.message } + } + + console.log('✅ Email sent successfully:', { + to: params.to, + subject: params.subject, + id: data?.id + }) + + return { success: true, data } + } catch (error) { + console.error('❌ Error sending email:', error) + return { success: false, error: String(error) } + } +}