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; } 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() { try { console.log('GET /api/mail called'); const credentials = getStoredCredentials(); if (!credentials) { console.log('No credentials found in cookies'); return NextResponse.json( { error: 'No stored credentials found' }, { status: 401 } ); } console.log('Using credentials:', { ...credentials, password: '***' }); const imapConfig: ImapConfig = { user: credentials.email, password: credentials.password, host: credentials.host, port: credentials.port, tls: true, authTimeout: 10000, connTimeout: 10000, debug: (info: string) => console.log('IMAP Debug:', info) }; console.log('Connecting to IMAP server...'); const imap = new Imap(imapConfig); const connectPromise = new Promise((resolve, reject) => { imap.once('ready', () => { console.log('IMAP connection ready'); resolve(true); }); imap.once('error', (err: Error) => { console.error('IMAP connection error:', err); reject(err); }); imap.connect(); }); await connectPromise; console.log('Opening INBOX...'); const openBoxPromise = new Promise((resolve, reject) => { imap.openBox('INBOX', false, (err: Error | null, box: ImapBox) => { if (err) { console.error('Error opening INBOX:', err); reject(err); } else { console.log('INBOX opened successfully'); resolve(box); } }); }); const box = await openBoxPromise; console.log('Total messages in INBOX:', box.messages.total); const fetchPromise = (imap: Imap, seqno: number): Promise => { return new Promise((resolve, reject) => { const f = imap.fetch(seqno, { bodies: ['HEADER', 'TEXT'], struct: true, markSeen: false }); f.on('message', (msg: ImapMessage) => { resolve(msg); }); f.once('error', (err: Error) => { console.error('Fetch error:', err); reject(err); }); }); }; const processMessage = (msg: ImapMessage): Promise => { return new Promise((resolve, reject) => { let headerContent = ''; let bodyContent = ''; let headersParsed = false; let bodyParsed = false; // Process headers msg.body['HEADER']?.on('data', (chunk: Buffer) => { headerContent += chunk.toString('utf8'); }); // Process body msg.body['TEXT']?.on('data', (chunk: Buffer) => { bodyContent += chunk.toString('utf8'); }); const tryResolve = () => { if (headersParsed && bodyParsed) { try { const headers = parseEmailHeaders(headerContent); const contentType = headerContent.match(/Content-Type:\s*([^;\r\n]+)/i)?.[1] || 'text/plain'; const decodedBody = decodeEmailBody(bodyContent, contentType); resolve({ uid: msg.attributes.uid, flags: msg.attributes.flags, size: msg.attributes.size, body: decodedBody, ...headers }); } catch (error) { console.error('Error processing message:', error); reject(error); } } }; msg.body['HEADER']?.on('end', () => { headersParsed = true; tryResolve(); }); msg.body['TEXT']?.on('end', () => { bodyParsed = true; tryResolve(); }); msg.body['HEADER']?.on('error', reject); msg.body['TEXT']?.on('error', reject); }); }; const messages: any[] = []; // Fetch the most recent 20 emails const start = Math.max(1, box.messages.total - 19); // last 20 emails const end = box.messages.total; for (let seqno = start; seqno <= end; seqno++) { const msg = await fetchPromise(imap, seqno); const processedMsg = await processMessage(msg); messages.push(processedMsg); } imap.end(); console.log('Raw messages:', messages); const emails: Email[] = messages.map((msg) => { return { id: msg.uid.toString(), from: msg.from, subject: msg.subject || '(No subject)', date: new Date(msg.date), read: !msg.flags?.includes('\\Unseen'), starred: msg.flags?.includes('\\Flagged') || false, body: msg.body || '', to: msg.to }; }); return NextResponse.json({ emails, mailUrl: process.env.NEXTCLOUD_URL ? `${process.env.NEXTCLOUD_URL}/apps/mail/` : null }, { headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('Error fetching emails:', error); const status = error instanceof Error && error.message.includes('Invalid login') ? 401 : 500; return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to fetch emails' }, { status } ); } } 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 } ); } }