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
This commit is contained in:
Matt 2025-06-09 23:09:32 +02:00
parent 77b6aa2752
commit f593036d0f
4 changed files with 137 additions and 80 deletions

View File

@ -253,14 +253,14 @@ const getSignaturePreview = () => {
return ` return `
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<img src="/Port_Nimara_Logo_2_Colour_New_Transparent.png" alt="Port Nimara" style="height: 60px; max-width: 200px; margin-bottom: 15px;">
<br>
<div style="font-weight: bold;">${sig.name || 'Your Name'}</div> <div style="font-weight: bold;">${sig.name || 'Your Name'}</div>
<div style="color: #666; margin-bottom: 8px;">${sig.title || 'Your Title'}</div> <div style="color: #666; margin-bottom: 8px;">${sig.title || 'Your Title'}</div>
<div style="font-weight: bold; margin-bottom: 12px;">${sig.company || 'Company Name'}</div> <div style="font-weight: bold; margin-bottom: 12px;">${sig.company || 'Company Name'}</div>
${contactLines ? contactLines + '<br>' : ''} ${contactLines ? contactLines + '<br>' : ''}
<a href="mailto:${userEmail}" style="color: #0066cc;">${userEmail}</a> <a href="mailto:${userEmail}" style="color: #0066cc;">${userEmail}</a>
<br><br> <br><br>
<img src="/Port_Nimara_Logo_2_Colour_New_Transparent.png" alt="Port Nimara" style="height: 60px; max-width: 200px;">
<br>
<div style="color: #666; font-size: 12px; margin-top: 10px;"> <div style="color: #666; font-size: 12px; margin-top: 10px;">
The information in this message is confidential and may be privileged.<br> The information in this message is confidential and may be privileged.<br>
It is intended for the addressee alone.<br> It is intended for the addressee alone.<br>

View File

@ -30,6 +30,27 @@
- Updated `update-interest.ts` API to accept both x-tag headers ("094ut234" and "pjnvü1230") - 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 - 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 ## Required Environment Variables
Make sure these are set in your `.env` file: Make sure these are set in your `.env` file:

View File

@ -163,30 +163,53 @@ async function fetchImapEmails(
}; };
imap.once('ready', () => { imap.once('ready', () => {
imap.openBox('INBOX', true, (err, box) => { // Search in both INBOX and Sent folders
if (err) { const foldersToSearch = ['INBOX', 'Sent', 'Sent Items', 'Sent Mail'];
let currentFolderIndex = 0;
const allEmails: EmailMessage[] = [];
const searchNextFolder = () => {
if (currentFolderIndex >= foldersToSearch.length) {
cleanup(); cleanup();
reject(err); resolve(allEmails);
return; return;
} }
const folderName = foldersToSearch[currentFolderIndex];
currentFolderIndex++;
imap.openBox(folderName, true, (err, box) => {
if (err) {
console.log(`Folder ${folderName} not found, trying next...`);
searchNextFolder();
return;
}
console.log(`Searching in folder: ${folderName}`);
// Search for emails both sent and received with this client
const searchCriteria = [ const searchCriteria = [
['OR', ['FROM', clientEmail], ['TO', clientEmail]] 'OR',
['FROM', clientEmail],
['TO', clientEmail],
['CC', clientEmail],
['BCC', clientEmail]
]; ];
imap.search(searchCriteria, (err, results) => { imap.search(searchCriteria, (err, results) => {
if (err) { if (err) {
cleanup(); console.error(`Search error in ${folderName}:`, err);
reject(err); searchNextFolder();
return; return;
} }
if (!results || results.length === 0) { if (!results || results.length === 0) {
cleanup(); console.log(`No emails found in ${folderName}`);
resolve(emails); searchNextFolder();
return; return;
} }
console.log(`Found ${results.length} emails in ${folderName}`);
const messagesToFetch = results.slice(-limit); const messagesToFetch = results.slice(-limit);
let messagesProcessed = 0; let messagesProcessed = 0;
@ -203,8 +226,7 @@ async function fetchImapEmails(
console.error('Parse error:', err); console.error('Parse error:', err);
messagesProcessed++; messagesProcessed++;
if (messagesProcessed === messagesToFetch.length) { if (messagesProcessed === messagesToFetch.length) {
cleanup(); searchNextFolder();
resolve(emails);
} }
return; return;
} }
@ -219,19 +241,18 @@ async function fetchImapEmails(
body: parsed.text || '', body: parsed.text || '',
html: parsed.html || undefined, html: parsed.html || undefined,
timestamp: parsed.date?.toISOString() || new Date().toISOString(), timestamp: parsed.date?.toISOString() || new Date().toISOString(),
direction: parsed.from?.text.includes(userEmail) ? 'sent' : 'received' direction: parsed.from?.text.toLowerCase().includes(userEmail.toLowerCase()) ? 'sent' : 'received'
}; };
if (parsed.headers.has('in-reply-to')) { if (parsed.headers.has('in-reply-to')) {
email.threadId = parsed.headers.get('in-reply-to') as string; email.threadId = parsed.headers.get('in-reply-to') as string;
} }
emails.push(email); allEmails.push(email);
messagesProcessed++; messagesProcessed++;
if (messagesProcessed === messagesToFetch.length) { if (messagesProcessed === messagesToFetch.length) {
cleanup(); searchNextFolder();
resolve(emails);
} }
}); });
}); });
@ -239,18 +260,19 @@ async function fetchImapEmails(
fetch.once('error', (err) => { fetch.once('error', (err) => {
console.error('Fetch error:', err); console.error('Fetch error:', err);
cleanup(); searchNextFolder();
reject(err);
}); });
fetch.once('end', () => { fetch.once('end', () => {
if (messagesProcessed === 0) { if (messagesProcessed === 0) {
cleanup(); searchNextFolder();
resolve(emails);
} }
}); });
}); });
}); });
};
searchNextFolder();
}); });
imap.once('error', (err: any) => { imap.once('error', (err: any) => {

View File

@ -10,8 +10,16 @@ interface DocumensoRecipient {
} }
interface DocumensoResponse { interface DocumensoResponse {
id: string; documentId: number;
recipients: DocumensoRecipient[]; recipients: Array<{
recipientId: number;
name: string;
email: string;
token: string;
role: 'SIGNER' | 'APPROVER';
signingOrder: number;
signingUrl: string;
}>;
} }
export default defineEventHandler(async (event) => { 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 { 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', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${documensoApiKey}` 'Authorization': `Bearer ${documensoApiKey}`
}, },
body: JSON.stringify({ body: JSON.stringify({
sendEmail: false, sendEmail: true,
sendCompletionEmails: true sendCompletionEmails: true
}) })
}); });
if (!completionResponse.ok) { if (!sendResponse.ok) {
console.error('Failed to setup completion emails:', await completionResponse.text()); const errorText = await sendResponse.text();
// Don't fail the whole process if this fails console.error('Failed to send document:', errorText);
throw new Error(`Failed to send document: ${sendResponse.statusText}`);
} }
console.log('Document sent successfully');
} catch (error) { } catch (error) {
console.error('Completion email setup error:', error); console.error('Document send error:', error);
// Continue anyway throw createError({
statusCode: 500,
statusMessage: "Document created but failed to send. Please check Documenso dashboard."
});
} }
// Extract signing URLs from recipients // Extract signing URLs from recipients
@ -261,7 +275,7 @@ export default defineEventHandler(async (event) => {
return { return {
success: true, success: true,
documentId: documentResponse.id, documentId: documentResponse.documentId,
clientSigningUrl: signingLinks['Client'] || '', clientSigningUrl: signingLinks['Client'] || '',
signingLinks: signingLinks signingLinks: signingLinks
}; };