import { NextRequest, NextResponse } from 'next/server'; import { simpleParser } from 'mailparser'; import * as DOMPurify from 'isomorphic-dompurify'; interface EmailAddress { name?: string; address: string; } // Helper to extract email addresses from mailparser Address objects function getEmailAddresses(addresses: any): EmailAddress[] { if (!addresses) return []; // Handle various address formats if (Array.isArray(addresses)) { return addresses.map(addr => ({ name: addr.name || undefined, address: addr.address })); } if (typeof addresses === 'object') { const result: EmailAddress[] = []; // Handle mailparser format with text, html, value properties if (addresses.value) { addresses.value.forEach((addr: any) => { result.push({ name: addr.name || undefined, address: addr.address }); }); return result; } // Handle direct object with address property if (addresses.address) { return [{ name: addresses.name || undefined, address: addresses.address }]; } } return []; } // Process HTML to ensure it displays well in our email context function processHtml(html: string): string { if (!html) return ''; try { // Fix self-closing tags that might break in contentEditable html = html.replace(/<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([^>]*)>/gi, (match, tag, attrs) => `<${tag}${attrs}${attrs.endsWith('/') ? '' : '/'}>`) // Clean up HTML with DOMPurify - CRITICAL for security // Allow style tags but remove script tags const cleaned = DOMPurify.sanitize(html, { ADD_TAGS: ['style'], FORBID_TAGS: ['script', 'iframe', 'object', 'embed'], WHOLE_DOCUMENT: false }); // Scope CSS to prevent leakage return cleaned.replace(/]*)>([\s\S]*?)<\/style>/gi, (match, attrs, css) => { // Generate a unique class for this email content const uniqueClass = `email-content-${Date.now()}`; // Add the unique class to outer container that will be added return `.${uniqueClass} {contain: content;} .${uniqueClass} ${css}`; }); } catch (e) { console.error('Error processing HTML:', e); return html; // Return original if processing fails } } export async function POST(req: NextRequest) { try { const { email } = await req.json(); if (!email || typeof email !== 'string') { return NextResponse.json( { error: 'Invalid email content' }, { status: 400 } ); } const parsed = await simpleParser(email); // Process the HTML content to make it safe and displayable const html = parsed.html ? processHtml(parsed.html.toString()) : undefined; const text = parsed.text ? parsed.text.toString() : undefined; // Extract attachments info if available const attachments = parsed.attachments?.map(attachment => ({ filename: attachment.filename, contentType: attachment.contentType, contentDisposition: attachment.contentDisposition, size: attachment.size })) || []; // Return all parsed email details return NextResponse.json({ subject: parsed.subject, from: getEmailAddresses(parsed.from), to: getEmailAddresses(parsed.to), cc: getEmailAddresses(parsed.cc), bcc: getEmailAddresses(parsed.bcc), date: parsed.date, html, text, attachments }); } catch (error) { console.error('Error parsing email:', error); return NextResponse.json( { error: 'Failed to parse email content' }, { status: 500 } ); } }