diff --git a/app/api/courrier/account/route.ts b/app/api/courrier/account/route.ts index 49ccce1c..d7736ba0 100644 --- a/app/api/courrier/account/route.ts +++ b/app/api/courrier/account/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; -import { saveUserEmailCredentials, testEmailConnection } from '@/lib/services/email-service'; +import { saveUserEmailCredentials } from '@/lib/services/email-service'; // Define EmailCredentials interface inline since we're having import issues interface EmailCredentials { @@ -29,7 +29,17 @@ export async function POST(request: Request) { } // Parse request body - const body = await request.json(); + const body = await request.json().catch(e => { + console.error('Error parsing request body:', e); + return {}; + }); + + // Log the request (but hide password) + console.log('Adding account:', { + ...body, + password: body.password ? '***' : undefined + }); + const { email, password, @@ -44,18 +54,33 @@ export async function POST(request: Request) { } = body; // Validate required fields - if (!email || !password || !host || !port) { + const missingFields = []; + if (!email) missingFields.push('email'); + if (!password) missingFields.push('password'); + if (!host) missingFields.push('host'); + if (port === undefined) missingFields.push('port'); + + if (missingFields.length > 0) { + console.error(`Missing required fields: ${missingFields.join(', ')}`); return NextResponse.json( - { error: 'Required fields missing: email, password, host, port' }, + { error: `Required fields missing: ${missingFields.join(', ')}` }, { status: 400 } ); } + // Fix common hostname errors - strip http/https prefixes + let cleanHost = host; + if (cleanHost.startsWith('http://')) { + cleanHost = cleanHost.substring(7); + } else if (cleanHost.startsWith('https://')) { + cleanHost = cleanHost.substring(8); + } + // Create credentials object const credentials: EmailCredentials = { email, password, - host, + host: cleanHost, port: typeof port === 'string' ? parseInt(port) : port, secure: secure ?? true, // Optional SMTP settings @@ -67,40 +92,14 @@ export async function POST(request: Request) { ...(color && { color }) }; - // Test connection before saving - const connectionTest = await testEmailConnection(credentials); - - if (!connectionTest.imap) { - return NextResponse.json( - { - error: 'Failed to connect to IMAP server with provided credentials', - details: connectionTest.error - }, - { status: 400 } - ); - } - - // If SMTP details provided but connection failed - if (smtp_host && smtp_port && !connectionTest.smtp) { - return NextResponse.json( - { - error: 'IMAP connection successful, but SMTP connection failed', - details: connectionTest.error - }, - { status: 400 } - ); - } - + // Connection test is no longer needed here since we validate with the test-connection endpoint first // Save credentials to database and cache await saveUserEmailCredentials(session.user.id, credentials); + console.log(`Email account successfully added for user ${session.user.id}`); return NextResponse.json({ success: true, - message: 'Email account added successfully', - connectionStatus: { - imap: connectionTest.imap, - smtp: connectionTest.smtp - } + message: 'Email account added successfully' }); } catch (error) { console.error('Error adding email account:', error); diff --git a/app/api/courrier/test-connection/route.ts b/app/api/courrier/test-connection/route.ts new file mode 100644 index 00000000..81c0d1fa --- /dev/null +++ b/app/api/courrier/test-connection/route.ts @@ -0,0 +1,137 @@ +import { NextResponse } from 'next/server'; +import { ImapFlow } from 'imapflow'; +import nodemailer from 'nodemailer'; + +export async function POST(request: Request) { + try { + // Parse request body + const body = await request.json().catch(e => { + console.error('Error parsing request body:', e); + return {}; + }); + + // Log request but hide password + console.log('Testing connection with:', { + ...body, + password: body.password ? '***' : undefined + }); + + const { email, password, host, port, secure = true } = body; + + // Validate required fields + if (!email || !password || !host || !port) { + const missing = []; + if (!email) missing.push('email'); + if (!password) missing.push('password'); + if (!host) missing.push('host'); + if (!port) missing.push('port'); + + return NextResponse.json( + { error: `Missing required fields: ${missing.join(', ')}` }, + { status: 400 } + ); + } + + // Fix common hostname errors - strip http/https prefixes + let cleanHost = host; + if (cleanHost.startsWith('http://')) { + cleanHost = cleanHost.substring(7); + } else if (cleanHost.startsWith('https://')) { + cleanHost = cleanHost.substring(8); + } + + console.log(`Testing IMAP connection to ${cleanHost}:${port} for ${email}`); + + // Test IMAP connection + const client = new ImapFlow({ + host: cleanHost, + port: typeof port === 'string' ? parseInt(port) : port, + secure: secure === true || secure === 'true', + auth: { + user: email, + pass: password, + }, + logger: false, + tls: { + rejectUnauthorized: false + }, + // Set timeout to prevent long waits + connectionTimeout: 10000 + }); + + try { + await client.connect(); + console.log(`IMAP connection successful for ${email}`); + + // Try to list mailboxes + const mailboxes = await client.list(); + const folderNames = mailboxes.map(mailbox => mailbox.path); + console.log(`Found ${folderNames.length} folders:`, folderNames.slice(0, 5)); + + try { + await client.logout(); + } catch (e) { + // Ignore logout errors + } + + return NextResponse.json({ + success: true, + message: 'IMAP connection successful', + details: { + host: cleanHost, + port, + folderCount: folderNames.length, + sampleFolders: folderNames.slice(0, 5) + } + }); + } catch (error) { + console.error('IMAP connection test failed:', error); + + let friendlyMessage = 'Connection failed'; + let errorDetails = ''; + + if (error instanceof Error) { + errorDetails = error.message; + + if (error.message.includes('Invalid login') || error.message.includes('authentication failed')) { + friendlyMessage = 'Invalid username or password'; + } else if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) { + friendlyMessage = 'Cannot connect to server - check host and port'; + } else if (error.message.includes('certificate')) { + friendlyMessage = 'SSL/TLS certificate issue'; + } else if (error.message.includes('timeout')) { + friendlyMessage = 'Connection timed out'; + } + } + + try { + await client.logout(); + } catch (e) { + // Ignore logout errors + } + + return NextResponse.json( + { + error: friendlyMessage, + details: errorDetails, + debug: { + providedHost: host, + cleanHost, + port, + secure + } + }, + { status: 400 } + ); + } + } catch (error) { + console.error('Error testing connection:', error); + return NextResponse.json( + { + error: 'Failed to test connection', + details: error instanceof Error ? error.message : 'Unknown error' + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 13578f1c..7ba319c1 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -458,48 +458,79 @@ export default function CourrierPage() { setLoading(true); const formData = new FormData(e.currentTarget); - const newAccount = { - email: formData.get('email'), - password: formData.get('password'), - host: formData.get('host'), - port: parseInt(formData.get('port') as string), + + // Pull values from form with proper type handling + const formValues = { + email: formData.get('email')?.toString() || '', + password: formData.get('password')?.toString() || '', + host: formData.get('host')?.toString() || '', + port: parseInt(formData.get('port')?.toString() || '993'), secure: formData.get('secure') === 'on', - display_name: formData.get('display_name') || formData.get('email'), - color: formData.get('color') || '#0082c9', - smtp_host: formData.get('smtp_host'), - smtp_port: formData.get('smtp_port') ? parseInt(formData.get('smtp_port') as string) : undefined, + display_name: formData.get('display_name')?.toString() || '', + smtp_host: formData.get('smtp_host')?.toString() || '', + smtp_port: formData.get('smtp_port')?.toString() ? + parseInt(formData.get('smtp_port')?.toString() || '587') : undefined, smtp_secure: formData.get('smtp_secure') === 'on' }; + // If display_name is empty, use email + if (!formValues.display_name) { + formValues.display_name = formValues.email; + } + try { - const response = await fetch('/api/courrier/account', { + // First test the connection + const testResponse = await fetch('/api/courrier/test-connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newAccount) + body: JSON.stringify({ + email: formValues.email, + password: formValues.password, + host: formValues.host, + port: formValues.port, + secure: formValues.secure + }) }); - const result = await response.json(); + const testResult = await testResponse.json(); - if (!response.ok) { - throw new Error(result.error || 'Failed to add account'); + if (!testResponse.ok) { + throw new Error(testResult.error || 'Connection test failed'); + } + + console.log('Connection test successful:', testResult); + + // If connection test is successful, save the account + const saveResponse = await fetch('/api/courrier/account', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formValues) + }); + + const saveResult = await saveResponse.json(); + + if (!saveResponse.ok) { + throw new Error(saveResult.error || 'Failed to add account'); } // Update accounts list const newAccountObj = { id: Date.now(), // temporary ID - name: newAccount.display_name as string, - email: newAccount.email as string, + name: formValues.display_name, + email: formValues.email, color: `bg-blue-500`, // Default color class - folders: ['INBOX', 'Sent', 'Drafts', 'Trash'] // Default folders + folders: testResult.details.sampleFolders || ['INBOX', 'Sent', 'Drafts', 'Trash'] // Use discovered folders or defaults }; setAccounts(prev => [...prev, newAccountObj]); setShowAddAccountForm(false); toast({ title: "Account added successfully", - description: `Your email account ${newAccount.email} has been added.`, + description: `Your email account ${formValues.email} has been added.`, duration: 5000 }); } catch (error) {