import nodemailer from "nodemailer"; interface ContractBlueprint { type: string; provider: string | null; policyNumber: string | null; startDate: string | null; endDate: string | null; premium: number | null; premiumCurrency: string | null; summary: string; } interface BlockchainEmailData { documentHash: string; txHash: string; blockNumber: number; blockTimestamp: Date; network: string; contractAddress: string; explorerUrl: string | null; } interface ContractAnalysisEmailInput { to: string; userDisplayName?: string | null; contractId: string; contractFileName: string; contractTitle: string; blueprint: ContractBlueprint; blockchain?: BlockchainEmailData | null; } let transporter: nodemailer.Transporter | null = null; let transportMode: "smtp" | "ethereal" | null = null; let hasWarnedMissingEmailConfig = false; const asBoolean = (value: string | undefined, fallback: boolean): boolean => { if (!value) return fallback; return value.toLowerCase() === "true" || value === "1"; }; const isEmailConfigured = (): boolean => { return Boolean( process.env.EMAIL_HOST && process.env.EMAIL_PORT && process.env.EMAIL_USER && process.env.EMAIL_PASS, ); }; const warnMissingEmailConfigOnce = () => { if (hasWarnedMissingEmailConfig) return; hasWarnedMissingEmailConfig = true; console.warn( "Email notifications are disabled. Configure EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, and MAIL_FROM to enable contract summary emails.", ); }; const getTransporter = async (): Promise => { if (transporter) { return transporter; } if (isEmailConfigured()) { transportMode = "smtp"; transporter = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: Number(process.env.EMAIL_PORT), secure: asBoolean( process.env.EMAIL_SECURE, Number(process.env.EMAIL_PORT) === 465, ), auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS, }, }); return transporter; } if (process.env.NODE_ENV !== "production") { const testAccount = await nodemailer.createTestAccount(); transportMode = "ethereal"; transporter = nodemailer.createTransport({ host: testAccount.smtp.host, port: testAccount.smtp.port, secure: testAccount.smtp.secure, auth: { user: testAccount.user, pass: testAccount.pass, }, }); console.warn( "Email service is running in development fallback mode using Ethereal. Configure SMTP env vars for real inbox delivery.", ); return transporter; } warnMissingEmailConfigOnce(); return null; }; const formatPremium = ( premium: number | null, currency: string | null, ): string => { if (premium === null || premium === undefined) return "N/A"; const formattedAmount = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(premium); if (!currency) return formattedAmount; if (["€", "$", "£"].includes(currency)) return `${currency}${formattedAmount}`; return `${formattedAmount} ${currency}`; }; const formatDateValue = (dateValue: string | null): string => { if (!dateValue) return "N/A"; const date = new Date(dateValue); if (Number.isNaN(date.getTime())) return dateValue; return date.toISOString().split("T")[0]; }; const formatContractLink = (contractId: string): string | null => { const baseUrl = process.env.NEXT_PUBLIC_APP_URL?.trim() || process.env.APP_URL?.trim(); if (!baseUrl) return null; return `${baseUrl.replace(/\/$/, "")}/contacts?contract=${contractId}`; }; export class EmailService { static async sendContractAnalysisCompletedEmail( input: ContractAnalysisEmailInput, ): Promise<{ success: boolean; error?: string; skipped?: boolean; previewUrl?: string | null; }> { try { const mailer = await getTransporter(); if (!mailer) { return { success: false, skipped: true, error: "Email service not configured", }; } const from = process.env.MAIL_FROM?.trim() || process.env.EMAIL_USER?.trim() || (transportMode === "ethereal" ? "LexiChain " : ""); if (!from) { warnMissingEmailConfigOnce(); return { success: false, skipped: true, error: "MAIL_FROM is missing" }; } if (!input.to?.trim()) { return { success: false, skipped: true, error: "Recipient email is missing", }; } const recipientName = input.userDisplayName || "there"; const premiumLabel = formatPremium( input.blueprint.premium, input.blueprint.premiumCurrency, ); const contractUrl = formatContractLink(input.contractId); const blockchainStatus = input.blockchain ? "Registered" : "Not registered (blockchain unavailable or skipped)"; const textBody = [ `Hello ${recipientName},`, "", "Your contract analysis is complete.", "", "Blueprint:", `- Contract title: ${input.contractTitle}`, `- Original file: ${input.contractFileName}`, `- Type: ${input.blueprint.type}`, `- Provider: ${input.blueprint.provider ?? "N/A"}`, `- Policy number: ${input.blueprint.policyNumber ?? "N/A"}`, `- Start date: ${formatDateValue(input.blueprint.startDate)}`, `- End date: ${formatDateValue(input.blueprint.endDate)}`, `- Premium: ${premiumLabel}`, "", "Summary:", input.blueprint.summary, "", "Blockchain proof:", `- Status: ${blockchainStatus}`, `- Document hash: ${input.blockchain?.documentHash ?? "N/A"}`, `- Transaction hash: ${input.blockchain?.txHash ?? "N/A"}`, `- Block number: ${input.blockchain?.blockNumber ?? "N/A"}`, `- Block time: ${input.blockchain?.blockTimestamp?.toISOString() ?? "N/A"}`, `- Network: ${input.blockchain?.network ?? "N/A"}`, `- Contract address: ${input.blockchain?.contractAddress ?? "N/A"}`, `- Explorer URL: ${input.blockchain?.explorerUrl ?? "N/A"}`, "", contractUrl ? `Open in app: ${contractUrl}` : "", "", "Keep this email for your records.", ] .filter(Boolean) .join("\n"); const htmlBody = `

Contract Analysis Completed

Hello ${recipientName},

Your contract analysis has been completed successfully.

Blueprint

  • Contract title: ${input.contractTitle}
  • Original file: ${input.contractFileName}
  • Type: ${input.blueprint.type}
  • Provider: ${input.blueprint.provider ?? "N/A"}
  • Policy number: ${input.blueprint.policyNumber ?? "N/A"}
  • Start date: ${formatDateValue(input.blueprint.startDate)}
  • End date: ${formatDateValue(input.blueprint.endDate)}
  • Premium: ${premiumLabel}

Summary

${input.blueprint.summary.replace(/\n/g, "
")}

Blockchain Proof

  • Status: ${blockchainStatus}
  • Document hash: ${input.blockchain?.documentHash ?? "N/A"}
  • Transaction hash: ${input.blockchain?.txHash ?? "N/A"}
  • Block number: ${input.blockchain?.blockNumber ?? "N/A"}
  • Block time: ${input.blockchain?.blockTimestamp?.toISOString() ?? "N/A"}
  • Network: ${input.blockchain?.network ?? "N/A"}
  • Contract address: ${input.blockchain?.contractAddress ?? "N/A"}
  • Explorer URL: ${input.blockchain?.explorerUrl ? `Open transaction` : "N/A"}
${contractUrl ? `

Open this contract in your dashboard

` : ""}

Keep this email for your records.

`; const info = await mailer.sendMail({ from, to: input.to, subject: `Contract analyzed: ${input.contractTitle}`, text: textBody, html: htmlBody, }); const previewUrl = nodemailer.getTestMessageUrl(info); if (previewUrl) { console.log(`📨 Ethereal preview URL: ${previewUrl}`); } return { success: true, previewUrl }; } catch (error) { console.error("Failed to send analysis completion email:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown email error", }; } } }