import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as sgMail from '@sendgrid/mail'; import * as nodemailer from 'nodemailer'; @Injectable() export class EmailService { private readonly logger = new Logger(EmailService.name); private readonly provider: string; private smtpTransporter?: nodemailer.Transporter; constructor(private configService: ConfigService) { this.provider = this.configService.get('EMAIL_PROVIDER', 'SENDGRID'); this.initializeProvider(); } private initializeProvider() { switch (this.provider) { case 'SENDGRID': const apiKey = this.configService.get('SENDGRID_API_KEY'); if (apiKey) { sgMail.setApiKey(apiKey); this.logger.log('SendGrid email provider initialized'); } else { this.logger.warn('SENDGRID_API_KEY not found, email sending disabled'); } break; case 'SMTP': this.smtpTransporter = nodemailer.createTransport({ host: this.configService.get('SMTP_HOST'), port: this.configService.get('SMTP_PORT', 587), secure: this.configService.get('SMTP_SECURE', false), auth: { user: this.configService.get('SMTP_USER'), pass: this.configService.get('SMTP_PASS'), }, }); this.logger.log('SMTP email provider initialized'); break; default: this.logger.warn(`Unknown email provider: ${this.provider}`); } } /** * Send ticket confirmation email to buyer */ async sendTicketConfirmation(data: { order: any; raffle: any; tickets: number[]; }) { const { order, raffle, tickets } = data; const subject = `Your ${raffle.name} Raffle Tickets - Confirmation`; const html = this.generateTicketConfirmationHtml({ buyerName: order.buyerName, orderDate: order.createdAt, raffleInfo: { name: raffle.name, description: raffle.description, licenseNumber: raffle.licenseNumber, rulesUrl: raffle.rulesUrl, drawDate: raffle.drawAt || 'To be announced', }, orderSummary: { orderId: order.id, quantity: tickets.length, ticketNumbers: tickets, totalAmount: `$${(order.amountCents / 100).toFixed(2)} CAD`, }, charityInfo: { name: raffle.charityOrg.legalName, email: raffle.charityOrg.contactEmail, address: raffle.charityOrg.address, }, }); const text = this.generateTicketConfirmationText({ buyerName: order.buyerName, raffleName: raffle.name, ticketNumbers: tickets, orderId: order.id, licenseNumber: raffle.licenseNumber, }); await this.sendEmail({ to: order.buyerEmail, subject, html, text, }); this.logger.log(`Sent ticket confirmation email to ${order.buyerEmail} for order ${order.id}`); } /** * Send draw notification email */ async sendDrawNotification(data: { order: any; raffle: any; draw: any; isWinner: boolean; winningTicketNumber?: number; }) { const { order, raffle, draw, isWinner, winningTicketNumber } = data; const subject = isWinner ? `🎉 CONGRATULATIONS! You Won ${raffle.name}!` : `${raffle.name} - Draw Results`; const html = this.generateDrawNotificationHtml({ buyerName: order.buyerName, isWinner, raffleName: raffle.name, winningTicketNumber, drawDate: draw.drawnAt, licenseNumber: raffle.licenseNumber, charityInfo: { name: raffle.charityOrg.legalName, email: raffle.charityOrg.contactEmail, }, }); await this.sendEmail({ to: order.buyerEmail, subject, html, }); this.logger.log(`Sent draw notification email to ${order.buyerEmail} (winner: ${isWinner})`); } /** * Generic email sending method */ private async sendEmail(data: { to: string; subject: string; html: string; text?: string; }) { const fromEmail = this.configService.get('SENDGRID_FROM_EMAIL', 'noreply@example.com'); const fromName = this.configService.get('SENDGRID_FROM_NAME', 'Charity Raffle'); try { switch (this.provider) { case 'SENDGRID': await sgMail.send({ to: data.to, from: { email: fromEmail, name: fromName, }, subject: data.subject, html: data.html, text: data.text, }); break; case 'SMTP': if (this.smtpTransporter) { await this.smtpTransporter.sendMail({ from: `"${fromName}" <${fromEmail}>`, to: data.to, subject: data.subject, html: data.html, text: data.text, }); } break; default: this.logger.warn('No email provider configured, skipping email send'); return; } this.logger.log(`Email sent successfully to ${data.to}`); } catch (error) { this.logger.error(`Failed to send email to ${data.to}: ${error.message}`, error.stack); throw error; } } /** * Generate ticket confirmation HTML email */ private generateTicketConfirmationHtml(data: any): string { return ` Raffle Ticket Confirmation

🎟️ Raffle Ticket Confirmation

Dear ${data.buyerName},

Thank you for your purchase! Your raffle tickets have been confirmed.

${data.raffleInfo.name}

License Number: ${data.raffleInfo.licenseNumber}

Your Ticket Numbers:

${data.orderSummary.ticketNumbers.join(', ')}

Quantity: ${data.orderSummary.quantity} tickets

Total Paid: ${data.orderSummary.totalAmount}

Order ID: ${data.orderSummary.orderId}

Important Information:

  • Keep this email as proof of purchase
  • Draw date: ${data.raffleInfo.drawDate}
  • Rules: View Official Rules
  • Winners will be notified by email

Thank you for supporting ${data.charityInfo.name}!

`; } /** * Generate plain text ticket confirmation */ private generateTicketConfirmationText(data: any): string { return ` Raffle Ticket Confirmation Dear ${data.buyerName}, Thank you for your purchase! Your raffle tickets have been confirmed. ${data.raffleName} License Number: ${data.licenseNumber} Your Ticket Numbers: ${data.ticketNumbers.join(', ')} Order ID: ${data.orderId} Keep this email as proof of purchase. Winners will be notified by email. Thank you for your support! `; } /** * Generate draw notification HTML email */ private generateDrawNotificationHtml(data: any): string { const winnerContent = data.isWinner ? `

🎉 CONGRATULATIONS! 🎉

You are the winner of ${data.raffleName}!

Winning Ticket Number: ${data.winningTicketNumber}

We will contact you within 24 hours with instructions on how to claim your prize.

` : `

The draw for ${data.raffleName} has been completed.

Unfortunately, your tickets were not selected as the winner this time.

Winning Ticket Number: ${data.winningTicketNumber}

Thank you for your participation and support!

`; return ` Draw Results

${data.raffleName} - Draw Results

Dear ${data.buyerName},

${winnerContent}

Draw Date: ${new Date(data.drawDate).toLocaleDateString()}

License Number: ${data.licenseNumber}

`; }