diff --git a/app/api/parse-email/route.ts b/app/api/parse-email/route.ts
index 80246be7..c23e3059 100644
--- a/app/api/parse-email/route.ts
+++ b/app/api/parse-email/route.ts
@@ -11,38 +11,81 @@ function getEmailAddress(address: AddressObject | AddressObject[] | undefined):
export async function POST(request: Request) {
try {
+ console.log('[DEBUG] Parse-email API called');
+
const body = await request.json();
const emailContent = body.email || body.emailContent;
if (!emailContent || typeof emailContent !== 'string') {
+ console.error('[DEBUG] Parse-email API error: Invalid email content');
return NextResponse.json(
{ error: 'Invalid email content' },
{ status: 400 }
);
}
- const parsed = await simpleParser(emailContent);
+ console.log('[DEBUG] Parse-email API processing email content, length:', emailContent.length);
+ console.log('[DEBUG] Content sample:', emailContent.substring(0, 100) + '...');
- return NextResponse.json({
- subject: parsed.subject || null,
- from: getEmailAddress(parsed.from),
- to: getEmailAddress(parsed.to),
- cc: getEmailAddress(parsed.cc),
- bcc: getEmailAddress(parsed.bcc),
- date: parsed.date || null,
- html: parsed.html || null,
- text: parsed.textAsHtml || parsed.text || null,
- attachments: parsed.attachments?.map(att => ({
- filename: att.filename,
- contentType: att.contentType,
- size: att.size
- })) || [],
- headers: parsed.headers || {}
- });
+ try {
+ const parsed = await simpleParser(emailContent);
+
+ console.log('[DEBUG] Parse-email API successfully parsed email:', {
+ hasSubject: !!parsed.subject,
+ hasHtml: !!parsed.html,
+ hasText: !!parsed.text,
+ hasTextAsHtml: !!parsed.textAsHtml,
+ fromCount: parsed.from ? (Array.isArray(parsed.from) ? parsed.from.length : 1) : 0,
+ attachmentCount: parsed.attachments?.length || 0
+ });
+
+ return NextResponse.json({
+ subject: parsed.subject || null,
+ from: getEmailAddress(parsed.from),
+ to: getEmailAddress(parsed.to),
+ cc: getEmailAddress(parsed.cc),
+ bcc: getEmailAddress(parsed.bcc),
+ date: parsed.date || null,
+ html: parsed.html || parsed.textAsHtml || null,
+ text: parsed.text || null,
+ attachments: parsed.attachments?.map(att => ({
+ filename: att.filename,
+ contentType: att.contentType,
+ size: att.size
+ })) || [],
+ headers: parsed.headers || {}
+ });
+ } catch (parseError) {
+ console.error('[DEBUG] Parse-email API error parsing email:', parseError);
+
+ // Try simpler parsing method for more resilience
+ try {
+ console.log('[DEBUG] Attempting fallback parsing method');
+ const resultObj: any = { text: emailContent, html: null };
+
+ // Simple check if it might be HTML
+ if (emailContent.includes('
/g, '>')
+ .replace(/\n/g, '
');
+ }
+
+ console.log('[DEBUG] Fallback parsing generated simple result');
+ return NextResponse.json(resultObj);
+ } catch (fallbackError) {
+ console.error('[DEBUG] Even fallback parsing failed:', fallbackError);
+ throw parseError; // Throw the original error
+ }
+ }
} catch (error) {
- console.error('Error parsing email:', error);
+ console.error('[DEBUG] Parse-email API unhandled error:', error);
return NextResponse.json(
- { error: 'Failed to parse email' },
+ { error: 'Failed to parse email', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
diff --git a/app/courrier/[id]/route.ts b/app/courrier/[id]/route.ts
new file mode 100644
index 00000000..bdd0084d
--- /dev/null
+++ b/app/courrier/[id]/route.ts
@@ -0,0 +1,273 @@
+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 { simpleParser } from 'mailparser';
+
+// Simple in-memory cache for email content
+const emailContentCache = new Map();
+const CACHE_TTL = 30 * 60 * 1000; // 30 minutes in milliseconds
+
+export async function GET(
+ request: Request,
+ { params }: { params: { id: string } }
+) {
+ try {
+ console.log(`[DEBUG] GET request for single email ID: ${params.id}`);
+
+ const session = await getServerSession(authOptions);
+ if (!session || !session.user?.id) {
+ console.log('[DEBUG] Not authenticated');
+ return NextResponse.json(
+ { error: "Not authenticated" },
+ { status: 401 }
+ );
+ }
+
+ const { id } = params;
+ if (!id) {
+ console.log('[DEBUG] Missing email ID');
+ return NextResponse.json(
+ { error: "Missing email ID" },
+ { status: 400 }
+ );
+ }
+
+ // Check cache first
+ const cacheKey = `${session.user.id}:${id}`;
+ const cachedContent = emailContentCache.get(cacheKey);
+ const now = Date.now();
+
+ if (cachedContent && now - cachedContent.timestamp < CACHE_TTL) {
+ console.log(`[DEBUG] Using cached content for email ${id}`);
+ return NextResponse.json({
+ id,
+ content: cachedContent.content,
+ contentFetched: true,
+ fromCache: true
+ });
+ }
+
+ // Get mail credentials
+ const credentials = await prisma.mailCredentials.findUnique({
+ where: {
+ userId: session.user.id,
+ },
+ });
+
+ if (!credentials) {
+ console.log('[DEBUG] No mail credentials found');
+ return NextResponse.json(
+ { error: "No mail credentials found" },
+ { status: 404 }
+ );
+ }
+
+ const { searchParams } = new URL(request.url);
+ const folder = searchParams.get("folder") || "INBOX";
+ console.log(`[DEBUG] Fetching email ${id} from folder ${folder}`);
+
+ // Create IMAP client
+ let imapClient: any = null;
+ try {
+ imapClient = new ImapFlow({
+ host: credentials.host,
+ port: credentials.port,
+ secure: true,
+ auth: {
+ user: credentials.email,
+ pass: credentials.password,
+ },
+ logger: false,
+ });
+
+ await imapClient.connect();
+ console.log(`[DEBUG] Connected to IMAP server to fetch email ${id}`);
+
+ // Select mailbox
+ const mailboxData = await imapClient.mailboxOpen(folder);
+ console.log(`[DEBUG] Opened mailbox ${folder}, total messages: ${mailboxData.exists}`);
+
+ // Fetch the complete email with its source
+ const message = await imapClient.fetchOne(Number(id), {
+ source: true,
+ envelope: true
+ });
+
+ if (!message) {
+ console.log(`[DEBUG] Email ${id} not found`);
+ return NextResponse.json(
+ { error: "Email not found" },
+ { status: 404 }
+ );
+ }
+
+ const { source, envelope } = message;
+ console.log(`[DEBUG] Successfully fetched email source, length: ${source.length}`);
+
+ // Parse the full email content
+ const parsedEmail = await simpleParser(source.toString());
+ console.log(`[DEBUG] Parsed email source: has HTML: ${!!parsedEmail.html}, has text: ${!!parsedEmail.text}`);
+
+ // Prioritize HTML content with fallbacks
+ const content = parsedEmail.html || parsedEmail.textAsHtml || parsedEmail.text || '';
+
+ // Cache the content
+ emailContentCache.set(cacheKey, {
+ content,
+ timestamp: now
+ });
+
+ // Return the content
+ return NextResponse.json({
+ id,
+ subject: envelope.subject,
+ content,
+ contentFetched: true
+ });
+ } catch (error: any) {
+ console.error("[DEBUG] Error fetching email content:", error);
+ return NextResponse.json(
+ {
+ error: "Failed to fetch email content",
+ message: error.message,
+ details: error.stack
+ },
+ { status: 500 }
+ );
+ } finally {
+ // Close the mailbox and connection
+ if (imapClient) {
+ try {
+ await imapClient.mailboxClose();
+ await imapClient.logout();
+ console.log(`[DEBUG] Closed IMAP connection for email ${id}`);
+ } catch (e) {
+ console.error("[DEBUG] Error closing IMAP connection:", e);
+ }
+ }
+ }
+ } catch (error: any) {
+ console.error("[DEBUG] Unhandled error in GET:", error);
+ return NextResponse.json(
+ {
+ error: "Internal server error",
+ message: error.message,
+ details: error.stack
+ },
+ { status: 500 }
+ );
+ }
+}
+
+// Handle marking emails as read
+export async function POST(
+ request: Request,
+ { params }: { params: { id: string } }
+) {
+ try {
+ console.log(`[DEBUG] POST request for email ID: ${params.id}`);
+
+ const session = await getServerSession(authOptions);
+ if (!session || !session.user?.id) {
+ return NextResponse.json(
+ { error: "Not authenticated" },
+ { status: 401 }
+ );
+ }
+
+ const { id } = params;
+ if (!id) {
+ return NextResponse.json(
+ { error: "Missing email ID" },
+ { status: 400 }
+ );
+ }
+
+ const body = await request.json();
+ const { action } = body;
+
+ if (action !== 'mark-read' && action !== 'mark-unread') {
+ return NextResponse.json(
+ { error: "Invalid action. Supported actions: mark-read, mark-unread" },
+ { status: 400 }
+ );
+ }
+
+ console.log(`[DEBUG] Processing action ${action} for email ${id}`);
+
+ // Get mail credentials
+ const credentials = await prisma.mailCredentials.findUnique({
+ where: {
+ userId: session.user.id,
+ },
+ });
+
+ if (!credentials) {
+ return NextResponse.json(
+ { error: "No mail credentials found" },
+ { status: 404 }
+ );
+ }
+
+ const { searchParams } = new URL(request.url);
+ const folder = searchParams.get("folder") || "INBOX";
+
+ // Create IMAP client
+ let imapClient: any = null;
+ try {
+ imapClient = new ImapFlow({
+ host: credentials.host,
+ port: credentials.port,
+ secure: true,
+ auth: {
+ user: credentials.email,
+ pass: credentials.password,
+ },
+ logger: false,
+ });
+
+ await imapClient.connect();
+ console.log(`[DEBUG] Connected to IMAP server for ${action}`);
+
+ // Select mailbox
+ await imapClient.mailboxOpen(folder);
+ console.log(`[DEBUG] Opened mailbox ${folder} for ${action}`);
+
+ // Set flag based on action
+ if (action === 'mark-read') {
+ await imapClient.messageFlagsAdd(Number(id), ['\\Seen']);
+ console.log(`[DEBUG] Marked email ${id} as read`);
+ } else {
+ await imapClient.messageFlagsRemove(Number(id), ['\\Seen']);
+ console.log(`[DEBUG] Marked email ${id} as unread`);
+ }
+
+ return NextResponse.json({ success: true, action });
+ } catch (error: any) {
+ console.error(`[DEBUG] Error ${action}:`, error);
+ return NextResponse.json(
+ { error: `Failed to ${action}`, message: error.message },
+ { status: 500 }
+ );
+ } finally {
+ // Close the mailbox and connection
+ if (imapClient) {
+ try {
+ await imapClient.mailboxClose();
+ await imapClient.logout();
+ console.log(`[DEBUG] Closed IMAP connection after ${action}`);
+ } catch (e) {
+ console.error("[DEBUG] Error closing IMAP connection:", e);
+ }
+ }
+ }
+ } catch (error: any) {
+ console.error("[DEBUG] Unhandled error in POST:", error);
+ return NextResponse.json(
+ { error: "Internal server error", message: error.message },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx
index 164719c1..c45bacb1 100644
--- a/app/courrier/page.tsx
+++ b/app/courrier/page.tsx
@@ -888,6 +888,38 @@ export default function CourrierPage() {
throw new Error('Email not found in list');
}
+ // Check if we need to fetch full content
+ if (!selectedEmail.content || selectedEmail.content.length === 0) {
+ console.log('[DEBUG] Fetching full content for email:', emailId);
+
+ try {
+ const response = await fetch(`/api/courrier/${emailId}?folder=${encodeURIComponent(selectedEmail.folder || 'INBOX')}`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch email content: ${response.status}`);
+ }
+
+ const fullContent = await response.json();
+
+ // Update the email content with the fetched full content
+ selectedEmail.content = fullContent.content;
+ selectedEmail.contentFetched = true;
+
+ // Update the email in the list too so we don't refetch
+ setEmails(prevEmails =>
+ prevEmails.map(email =>
+ email.id === emailId
+ ? { ...email, content: fullContent.content, contentFetched: true }
+ : email
+ )
+ );
+
+ console.log('[DEBUG] Successfully fetched full content for email:', emailId);
+ } catch (error) {
+ console.error('[DEBUG] Error fetching full content:', error);
+ }
+ }
+
// Set selected email from our existing data (which now includes full content)
setSelectedEmail(selectedEmail);
@@ -1507,6 +1539,44 @@ export default function CourrierPage() {
if (!selectedEmail) return;
try {
+ // Ensure we have full content before proceeding
+ if (!selectedEmail.content || selectedEmail.content.length === 0) {
+ console.log('[DEBUG] Need to fetch content before reply/forward');
+ setContentLoading(true);
+
+ try {
+ const response = await fetch(`/api/courrier/${selectedEmail.id}?folder=${encodeURIComponent(selectedEmail.folder || 'INBOX')}`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch email content: ${response.status}`);
+ }
+
+ const fullContent = await response.json();
+
+ // Update the email content with the fetched full content
+ selectedEmail.content = fullContent.content;
+ selectedEmail.contentFetched = true;
+
+ // Update the email in the list too so we don't refetch
+ setEmails(prevEmails =>
+ prevEmails.map(email =>
+ email.id === selectedEmail.id
+ ? { ...email, content: fullContent.content, contentFetched: true }
+ : email
+ )
+ );
+
+ console.log('[DEBUG] Successfully fetched content for reply/forward');
+ } catch (error) {
+ console.error('[DEBUG] Error fetching content for reply:', error);
+ alert('Failed to load email content for reply. Please try again.');
+ setContentLoading(false);
+ return; // Exit if we couldn't get the content
+ }
+
+ setContentLoading(false);
+ }
+
const getReplyTo = () => {
if (type === 'forward') return '';
return selectedEmail.from;
diff --git a/components/ComposeEmail.tsx b/components/ComposeEmail.tsx
index 65f69164..2ac5c515 100644
--- a/components/ComposeEmail.tsx
+++ b/components/ComposeEmail.tsx
@@ -90,8 +90,27 @@ export default function ComposeEmail({
try {
const emailToProcess = replyTo || forwardFrom;
+ console.log('[DEBUG] Initializing compose content with email:',
+ emailToProcess ? {
+ id: emailToProcess.id,
+ subject: emailToProcess.subject,
+ hasContent: !!emailToProcess.content,
+ contentLength: emailToProcess.content ? emailToProcess.content.length : 0,
+ preview: emailToProcess.preview
+ } : 'null'
+ );
+
if (!emailToProcess?.content) {
- console.error('No email content found to process');
+ console.error('[DEBUG] No email content found to process');
+ composeBodyRef.current.innerHTML = `
+
+
+
Error: No original message content available.
+
+ Please select the email again or try refreshing the page.
+
+
+ `;
return;
}
@@ -103,6 +122,8 @@ export default function ComposeEmail({
`;
+ console.log('[DEBUG] Sending content to parse-email API, length:', emailToProcess.content.length);
+
// Parse the original email using the API
const response = await fetch('/api/parse-email', {
method: 'POST',
@@ -112,12 +133,24 @@ export default function ComposeEmail({
body: JSON.stringify({ email: emailToProcess.content }),
});
+ console.log('[DEBUG] Parse-email API response status:', response.status);
+
const data = await response.json();
+ console.log('[DEBUG] Parse-email API response:', {
+ hasHtml: !!data.html,
+ hasText: !!data.text,
+ subject: data.subject,
+ error: data.error
+ });
+
if (!response.ok) {
throw new Error(data.error || 'Failed to parse email');
}
const emailContent = data.html || data.text || '';
+ if (!emailContent) {
+ console.warn('[DEBUG] No HTML or text content returned from parser');
+ }
// Format the reply/forward content
const quotedContent = forwardFrom ? `
@@ -167,14 +200,18 @@ export default function ComposeEmail({
// Update compose state
setComposeBody(formattedContent);
setLocalContent(formattedContent);
+ console.log('[DEBUG] Successfully set compose content');
}
} catch (error) {
- console.error('Error initializing compose content:', error);
+ console.error('[DEBUG] Error initializing compose content:', error);
if (composeBodyRef.current) {
const errorContent = `
Error loading original message.
+
+ Technical details: ${error instanceof Error ? error.message : 'Unknown error'}
+
`;
composeBodyRef.current.innerHTML = errorContent;