667 lines
21 KiB
TypeScript
667 lines
21 KiB
TypeScript
/**
|
|
* Unified Email Utilities
|
|
*
|
|
* This file provides backward compatibility for email utilities.
|
|
* New code should import directly from the specialized modules:
|
|
* - email-content.ts (content processing)
|
|
* - text-direction.ts (direction handling)
|
|
* - dom-purify-config.ts (sanitization)
|
|
*/
|
|
|
|
// Import from specialized modules
|
|
import { sanitizeHtml } from './dom-purify-config';
|
|
import { detectTextDirection, applyTextDirection } from './text-direction';
|
|
import {
|
|
extractEmailContent,
|
|
formatEmailContent,
|
|
processHtmlContent,
|
|
formatPlainTextToHtml,
|
|
isHtmlContent,
|
|
extractTextFromHtml
|
|
} from './email-content';
|
|
|
|
import {
|
|
EmailMessage,
|
|
EmailContent,
|
|
EmailAddress,
|
|
LegacyEmailMessage
|
|
} from '@/types/email';
|
|
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
|
|
import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder';
|
|
import { format } from 'date-fns';
|
|
|
|
// Re-export important functions for backward compatibility
|
|
export {
|
|
sanitizeHtml,
|
|
extractEmailContent,
|
|
formatEmailContent,
|
|
processHtmlContent,
|
|
formatPlainTextToHtml,
|
|
detectTextDirection,
|
|
applyTextDirection
|
|
};
|
|
|
|
/**
|
|
* Standard interface for formatted email responses
|
|
*/
|
|
export interface FormattedEmail {
|
|
to: string;
|
|
cc?: string;
|
|
subject: string;
|
|
content: EmailContent;
|
|
attachments?: Array<{
|
|
filename: string;
|
|
contentType: string;
|
|
content?: string;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* Format email addresses for display
|
|
* Can handle both array of EmailAddress objects or a string
|
|
*/
|
|
export function formatEmailAddresses(addresses: EmailAddress[] | string | undefined): string {
|
|
if (!addresses) return '';
|
|
|
|
// If already a string, return as is
|
|
if (typeof addresses === 'string') {
|
|
return addresses;
|
|
}
|
|
|
|
// If array, format each address
|
|
if (Array.isArray(addresses) && addresses.length > 0) {
|
|
return addresses.map(addr =>
|
|
addr.name && addr.name !== addr.address
|
|
? `${addr.name} <${addr.address}>`
|
|
: addr.address
|
|
).join(', ');
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Format date for display
|
|
*/
|
|
export function formatEmailDate(date: Date | string | undefined): string {
|
|
if (!date) return '';
|
|
|
|
try {
|
|
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
return dateObj.toLocaleString('en-US', {
|
|
weekday: 'short',
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
} catch (e) {
|
|
return typeof date === 'string' ? date : date.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Normalize email content to our standard format regardless of input format
|
|
*/
|
|
export function normalizeEmailContent(email: any): EmailMessage {
|
|
if (!email) {
|
|
throw new Error('Cannot normalize null or undefined email');
|
|
}
|
|
|
|
// First check if this is a MIME format email that needs decoding
|
|
if (email.content && isMimeFormat(email.content)) {
|
|
try {
|
|
console.log('Detected MIME format email, decoding...');
|
|
// We need to force cast here due to type incompatibility between EmailMessage and the mime result
|
|
const adaptedEmail = adaptMimeEmail(email);
|
|
return {
|
|
...adaptedEmail,
|
|
flags: adaptedEmail.flags || [] // Ensure flags is always an array
|
|
} as EmailMessage;
|
|
} catch (error) {
|
|
console.error('Error decoding MIME email:', error);
|
|
// Continue with regular normalization if MIME decoding fails
|
|
}
|
|
}
|
|
|
|
// Check if it's already in the standardized format
|
|
if (email.content && typeof email.content === 'object' &&
|
|
(email.content.html !== undefined || email.content.text !== undefined)) {
|
|
// Already in the correct format
|
|
return email as EmailMessage;
|
|
}
|
|
|
|
// Otherwise, adapt from legacy format
|
|
// We need to force cast here due to type incompatibility
|
|
const adaptedEmail = adaptLegacyEmail(email);
|
|
return {
|
|
...adaptedEmail,
|
|
flags: adaptedEmail.flags || [] // Ensure flags is always an array
|
|
} as EmailMessage;
|
|
}
|
|
|
|
/**
|
|
* Render normalized email content into HTML for display
|
|
*/
|
|
export function renderEmailContent(content: EmailContent | null): string {
|
|
if (!content) {
|
|
return '<div class="email-content-empty">No content available</div>';
|
|
}
|
|
|
|
// Create a simple object that can be processed by formatEmailContent
|
|
const emailObj = { content };
|
|
|
|
// Use the centralized formatting function
|
|
return formatEmailContent(emailObj);
|
|
}
|
|
|
|
/**
|
|
* Get recipient addresses from an email for reply or forward
|
|
*/
|
|
function getRecipientAddresses(email: any, type: 'reply' | 'reply-all'): { to: string; cc: string } {
|
|
// Format the recipients
|
|
const to = Array.isArray(email.from)
|
|
? email.from.map((addr: any) => {
|
|
if (typeof addr === 'string') return addr;
|
|
return addr.address ? addr.address : '';
|
|
}).filter(Boolean).join(', ')
|
|
: typeof email.from === 'string'
|
|
? email.from
|
|
: '';
|
|
|
|
// For reply-all, include other recipients in CC
|
|
let cc = '';
|
|
if (type === 'reply-all') {
|
|
const toRecipients = Array.isArray(email.to)
|
|
? email.to.map((addr: any) => {
|
|
if (typeof addr === 'string') return addr;
|
|
return addr.address ? addr.address : '';
|
|
}).filter(Boolean)
|
|
: typeof email.to === 'string'
|
|
? [email.to]
|
|
: [];
|
|
|
|
const ccRecipients = Array.isArray(email.cc)
|
|
? email.cc.map((addr: any) => {
|
|
if (typeof addr === 'string') return addr;
|
|
return addr.address ? addr.address : '';
|
|
}).filter(Boolean)
|
|
: typeof email.cc === 'string'
|
|
? [email.cc]
|
|
: [];
|
|
|
|
cc = [...toRecipients, ...ccRecipients].join(', ');
|
|
}
|
|
|
|
return { to, cc };
|
|
}
|
|
|
|
/**
|
|
* Get formatted header information for reply or forward
|
|
*/
|
|
function getFormattedHeaderInfo(email: any): {
|
|
fromStr: string;
|
|
toStr: string;
|
|
ccStr: string;
|
|
dateStr: string;
|
|
subject: string;
|
|
} {
|
|
// Format the subject
|
|
const subject = email.subject && !email.subject.startsWith('Re:') && !email.subject.startsWith('Fwd:')
|
|
? email.subject
|
|
: email.subject || '';
|
|
|
|
// Format the date
|
|
const dateStr = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date';
|
|
|
|
// Format sender
|
|
const fromStr = Array.isArray(email.from)
|
|
? email.from.map((addr: any) => {
|
|
if (typeof addr === 'string') return addr;
|
|
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
}).join(', ')
|
|
: typeof email.from === 'string'
|
|
? email.from
|
|
: 'Unknown Sender';
|
|
|
|
// Format recipients
|
|
const toStr = Array.isArray(email.to)
|
|
? email.to.map((addr: any) => {
|
|
if (typeof addr === 'string') return addr;
|
|
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
}).join(', ')
|
|
: typeof email.to === 'string'
|
|
? email.to
|
|
: '';
|
|
|
|
// Format CC
|
|
const ccStr = Array.isArray(email.cc)
|
|
? email.cc.map((addr: any) => {
|
|
if (typeof addr === 'string') return addr;
|
|
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
}).join(', ')
|
|
: typeof email.cc === 'string'
|
|
? email.cc
|
|
: '';
|
|
|
|
return { fromStr, toStr, ccStr, dateStr, subject };
|
|
}
|
|
|
|
/**
|
|
* Format email for reply
|
|
*/
|
|
export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessage | null, type: 'reply' | 'reply-all' = 'reply'): FormattedEmail {
|
|
console.log('formatReplyEmail called:', { type, emailId: originalEmail?.id });
|
|
|
|
if (!originalEmail) {
|
|
console.warn('formatReplyEmail: No original email provided');
|
|
return {
|
|
to: '',
|
|
subject: '',
|
|
content: { text: '', html: '', isHtml: false, direction: 'ltr' }
|
|
};
|
|
}
|
|
|
|
// Adapt legacy format if needed
|
|
const email = 'content' in originalEmail ? originalEmail : adaptLegacyEmail(originalEmail);
|
|
|
|
// Format subject with Re: prefix
|
|
const subject = email.subject ?
|
|
(email.subject.toLowerCase().startsWith('re:') ? email.subject : `Re: ${email.subject}`) :
|
|
'Re: ';
|
|
|
|
// Get recipient addresses
|
|
const { to, cc } = getRecipientAddresses(email, type);
|
|
|
|
// Get email content and sanitize it
|
|
const originalContent = email.content;
|
|
|
|
// Extract text and html content
|
|
let htmlContent = '';
|
|
let textContent = '';
|
|
let direction: 'ltr' | 'rtl' = 'ltr';
|
|
|
|
// Handle different content formats
|
|
if (typeof originalContent === 'string') {
|
|
console.log('formatReplyEmail: content is string, length:', originalContent.length);
|
|
// Simple string content
|
|
textContent = originalContent;
|
|
const isHtml = isHtmlContent(originalContent);
|
|
if (isHtml) {
|
|
htmlContent = originalContent;
|
|
} else {
|
|
// If it's plain text, convert to HTML
|
|
htmlContent = formatPlainTextToHtml(originalContent);
|
|
}
|
|
}
|
|
else if (originalContent) {
|
|
console.log('formatReplyEmail: content is object:', {
|
|
hasHtml: !!originalContent.html,
|
|
htmlLength: originalContent.html?.length || 0,
|
|
hasText: !!originalContent.text,
|
|
textLength: originalContent.text?.length || 0,
|
|
direction: originalContent.direction
|
|
});
|
|
|
|
// Standard EmailContent object
|
|
htmlContent = originalContent.html || '';
|
|
textContent = originalContent.text || '';
|
|
direction = originalContent.direction || 'ltr' as const;
|
|
|
|
// If no HTML but has text, convert text to HTML
|
|
if (!htmlContent && textContent) {
|
|
htmlContent = formatPlainTextToHtml(textContent);
|
|
}
|
|
}
|
|
|
|
// Get quote header
|
|
const { fromStr, dateStr } = getFormattedHeaderInfo(email);
|
|
|
|
// Use the from name if available, otherwise use email address
|
|
const sender = fromStr;
|
|
const date = dateStr;
|
|
|
|
// Create the quoted reply content
|
|
if (htmlContent) {
|
|
// Format HTML reply with better styling for quoted content
|
|
console.log('Formatting HTML reply, quoted content length:', htmlContent.length);
|
|
|
|
// Apply minimal sanitization to the original content - preserve more structure
|
|
// We'll do a more comprehensive sanitization later in the flow
|
|
const sanitizedOriginal = sanitizeHtml(htmlContent, { preserveReplyFormat: true });
|
|
|
|
// Check if original content already contains blockquotes or is reply/forward
|
|
const containsExistingQuote =
|
|
sanitizedOriginal.includes('<blockquote') ||
|
|
sanitizedOriginal.includes('wrote:') ||
|
|
sanitizedOriginal.includes('---------- Forwarded message ----------');
|
|
|
|
// Preserve existing quotes and add outer quote
|
|
htmlContent = `
|
|
<div style="margin: 20px 0 10px 0; color: #666; border-bottom: 1px solid #ddd; padding-bottom: 5px;">
|
|
On ${date}, ${sender} wrote:
|
|
</div>
|
|
<blockquote style="margin: 0; padding-left: 10px; border-left: 3px solid #ddd; color: #505050; background-color: #f9f9f9; padding: 10px;">
|
|
${sanitizedOriginal}
|
|
</blockquote>
|
|
`;
|
|
}
|
|
|
|
if (textContent) {
|
|
// Format plain text reply
|
|
const lines = textContent.split(/\r\n|\r|\n/);
|
|
textContent = `On ${date}, ${sender} wrote:\n\n${lines.map(line => `> ${line}`).join('\n')}`;
|
|
}
|
|
|
|
const result = {
|
|
to,
|
|
cc: cc || undefined,
|
|
subject,
|
|
content: {
|
|
html: htmlContent,
|
|
text: textContent,
|
|
isHtml: true,
|
|
direction,
|
|
},
|
|
attachments: email.attachments?.map(att => {
|
|
// Create properly typed attachment
|
|
if ('name' in att) {
|
|
return {
|
|
filename: att.filename || att.name || 'attachment',
|
|
contentType: att.contentType || 'application/octet-stream',
|
|
content: att.content
|
|
};
|
|
}
|
|
return {
|
|
filename: att.filename || 'attachment',
|
|
contentType: att.contentType || 'application/octet-stream',
|
|
content: att.content
|
|
};
|
|
})
|
|
};
|
|
|
|
console.log('formatReplyEmail result:', {
|
|
to: result.to,
|
|
subject: result.subject,
|
|
hasHtml: !!result.content.html,
|
|
htmlLength: result.content.html?.length || 0,
|
|
hasText: !!result.content.text,
|
|
textLength: result.content.text?.length || 0
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Process and replace CID references with base64 data URLs using the email's attachments.
|
|
* This function should be called before sanitizing the content.
|
|
*/
|
|
export function processCidReferences(htmlContent: string, attachments?: Array<{
|
|
filename?: string;
|
|
name?: string;
|
|
contentType?: string;
|
|
content?: string;
|
|
contentId?: string;
|
|
}>): string {
|
|
if (!htmlContent || !attachments || !attachments.length) {
|
|
return htmlContent;
|
|
}
|
|
|
|
console.log(`Processing CID references with ${attachments.length} attachments available`);
|
|
|
|
try {
|
|
// Create a map of content IDs to their attachment data
|
|
const cidMap = new Map();
|
|
attachments.forEach(att => {
|
|
if (att.contentId) {
|
|
// Content ID sometimes has <> brackets which need to be removed
|
|
const cleanCid = att.contentId.replace(/[<>]/g, '');
|
|
cidMap.set(cleanCid, {
|
|
contentType: att.contentType || 'application/octet-stream',
|
|
content: att.content
|
|
});
|
|
console.log(`Mapped CID: ${cleanCid} to attachment of type ${att.contentType || 'unknown'}`);
|
|
}
|
|
});
|
|
|
|
// If we have no content IDs mapped, return original content
|
|
if (cidMap.size === 0) {
|
|
console.log('No CID references found in attachments');
|
|
return htmlContent;
|
|
}
|
|
|
|
// Check if we're in a browser environment
|
|
if (typeof document === 'undefined') {
|
|
console.log('Not in browser environment, skipping CID processing');
|
|
return htmlContent;
|
|
}
|
|
|
|
// Parse the HTML content and replace CID references
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = htmlContent;
|
|
|
|
// Find all images with CID sources
|
|
const imgElements = tempDiv.querySelectorAll('img[src^="cid:"]');
|
|
|
|
console.log(`Found ${imgElements.length} img elements with CID references`);
|
|
|
|
if (imgElements.length === 0) {
|
|
return htmlContent;
|
|
}
|
|
|
|
// Process each image with a CID reference
|
|
let replacedCount = 0;
|
|
imgElements.forEach(img => {
|
|
const src = img.getAttribute('src');
|
|
if (!src || !src.startsWith('cid:')) return;
|
|
|
|
// Extract the content ID from the src
|
|
const cid = src.substring(4); // Remove 'cid:' prefix
|
|
|
|
// Find the matching attachment
|
|
const attachment = cidMap.get(cid);
|
|
|
|
if (attachment && attachment.content) {
|
|
// Convert the attachment content to a data URL
|
|
const dataUrl = `data:${attachment.contentType};base64,${attachment.content}`;
|
|
|
|
// Replace the CID reference with the data URL
|
|
img.setAttribute('src', dataUrl);
|
|
replacedCount++;
|
|
console.log(`Replaced CID ${cid} with data URL`);
|
|
} else {
|
|
console.log(`No matching attachment found for CID: ${cid}`);
|
|
}
|
|
});
|
|
|
|
console.log(`Replaced ${replacedCount} CID references with data URLs`);
|
|
|
|
// Return the updated HTML content
|
|
return tempDiv.innerHTML;
|
|
} catch (error) {
|
|
console.error('Error processing CID references:', error);
|
|
return htmlContent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format email for forwarding
|
|
*/
|
|
export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMessage | null): FormattedEmail {
|
|
console.log('formatForwardedEmail called, emailId:', originalEmail?.id);
|
|
|
|
if (!originalEmail) {
|
|
console.warn('formatForwardedEmail: No original email provided');
|
|
return {
|
|
to: '',
|
|
subject: '',
|
|
content: { text: '', html: '', isHtml: false, direction: 'ltr' }
|
|
};
|
|
}
|
|
|
|
// Adapt legacy format if needed
|
|
const email = 'content' in originalEmail ? originalEmail : adaptLegacyEmail(originalEmail);
|
|
|
|
// Format subject with Fwd: prefix
|
|
const subject = email.subject ?
|
|
(email.subject.toLowerCase().startsWith('fwd:') ? email.subject : `Fwd: ${email.subject}`) :
|
|
'Fwd: ';
|
|
|
|
// Get email content
|
|
const originalContent = email.content;
|
|
|
|
// Extract text and html content
|
|
let htmlContent = '';
|
|
let textContent = '';
|
|
let direction: 'ltr' | 'rtl' = 'ltr';
|
|
|
|
// Handle different content formats
|
|
if (typeof originalContent === 'string') {
|
|
console.log('formatForwardedEmail: content is string, length:', originalContent.length);
|
|
// Simple string content
|
|
textContent = originalContent;
|
|
const isHtml = isHtmlContent(originalContent);
|
|
if (isHtml) {
|
|
htmlContent = originalContent;
|
|
} else {
|
|
// If it's plain text, convert to HTML
|
|
htmlContent = formatPlainTextToHtml(originalContent);
|
|
}
|
|
}
|
|
else if (originalContent) {
|
|
console.log('formatForwardedEmail: content is object:', {
|
|
hasHtml: !!originalContent.html,
|
|
htmlLength: originalContent.html?.length || 0,
|
|
hasText: !!originalContent.text,
|
|
textLength: originalContent.text?.length || 0,
|
|
direction: originalContent.direction
|
|
});
|
|
|
|
// Standard EmailContent object
|
|
htmlContent = originalContent.html || '';
|
|
textContent = originalContent.text || '';
|
|
direction = originalContent.direction || 'ltr' as const;
|
|
|
|
// If no HTML but has text, convert text to HTML
|
|
if (!htmlContent && textContent) {
|
|
htmlContent = formatPlainTextToHtml(textContent);
|
|
}
|
|
}
|
|
|
|
// Get header info for the forwarded message
|
|
const headerInfo = getFormattedHeaderInfo(email);
|
|
|
|
// Create the forwarded content
|
|
if (htmlContent) {
|
|
console.log('Formatting HTML forward, content length:', htmlContent.length);
|
|
|
|
// Apply minimal sanitization to the original content - preserve more structure
|
|
// We'll do a more comprehensive sanitization later in the flow
|
|
const sanitizedOriginal = sanitizeHtml(htmlContent, { preserveReplyFormat: true });
|
|
|
|
// Create forwarded message with header info
|
|
htmlContent = `
|
|
<div style="margin: 20px 0 10px 0; color: #666; font-family: Arial, sans-serif;">
|
|
<div style="border-bottom: 1px solid #ccc; margin-bottom: 10px; padding-bottom: 5px;">
|
|
<div>---------------------------- Forwarded Message ----------------------------</div>
|
|
</div>
|
|
<table style="margin-bottom: 10px; font-size: 14px; border-collapse: collapse;">
|
|
<tr>
|
|
<td style="padding: 3px 10px 3px 0; font-weight: bold; text-align: right; vertical-align: top;">From:</td>
|
|
<td style="padding: 3px 0;">${headerInfo.fromStr}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 3px 10px 3px 0; font-weight: bold; text-align: right; vertical-align: top;">Date:</td>
|
|
<td style="padding: 3px 0;">${headerInfo.dateStr}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 3px 10px 3px 0; font-weight: bold; text-align: right; vertical-align: top;">Subject:</td>
|
|
<td style="padding: 3px 0;">${headerInfo.subject}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 3px 10px 3px 0; font-weight: bold; text-align: right; vertical-align: top;">To:</td>
|
|
<td style="padding: 3px 0;">${headerInfo.toStr}</td>
|
|
</tr>
|
|
${headerInfo.ccStr ? `
|
|
<tr>
|
|
<td style="padding: 3px 10px 3px 0; font-weight: bold; text-align: right; vertical-align: top;">Cc:</td>
|
|
<td style="padding: 3px 0;">${headerInfo.ccStr}</td>
|
|
</tr>` : ''}
|
|
</table>
|
|
<div style="border-bottom: 1px solid #ccc; margin-top: 5px; margin-bottom: 15px; padding-bottom: 5px;">
|
|
<div>----------------------------------------------------------------------</div>
|
|
</div>
|
|
</div>
|
|
<div class="forwarded-content" style="margin: 0; color: #333;">
|
|
${sanitizedOriginal}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (textContent) {
|
|
// Format plain text forward
|
|
textContent = `
|
|
---------- Forwarded message ----------
|
|
From: ${headerInfo.fromStr}
|
|
Date: ${headerInfo.dateStr}
|
|
Subject: ${headerInfo.subject}
|
|
To: ${headerInfo.toStr}
|
|
${headerInfo.ccStr ? `Cc: ${headerInfo.ccStr}` : ''}
|
|
|
|
${textContent}
|
|
`.trim();
|
|
}
|
|
|
|
const result = {
|
|
to: '',
|
|
subject,
|
|
content: {
|
|
html: htmlContent,
|
|
text: textContent,
|
|
isHtml: true,
|
|
direction,
|
|
},
|
|
attachments: email.attachments?.map(att => {
|
|
// Create properly typed attachment
|
|
if ('name' in att) {
|
|
return {
|
|
filename: att.filename || att.name || 'attachment',
|
|
contentType: att.contentType || 'application/octet-stream',
|
|
content: att.content
|
|
};
|
|
}
|
|
return {
|
|
filename: att.filename || 'attachment',
|
|
contentType: att.contentType || 'application/octet-stream',
|
|
content: att.content
|
|
};
|
|
})
|
|
};
|
|
|
|
console.log('formatForwardedEmail result:', {
|
|
subject: result.subject,
|
|
hasHtml: !!result.content.html,
|
|
htmlLength: result.content.html?.length || 0,
|
|
hasText: !!result.content.text,
|
|
textLength: result.content.text?.length || 0
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Format an email for reply or reply-all - canonical implementation
|
|
*/
|
|
export function formatEmailForReplyOrForward(
|
|
email: EmailMessage | LegacyEmailMessage | null,
|
|
type: 'reply' | 'reply-all' | 'forward'
|
|
): FormattedEmail {
|
|
// Use our dedicated formatters
|
|
if (type === 'forward') {
|
|
return formatForwardedEmail(email);
|
|
} else {
|
|
return formatReplyEmail(email, type as 'reply' | 'reply-all');
|
|
}
|
|
}
|