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 `
<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="color: #666; margin-bottom: 8px;">${sig.title || 'Your Title'}</div>
<div style="font-weight: bold; margin-bottom: 12px;">${sig.company || 'Company Name'}</div>
${contactLines ? contactLines + '<br>' : ''}
<a href="mailto:${userEmail}" style="color: #0066cc;">${userEmail}</a>
<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;">
The information in this message is confidential and may be privileged.<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")
- 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:

View File

@ -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) => {

View File

@ -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
};