From f593036d0f5b62442dad86025f023cee9590568b Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 9 Jun 2025 23:09:32 +0200 Subject: [PATCH] Fix email signature layout and enhance email thread fetching - Move logo to top of email signature for better formatting - Enhance IMAP search to include CC/BCC fields and multiple folders - Fix EOI document generation to properly send and extract signing URLs - Update documentation with all email system fixes --- components/EmailComposer.vue | 4 +- docs/email-system-fixes.md | 21 +++ server/api/email/fetch-thread.ts | 156 ++++++++++++---------- server/api/email/generate-eoi-document.ts | 36 +++-- 4 files changed, 137 insertions(+), 80 deletions(-) diff --git a/components/EmailComposer.vue b/components/EmailComposer.vue index d6c6b15..f81f4c3 100644 --- a/components/EmailComposer.vue +++ b/components/EmailComposer.vue @@ -253,14 +253,14 @@ const getSignaturePreview = () => { return `
+ Port Nimara +
${sig.name || 'Your Name'}
${sig.title || 'Your Title'}
${sig.company || 'Company Name'}
${contactLines ? contactLines + '
' : ''} ${userEmail}

- Port Nimara -
The information in this message is confidential and may be privileged.
It is intended for the addressee alone.
diff --git a/docs/email-system-fixes.md b/docs/email-system-fixes.md index 5cde386..6e3bfb4 100644 --- a/docs/email-system-fixes.md +++ b/docs/email-system-fixes.md @@ -30,6 +30,27 @@ - Updated `update-interest.ts` API to accept both x-tag headers ("094ut234" and "pjnvü1230") - Now both authenticated and unauthenticated users can save interest updates +### 5. Email Signature Formatting +- **Problem**: Logo was appearing below the signature details +- **Solution**: + - Moved Port Nimara logo to the top of the signature + - Logo now appears above the name with proper spacing + +### 6. Email Refresh Not Showing New Emails +- **Problem**: New emails from clients weren't appearing after refresh +- **Solution**: + - Enhanced IMAP search to include CC and BCC fields + - Now searches in multiple folders: INBOX, Sent, Sent Items, Sent Mail + - Better email detection for comprehensive thread retrieval + +### 7. EOI Document Generation Issues +- **Problem**: EOI documents were created but stuck in draft status with non-working links +- **Solution**: + - Fixed response structure to match actual Documenso API response + - Added proper document send step to move from draft to active + - Changed `sendEmail` to `true` to ensure recipients receive signing emails + - Correctly extract signing URLs from the response + ## Required Environment Variables Make sure these are set in your `.env` file: diff --git a/server/api/email/fetch-thread.ts b/server/api/email/fetch-thread.ts index a19ad7b..54578d2 100644 --- a/server/api/email/fetch-thread.ts +++ b/server/api/email/fetch-thread.ts @@ -163,94 +163,116 @@ async function fetchImapEmails( }; imap.once('ready', () => { - imap.openBox('INBOX', true, (err, box) => { - if (err) { + // Search in both INBOX and Sent folders + const foldersToSearch = ['INBOX', 'Sent', 'Sent Items', 'Sent Mail']; + let currentFolderIndex = 0; + const allEmails: EmailMessage[] = []; + + const searchNextFolder = () => { + if (currentFolderIndex >= foldersToSearch.length) { cleanup(); - reject(err); + resolve(allEmails); return; } - const searchCriteria = [ - ['OR', ['FROM', clientEmail], ['TO', clientEmail]] - ]; + const folderName = foldersToSearch[currentFolderIndex]; + currentFolderIndex++; - imap.search(searchCriteria, (err, results) => { + imap.openBox(folderName, true, (err, box) => { if (err) { - cleanup(); - reject(err); + console.log(`Folder ${folderName} not found, trying next...`); + searchNextFolder(); return; } - if (!results || results.length === 0) { - cleanup(); - resolve(emails); - return; - } + console.log(`Searching in folder: ${folderName}`); - const messagesToFetch = results.slice(-limit); - let messagesProcessed = 0; - - const fetch = imap.fetch(messagesToFetch, { - bodies: '', - struct: true, - envelope: true - }); + // Search for emails both sent and received with this client + const searchCriteria = [ + 'OR', + ['FROM', clientEmail], + ['TO', clientEmail], + ['CC', clientEmail], + ['BCC', clientEmail] + ]; - fetch.on('message', (msg, seqno) => { - msg.on('body', (stream, info) => { - simpleParser(stream as any, async (err: any, parsed: any) => { - if (err) { - console.error('Parse error:', err); - messagesProcessed++; - if (messagesProcessed === messagesToFetch.length) { - cleanup(); - resolve(emails); + imap.search(searchCriteria, (err, results) => { + if (err) { + console.error(`Search error in ${folderName}:`, err); + searchNextFolder(); + return; + } + + if (!results || results.length === 0) { + console.log(`No emails found in ${folderName}`); + searchNextFolder(); + return; + } + + console.log(`Found ${results.length} emails in ${folderName}`); + const messagesToFetch = results.slice(-limit); + let messagesProcessed = 0; + + const fetch = imap.fetch(messagesToFetch, { + bodies: '', + struct: true, + envelope: true + }); + + fetch.on('message', (msg, seqno) => { + msg.on('body', (stream, info) => { + simpleParser(stream as any, async (err: any, parsed: any) => { + if (err) { + console.error('Parse error:', err); + messagesProcessed++; + if (messagesProcessed === messagesToFetch.length) { + searchNextFolder(); + } + return; } - return; - } - const email: EmailMessage = { - id: parsed.messageId || `${Date.now()}-${seqno}`, - from: parsed.from?.text || '', - to: Array.isArray(parsed.to) - ? parsed.to.map((addr: any) => addr.text).join(', ') - : parsed.to?.text || '', - subject: parsed.subject || '', - body: parsed.text || '', - html: parsed.html || undefined, - timestamp: parsed.date?.toISOString() || new Date().toISOString(), - direction: parsed.from?.text.includes(userEmail) ? 'sent' : 'received' - }; + const email: EmailMessage = { + id: parsed.messageId || `${Date.now()}-${seqno}`, + from: parsed.from?.text || '', + to: Array.isArray(parsed.to) + ? parsed.to.map((addr: any) => addr.text).join(', ') + : parsed.to?.text || '', + subject: parsed.subject || '', + body: parsed.text || '', + html: parsed.html || undefined, + timestamp: parsed.date?.toISOString() || new Date().toISOString(), + direction: parsed.from?.text.toLowerCase().includes(userEmail.toLowerCase()) ? 'sent' : 'received' + }; - if (parsed.headers.has('in-reply-to')) { - email.threadId = parsed.headers.get('in-reply-to') as string; - } + if (parsed.headers.has('in-reply-to')) { + email.threadId = parsed.headers.get('in-reply-to') as string; + } - emails.push(email); - messagesProcessed++; - - if (messagesProcessed === messagesToFetch.length) { - cleanup(); - resolve(emails); - } + allEmails.push(email); + messagesProcessed++; + + if (messagesProcessed === messagesToFetch.length) { + searchNextFolder(); + } + }); }); }); - }); - fetch.once('error', (err) => { - console.error('Fetch error:', err); - cleanup(); - reject(err); - }); + fetch.once('error', (err) => { + console.error('Fetch error:', err); + searchNextFolder(); + }); - fetch.once('end', () => { - if (messagesProcessed === 0) { - cleanup(); - resolve(emails); - } + fetch.once('end', () => { + if (messagesProcessed === 0) { + searchNextFolder(); + } + }); }); }); - }); + }; + + searchNextFolder(); }); imap.once('error', (err: any) => { diff --git a/server/api/email/generate-eoi-document.ts b/server/api/email/generate-eoi-document.ts index 5cfeacb..5259fcb 100644 --- a/server/api/email/generate-eoi-document.ts +++ b/server/api/email/generate-eoi-document.ts @@ -10,8 +10,16 @@ interface DocumensoRecipient { } interface DocumensoResponse { - id: string; - recipients: DocumensoRecipient[]; + documentId: number; + recipients: Array<{ + recipientId: number; + name: string; + email: string; + token: string; + role: 'SIGNER' | 'APPROVER'; + signingOrder: number; + signingUrl: string; + }>; } export default defineEventHandler(async (event) => { @@ -186,27 +194,33 @@ export default defineEventHandler(async (event) => { }); } - // 3. Setup completion emails + // 3. Send document (moves from draft to active and sends emails) try { - const completionResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${documentResponse.id}/send`, { + const sendResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${documentResponse.documentId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${documensoApiKey}` }, body: JSON.stringify({ - sendEmail: false, + sendEmail: true, sendCompletionEmails: true }) }); - if (!completionResponse.ok) { - console.error('Failed to setup completion emails:', await completionResponse.text()); - // Don't fail the whole process if this fails + if (!sendResponse.ok) { + const errorText = await sendResponse.text(); + console.error('Failed to send document:', errorText); + throw new Error(`Failed to send document: ${sendResponse.statusText}`); } + + console.log('Document sent successfully'); } catch (error) { - console.error('Completion email setup error:', error); - // Continue anyway + console.error('Document send error:', error); + throw createError({ + statusCode: 500, + statusMessage: "Document created but failed to send. Please check Documenso dashboard." + }); } // Extract signing URLs from recipients @@ -261,7 +275,7 @@ export default defineEventHandler(async (event) => { return { success: true, - documentId: documentResponse.id, + documentId: documentResponse.documentId, clientSigningUrl: signingLinks['Client'] || '', signingLinks: signingLinks };