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:
parent
77b6aa2752
commit
f593036d0f
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue