import { NextResponse } from 'next/server'; import { ImapFlow } from 'imapflow'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { prisma } from '@/lib/prisma'; import { parseEmail } from '@/lib/server/email-parser'; import { LRUCache } from 'lru-cache'; // Simple in-memory cache for email content const emailContentCache = new LRUCache({ max: 100, ttl: 1000 * 60 * 15, // 15 minutes }); export async function GET( request: Request, { params }: { params: { id: string } } ) { try { // 1. Get email ID from params (properly awaited) const { id } = await Promise.resolve(params); // 2. Authentication check const session = await getServerSession(authOptions); if (!session?.user?.id) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // 3. Check cache first const cacheKey = `email:${session.user.id}:${id}`; const cachedEmail = emailContentCache.get(cacheKey); if (cachedEmail) { return NextResponse.json(cachedEmail); } // 4. Get credentials from database const credentials = await prisma.mailCredentials.findUnique({ where: { userId: session.user.id } }); if (!credentials) { return NextResponse.json( { error: 'No mail credentials found. Please configure your email account.' }, { status: 401 } ); } // 5. Get the current folder from the request URL const url = new URL(request.url); const folder = url.searchParams.get('folder') || 'INBOX'; // 6. Create IMAP client const client = new ImapFlow({ host: credentials.host, port: credentials.port, secure: true, auth: { user: credentials.email, pass: credentials.password, }, logger: false, emitLogs: false, tls: { rejectUnauthorized: false }, disableAutoIdle: true }); try { await client.connect(); // 7. Open the folder const mailbox = await client.mailboxOpen(folder); console.log(`Mailbox opened: ${folder}, total messages: ${mailbox.exists}`); // 8. Download the raw message data using a dynamic fetch approach console.log(`Attempting to fetch message with UID: ${id}`); // Create a loop to process all messages until we find the right one let foundMessage = null; const chunkSize = 10; for (let i = 1; i <= mailbox.exists; i += chunkSize) { const endIdx = Math.min(i + chunkSize - 1, mailbox.exists); const range = `${i}:${endIdx}`; console.log(`Scanning messages ${range}`); // Fetch messages in chunks with UID const messages = client.fetch(range, { uid: true, envelope: true, flags: true, bodyStructure: true, source: true }); for await (const message of messages) { if (message.uid.toString() === id) { console.log(`Found matching message with UID ${id}`); foundMessage = message; break; } } if (foundMessage) { break; } } if (!foundMessage) { console.log(`No message found with UID ${id}`); return NextResponse.json( { error: `Email not found with UID ${id}` }, { status: 404 } ); } console.log(`Successfully fetched message, parsing content...`); // 9. Parse the email content const parsedEmail = await parseEmail(foundMessage.source.toString()); // Debug the parsed email structure console.log('Parsed email data structure:', { hasHtml: !!parsedEmail.html, hasText: !!parsedEmail.text, htmlLength: parsedEmail.html ? parsedEmail.html.length : 0, textLength: parsedEmail.text ? parsedEmail.text.length : 0, attachmentsCount: parsedEmail.attachments ? parsedEmail.attachments.length : 0 }); // 10. Prepare the full email object with all needed data const fullEmail = { id, from: foundMessage.envelope.from?.[0]?.address || '', fromName: foundMessage.envelope.from?.[0]?.name || foundMessage.envelope.from?.[0]?.address?.split('@')[0] || '', to: foundMessage.envelope.to?.map((addr: any) => addr.address).join(', ') || '', subject: foundMessage.envelope.subject || '(No subject)', date: foundMessage.envelope.date?.toISOString() || new Date().toISOString(), content: typeof parsedEmail.html === 'string' ? parsedEmail.html : typeof parsedEmail.text === 'string' ? parsedEmail.text : '', textContent: typeof parsedEmail.text === 'string' ? parsedEmail.text : '', rawContent: typeof foundMessage.source === 'object' ? foundMessage.source.toString() : String(foundMessage.source || ''), read: foundMessage.flags.has('\\Seen'), starred: foundMessage.flags.has('\\Flagged'), folder: folder, hasAttachments: foundMessage.bodyStructure?.type === 'multipart', attachments: parsedEmail.attachments || [], flags: Array.from(foundMessage.flags), headers: parsedEmail.headers || {} }; // Log the structure of the email being returned console.log('Returning email object with content structure:', { id: fullEmail.id, hasContent: !!fullEmail.content, contentLength: fullEmail.content ? fullEmail.content.length : 0, hasTextContent: !!fullEmail.textContent, textContentLength: fullEmail.textContent ? fullEmail.textContent.length : 0 }); // 11. Mark as read if not already if (!foundMessage.flags.has('\\Seen')) { try { // Use the same sequence range to mark message as read for (let i = 1; i <= mailbox.exists; i += chunkSize) { const endIdx = Math.min(i + chunkSize - 1, mailbox.exists); const range = `${i}:${endIdx}`; // Find message and mark as read const messages = client.fetch(range, { uid: true }); for await (const message of messages) { if (message.uid.toString() === id) { await client.messageFlagsAdd(i.toString(), ['\\Seen']); break; } } } } catch (error) { console.error('Error marking message as read:', error); } } // 12. Cache the email content emailContentCache.set(cacheKey, fullEmail); // 13. Return the full email return NextResponse.json(fullEmail); } finally { // 14. Close the connection try { await client.logout(); } catch (e) { console.error('Error during IMAP logout:', e); } } } catch (error) { console.error('Error fetching email:', error); return NextResponse.json( { error: 'Failed to fetch email' }, { status: 500 } ); } }