import { NextResponse } from 'next/server'; import Imap from 'imap'; import nodemailer from 'nodemailer'; import { parseEmailHeaders, decodeEmailBody } from '@/lib/email-parser'; import { cookies } from 'next/headers'; interface StoredCredentials { email: string; password: string; host: string; port: number; } interface Email { id: string; from: string; subject: string; date: Date; read: boolean; starred: boolean; body: string; to?: string; folder: string; } interface ImapBox { messages: { total: number; }; } interface ImapMessage { on: (event: string, callback: (data: any) => void) => void; once: (event: string, callback: (data: any) => void) => void; attributes: { uid: number; flags: string[]; size: number; }; body: { [key: string]: { on: (event: string, callback: (data: any) => void) => void; }; }; } interface ImapConfig { user: string; password: string; host: string; port: number; tls: boolean; authTimeout: number; connTimeout: number; debug?: (info: string) => void; } function getStoredCredentials(): StoredCredentials | null { const cookieStore = cookies(); const credentialsCookie = cookieStore.get('imap_credentials'); console.log('Retrieved credentials cookie:', credentialsCookie ? 'Found' : 'Not found'); if (!credentialsCookie?.value) { console.log('No credentials cookie found'); return null; } try { const credentials = JSON.parse(credentialsCookie.value); console.log('Parsed credentials:', { ...credentials, password: '***' }); // Validate required fields if (!credentials.email || !credentials.password || !credentials.host || !credentials.port) { console.error('Missing required credentials fields'); return null; } return { email: credentials.email, password: credentials.password, host: credentials.host, port: credentials.port }; } catch (error) { console.error('Error parsing credentials cookie:', error); return null; } } export async function GET(request: Request) { try { const credentials = getStoredCredentials(); if (!credentials) { return NextResponse.json( { error: 'No stored credentials found' }, { status: 401 } ); } // Get pagination parameters from URL const url = new URL(request.url); const folder = url.searchParams.get('folder') || 'INBOX'; const page = parseInt(url.searchParams.get('page') || '1'); const limit = parseInt(url.searchParams.get('limit') || '24'); const offset = (page - 1) * limit; return new Promise((resolve) => { const imap = new Imap({ user: credentials.email, password: credentials.password, host: credentials.host, port: credentials.port, tls: true, tlsOptions: { rejectUnauthorized: false }, authTimeout: 30000, connTimeout: 30000 }); const timeout = setTimeout(() => { console.error('IMAP connection timeout'); imap.end(); resolve(NextResponse.json({ emails: [], error: 'Connection timeout' })); }, 30000); imap.once('error', (err: Error) => { console.error('IMAP error:', err); clearTimeout(timeout); resolve(NextResponse.json({ emails: [], error: 'IMAP connection error' })); }); imap.once('ready', () => { imap.getBoxes((err, boxes) => { if (err) { console.error('Error getting mailboxes:', err); clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails: [], error: 'Failed to get mailboxes' })); return; } const availableMailboxes = Object.keys(boxes).filter( box => !['Starred', 'Archives'].includes(box) ); console.log('Available mailboxes:', availableMailboxes); // Only process the requested folder imap.openBox(folder, false, (err, box) => { if (err) { console.error(`Error opening box ${folder}:`, err); clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails: [], error: `Failed to open folder ${folder}` })); return; } // Get the specified folder const totalMessages = box.messages.total; // Calculate the range of messages to fetch const start = Math.max(1, totalMessages - offset - limit + 1); const end = totalMessages - offset; if (start > end) { clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails: [], folders: availableMailboxes, mailUrl: process.env.NEXT_PUBLIC_IFRAME_MAIL_URL })); return; } // Fetch messages in the calculated range imap.search(['ALL'], (err, results) => { if (err) { console.error(`Error searching in ${folder}:`, err); clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails: [], error: `Failed to search in ${folder}` })); return; } if (!results || results.length === 0) { clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails: [], folders: availableMailboxes, mailUrl: process.env.NEXT_PUBLIC_IFRAME_MAIL_URL })); return; } // Take only the most recent emails up to the limit const recentResults = results.slice(-limit); const emails: any[] = []; const fetch = imap.fetch(recentResults, { bodies: ['HEADER', 'TEXT'], struct: true }); fetch.on('message', (msg) => { let header = ''; let text = ''; let messageId: number | null = null; let messageFlags: string[] = []; msg.once('attributes', (attrs) => { messageId = attrs.uid; messageFlags = attrs.flags || []; }); msg.on('body', (stream, info) => { let buffer = ''; stream.on('data', (chunk) => { buffer += chunk.toString('utf8'); }); stream.on('end', () => { if (info.which === 'HEADER') { header = buffer; } else if (info.which === 'TEXT') { text = buffer; } }); }); msg.on('end', () => { if (!messageId) { console.error('No message ID found for email'); return; } const parsedHeader = Imap.parseHeader(header); const email = { id: messageId, from: parsedHeader.from?.[0] || '', to: parsedHeader.to?.[0] || '', subject: parsedHeader.subject?.[0] || '(No subject)', date: parsedHeader.date?.[0] || new Date().toISOString(), body: text, folder: folder, flags: messageFlags, read: messageFlags.includes('\\Seen'), starred: messageFlags.includes('\\Flagged') }; emails.push(email); }); }); fetch.on('error', (err) => { console.error(`Error fetching emails from ${folder}:`, err); clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails: [], error: `Failed to fetch emails from ${folder}` })); }); fetch.on('end', () => { // Sort emails by date (most recent first) emails.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); clearTimeout(timeout); imap.end(); resolve(NextResponse.json({ emails, folders: availableMailboxes, mailUrl: process.env.NEXT_PUBLIC_IFRAME_MAIL_URL })); }); }); }); }); }); imap.connect(); }); } catch (error) { console.error('Error in GET /api/mail:', error); return NextResponse.json( { error: 'Failed to fetch emails' }, { status: 500 } ); } } export async function POST(request: Request) { try { const credentials = getStoredCredentials(); if (!credentials) { return NextResponse.json( { error: 'No stored credentials found' }, { status: 401 } ); } let body; try { body = await request.json(); } catch (error) { return NextResponse.json( { error: 'Invalid JSON in request body' }, { status: 400 } ); } const { to, subject, body: emailBody, attachments } = body; if (!to || !subject || !emailBody) { return NextResponse.json( { error: 'Missing required fields: to, subject, or body' }, { status: 400 } ); } const transporter = nodemailer.createTransport({ host: credentials.host, port: credentials.port, secure: true, auth: { user: credentials.email, pass: credentials.password, }, }); const mailOptions = { from: credentials.email, to, subject, text: emailBody, attachments: attachments || [], }; const info = await transporter.sendMail(mailOptions); console.log('Email sent:', info.messageId); return NextResponse.json({ success: true, messageId: info.messageId, message: 'Email sent successfully' }); } catch (error) { console.error('Error sending email:', error); return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to send email', details: error instanceof Error ? error.stack : undefined }, { status: 500 } ); } }