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; } interface ContractDeadlineEmailInput { to: string; userDisplayName?: string | null; contractId: string; contractTitle: string | null; contractProvider: string | null; contractEndDate: Date; daysUntilExpiration: number; } 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}`; }; const getBaseUrl = (): string | null => { const baseUrl = process.env.NEXT_PUBLIC_APP_URL?.trim() || process.env.APP_URL?.trim(); return baseUrl ? baseUrl.replace(/\/$/, "") : null; }; const getLogoUrl = (): string | null => { const baseUrl = getBaseUrl(); return baseUrl ? `${baseUrl}/LexiChain.png` : null; }; 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 logoUrl = getLogoUrl(); const blockchainStatus = input.blockchain ? "Registered" : "Not registered (blockchain unavailable or skipped)"; const textBody = [ `Hello ${recipientName},`, "", "Your LexiChain contract intelligence report is ready.", "", "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}`, "", "Executive 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}` : "", "", "Thank you for trusting LexiChain for secure contract governance.", ] .filter(Boolean) .join("\n"); const htmlBody = `
${logoUrl ? `LexiChain` : 'LexiChain'} Contract Intelligence

Your contract insight report is ready

Clear, verified, and ready for action.

Hello ${recipientName},

Your LexiChain analysis is complete. Below is the executive blueprint and proof trace.

Blueprint Summary
Contract: ${input.contractTitle}
File: ${input.contractFileName}
Type: ${input.blueprint.type}
Provider: ${input.blueprint.provider ?? "N/A"}
Policy #: ${input.blueprint.policyNumber ?? "N/A"}
Start: ${formatDateValue(input.blueprint.startDate)}
End: ${formatDateValue(input.blueprint.endDate)}
Premium: ${premiumLabel}
Executive 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: ${input.blockchain?.explorerUrl ? `Open transaction` : "N/A"}
${ contractUrl ? ` ` : "" }

Precision you can audit. Trust you can prove.

`; const info = await mailer.sendMail({ from, to: input.to, subject: `Contract analyzed: ${input.contractTitle}`, html: htmlBody, headers: { "Content-Type": "text/html; charset=utf-8", }, }); const previewUrl = (nodemailer.getTestMessageUrl(info) as string | false) || null; 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", }; } } static async sendContractDeadlineReminderEmail( input: ContractDeadlineEmailInput, ): 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 contractUrl = formatContractLink(input.contractId); const logoUrl = getLogoUrl(); const endDate = input.contractEndDate.toLocaleDateString(); const urgencyLabel = input.daysUntilExpiration <= 7 ? "Urgent" : input.daysUntilExpiration <= 14 ? "High" : "Planned"; const textBody = [ `Hello ${recipientName},`, "", `Your contract deadline is approaching in ${input.daysUntilExpiration} days.`, "", `Contract: ${input.contractTitle ?? "Untitled contract"}`, `Provider: ${input.contractProvider ?? "N/A"}`, `End date: ${endDate}`, `Urgency: ${urgencyLabel}`, "", contractUrl ? `Open in app: ${contractUrl}` : "", "", "Please review the contract and schedule renewal if needed.", ] .filter(Boolean) .join("\n"); const htmlBody = `
${logoUrl ? `LexiChain` : 'LexiChain'} Deadline Alert

Contract renewal reminder

Stay ahead of critical dates.

Hello ${recipientName},

Your contract deadline is approaching in ${input.daysUntilExpiration} days.

Deadline Summary
Contract: ${input.contractTitle ?? "Untitled contract"}
Provider: ${input.contractProvider ?? "N/A"}
End date: ${endDate}
Urgency: ${urgencyLabel}
${ contractUrl ? ` ` : "" }

Plan renewals early to avoid coverage gaps.

`; const info = await mailer.sendMail({ from, to: input.to, subject: `Contract deadline in ${input.daysUntilExpiration} days`, html: htmlBody, headers: { "Content-Type": "text/html; charset=utf-8", }, }); const previewUrl = (nodemailer.getTestMessageUrl(info) as string | false) || null; if (previewUrl) { console.log(`📨 Ethereal preview URL: ${previewUrl}`); } return { success: true, previewUrl }; } catch (error) { console.error("Failed to send deadline reminder email:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown email error", }; } } }