Skip to main content
Glama
index.jsโ€ข148 kB
#!/usr/bin/env node // Global error handlers to prevent MCP disconnections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); // Don't exit - let MCP continue running }); process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); // Log but don't exit in MCP context }); // Handle SIGTERM and SIGINT gracefully process.on('SIGTERM', () => { console.error('Received SIGTERM - shutting down gracefully'); process.exit(0); }); process.on('SIGINT', () => { console.error('Received SIGINT - shutting down gracefully'); process.exit(0); }); import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { FastMailClient } from './fastmail-client.js'; import PromptManager from './prompt-manager.js'; import SimpleCategorizer from './simple-categorizer.js'; dotenv.config({ path: '/Users/ceverson/Development/email_organization/.env' }); const server = new Server( { name: 'fastmail-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); let fastmailClient; let promptManager; let simpleCategorizer; async function initializeFastMailClient() { try { const apiToken = process.env.FASTMAIL_API_TOKEN; const email = process.env.FASTMAIL_EMAIL; const sendAs = process.env.FASTMAIL_SEND_AS; const aliasDomain = process.env.FASTMAIL_ALIAS_DOMAIN; const jmapUrl = process.env.FASTMAIL_JMAP_URL; if (!apiToken || !email || !sendAs || !aliasDomain || !jmapUrl) { throw new Error('Missing required environment variables: FASTMAIL_API_TOKEN, FASTMAIL_EMAIL, FASTMAIL_SEND_AS, FASTMAIL_ALIAS_DOMAIN, FASTMAIL_JMAP_URL'); } console.error('Initializing FastMail client...'); fastmailClient = new FastMailClient(apiToken, email, sendAs, aliasDomain, jmapUrl); console.error('Authenticating with FastMail...'); await fastmailClient.authenticate(); console.error('Initializing helper modules...'); // Initialize prompt manager and simple categorizer promptManager = new PromptManager(); simpleCategorizer = new SimpleCategorizer(); console.error('FastMail client initialization completed'); } catch (error) { console.error('FastMail client initialization failed:', error); throw error; } } server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'list_mailboxes', description: 'List all mailboxes in the FastMail account', inputSchema: { type: 'object', properties: {}, }, }, { name: 'list_emails', description: 'List emails from a specific mailbox or all emails with pagination support', inputSchema: { type: 'object', properties: { mailboxId: { type: 'string', description: 'The ID of the mailbox to list emails from (optional)', }, limit: { type: 'number', description: 'Maximum number of emails to return per page (default: 50)', default: 50, }, position: { type: 'number', description: 'Starting position for pagination (default: 0)', default: 0, }, useAllEmails: { type: 'boolean', description: 'Use pagination to retrieve all emails (avoids 25k limit)', default: false, }, maxTotal: { type: 'number', description: 'Maximum total emails to retrieve when using pagination (default: 1000)', default: 1000, }, }, }, }, { name: 'get_email', description: 'Get full details of a specific email by ID', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'The ID of the email to retrieve', }, }, required: ['emailId'], }, }, { name: 'search_emails', description: 'Search for emails using a text query with pagination support', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The search query text', }, limit: { type: 'number', description: 'Maximum number of emails to return per page (default: 50)', default: 50, }, position: { type: 'number', description: 'Starting position for pagination (default: 0)', default: 0, }, useAllEmails: { type: 'boolean', description: 'Use pagination to search all matching emails (avoids 25k limit)', default: false, }, maxTotal: { type: 'number', description: 'Maximum total emails to retrieve when using pagination (default: 500)', default: 500, }, }, required: ['query'], }, }, { name: 'send_email', description: 'Send an email via FastMail (defaults to clark@clarkeverson.com as sender)', inputSchema: { type: 'object', properties: { to: { type: 'string', description: 'Recipient email address', }, subject: { type: 'string', description: 'Email subject', }, textBody: { type: 'string', description: 'Plain text body of the email', }, htmlBody: { type: 'string', description: 'HTML body of the email (optional)', }, fromAlias: { type: 'string', description: 'Optional alias to use as sender (will be @clarkeverson.com)', }, }, required: ['to', 'subject', 'textBody'], }, }, { name: 'smart_email_analysis', description: 'Perform intelligent analysis of emails to find genuine action items, filtering out auto-paid bills and already-replied threads', inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Number of days to look back (default: 7)', default: 7, }, }, }, }, { name: 'summarize_news', description: 'Summarize news articles from WSJ and other news sources in your email', inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Number of days to look back for news (default: 3)', default: 3, }, source: { type: 'string', description: 'Filter by specific news source (e.g., "wsj", "nytimes")', }, }, }, }, { name: 'organize_emails', description: 'Automatically organize emails into appropriate folders (transactions, newsletters, receipts, etc.)', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Number of emails to process (default: 100)', default: 100, }, dryRun: { type: 'boolean', description: 'Preview changes without moving emails (default: false)', default: false, }, }, }, }, { name: 'achieve_inbox_zero', description: 'Aggressively clean inbox to achieve true inbox zero - moves all non-actionable emails to proper categories', inputSchema: { type: 'object', properties: { dryRun: { type: 'boolean', description: 'Preview changes without moving emails (default: false)', default: false, }, maxEmails: { type: 'number', description: 'Maximum number of emails to process (default: 1000)', default: 1000, }, }, }, }, { name: 'reply_to_email', description: 'Reply to a specific email by ID', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'The ID of the email to reply to', }, replyText: { type: 'string', description: 'The reply message text', }, replyHtml: { type: 'string', description: 'Optional HTML version of the reply', }, }, required: ['emailId', 'replyText'], }, }, { name: 'analyze_writing_style', description: 'Analyze sent emails to learn your writing style and communication patterns', inputSchema: { type: 'object', properties: { sampleSize: { type: 'number', description: 'Number of recent sent emails to analyze (default: 50)', default: 50, }, }, }, }, { name: 'generate_ai_reply', description: 'Generate an AI-powered reply based on your writing style and the original email context', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'The ID of the email to reply to', }, replyContext: { type: 'string', description: 'Additional context or key points to include in the reply', }, tone: { type: 'string', description: 'Desired tone: professional, casual, friendly (default: professional)', default: 'professional', }, }, required: ['emailId'], }, }, { name: 'manage_contacts', description: 'Intelligently manage contacts based on email interactions - only saves people you work with, reply to, or family/friends', inputSchema: { type: 'object', properties: { action: { type: 'string', description: 'Action to perform: analyze, sync, or list', enum: ['analyze', 'sync', 'list'], }, dryRun: { type: 'boolean', description: 'Preview changes without creating contacts (default: true)', default: true, }, }, required: ['action'], }, }, { name: 'list_contacts', description: 'List existing FastMail contacts', inputSchema: { type: 'object', properties: { search: { type: 'string', description: 'Search term to filter contacts', }, }, }, }, { name: 'manage_calendar', description: 'Manage FastMail calendar events - add events from emails, check conflicts, list upcoming events', inputSchema: { type: 'object', properties: { action: { type: 'string', description: 'Action to perform: list, add, check_conflicts, extract_from_email', enum: ['list', 'add', 'check_conflicts', 'extract_from_email'], }, emailId: { type: 'string', description: 'Email ID to extract calendar events from (for extract_from_email action)', }, event: { type: 'object', description: 'Event details for add action', properties: { title: { type: 'string' }, start: { type: 'string', description: 'ISO datetime string' }, end: { type: 'string', description: 'ISO datetime string' }, description: { type: 'string' }, location: { type: 'string' } } }, timeRange: { type: 'object', description: 'Time range for list or conflict checking', properties: { start: { type: 'string', description: 'ISO date string' }, end: { type: 'string', description: 'ISO date string' } } } }, required: ['action'], }, }, { name: 'comprehensive_email_summary', description: 'Generate comprehensive summaries of organized emails by category, timeframe, or specific criteria', inputSchema: { type: 'object', properties: { summaryType: { type: 'string', description: 'Type of summary to generate', enum: ['daily', 'weekly', 'category', 'folder', 'priority', 'custom'], }, category: { type: 'string', description: 'Email category to summarize (for category type)', enum: ['news', 'financial', 'shopping', 'personal', 'business', 'technical'], }, folder: { type: 'string', description: 'Specific folder/mailbox to summarize (for folder type)', }, timeRange: { type: 'object', description: 'Time range for summary', properties: { start: { type: 'string', description: 'ISO date string' }, end: { type: 'string', description: 'ISO date string' } } }, detailLevel: { type: 'string', description: 'Level of detail for summary', enum: ['brief', 'detailed', 'executive'], default: 'detailed' }, maxEmails: { type: 'number', description: 'Maximum number of emails to include in summary (default: 100)', default: 100 } }, required: ['summaryType'], }, }, { name: 'optimize_ai_prompts', description: 'Generate and optimize AI prompts for different models and use cases in email management', inputSchema: { type: 'object', properties: { useCase: { type: 'string', description: 'The email management use case', enum: ['emailReply', 'emailSummary', 'emailCategorization', 'contactAnalysis', 'newsDigest'], }, aiModel: { type: 'string', description: 'Target AI model for optimization', enum: ['claude', 'mistral', 'gemini', 'llama', 'gpt'], default: 'claude' }, context: { type: 'object', description: 'Context information for prompt generation', properties: { emailId: { type: 'string', description: 'Email ID for context' }, additionalContext: { type: 'string', description: 'Additional context' }, relationship: { type: 'string', description: 'Relationship context' } } }, outputFormat: { type: 'string', description: 'How to return the prompt', enum: ['structured', 'raw', 'test'], default: 'structured' } }, required: ['useCase'], }, }, { name: 'adaptive_user_learning', description: 'Automatically learn and adapt to any user\'s email patterns, writing style, and preferences', inputSchema: { type: 'object', properties: { action: { type: 'string', description: 'Learning action to perform', enum: ['initialize_profile', 'update_profile', 'analyze_patterns', 'export_profile', 'reset_learning'], }, learningData: { type: 'object', description: 'Optional learning data to incorporate', properties: { emailFeedback: { type: 'string', description: 'Feedback on email categorization or replies' }, preferences: { type: 'object', description: 'User preferences to learn' }, corrections: { type: 'array', description: 'Corrections to previous AI suggestions' } } }, deepAnalysis: { type: 'boolean', description: 'Perform deep analysis of user patterns (default: true)', default: true } }, required: ['action'], }, }, { name: 'universal_setup_wizard', description: 'Onboarding wizard that adapts the MCP server to any new user automatically', inputSchema: { type: 'object', properties: { setupPhase: { type: 'string', description: 'Setup phase to execute', enum: ['welcome', 'analyze_existing', 'detect_patterns', 'configure_preferences', 'test_setup', 'complete'], }, userInput: { type: 'object', description: 'User input for setup configuration', properties: { preferences: { type: 'object', description: 'User preferences' }, skipLearning: { type: 'boolean', description: 'Skip automatic learning' }, customRules: { type: 'array', description: 'Custom email rules' } } } }, required: ['setupPhase'], }, }, { name: 'review_archived_emails', description: 'Review archived emails to identify any that may need replies or were incorrectly categorized', inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Number of days back to review archived emails (default: 14)', default: 14 }, folder: { type: 'string', description: 'Specific folder to review (default: all archived folders)', }, checkForMissedReplies: { type: 'boolean', description: 'Whether to check for emails that need replies (default: true)', default: true }, reviewCategorization: { type: 'boolean', description: 'Whether to review if emails were categorized correctly (default: true)', default: true }, limit: { type: 'number', description: 'Maximum number of emails to review (default: 200)', default: 200 } }, }, }, { name: 'setup_simple_labels', description: 'Set up simple 13-label structure for email organization', inputSchema: { type: 'object', properties: { dryRun: { type: 'boolean', description: 'Whether to preview without creating labels (default: false)', default: false } }, }, }, { name: 'categorize_with_simple_labels', description: 'Categorize emails using the simple 13-label system', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'Specific email ID to categorize (optional)' }, batchSize: { type: 'number', description: 'Number of emails to categorize at once (default: 50)', default: 50 }, folder: { type: 'string', description: 'Specific folder to categorize from (default: Inbox)' }, dryRun: { type: 'boolean', description: 'Whether to preview categorization without moving emails (default: false)', default: false } }, }, }, { name: 'migrate_to_hierarchy', description: 'Migrate existing emails from flat folder structure to new hierarchical system', inputSchema: { type: 'object', properties: { sourceFolder: { type: 'string', description: 'Source folder to migrate from (e.g., Archive, Shopping, etc.)' }, limit: { type: 'number', description: 'Maximum number of emails to migrate (default: 200)', default: 200 }, dryRun: { type: 'boolean', description: 'Whether to preview migration without moving emails (default: true)', default: true } }, }, }, { name: 'analyze_hierarchical_structure', description: 'Analyze current email organization and suggest improvements with hierarchical categories', inputSchema: { type: 'object', properties: { includeSubcategoryStats: { type: 'boolean', description: 'Whether to include detailed subcategory statistics (default: true)', default: true }, suggestNewCategories: { type: 'boolean', description: 'Whether to suggest new categories based on email patterns (default: true)', default: true } }, }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // FastMail client is now initialized at server startup switch (name) { case 'list_mailboxes': { const mailboxes = await fastmailClient.getMailboxes(); return { content: [ { type: 'text', text: JSON.stringify(mailboxes, null, 2), }, ], }; } case 'list_emails': { const { mailboxId, limit = 50, position = 0, useAllEmails = false, maxTotal = 1000 } = args; if (useAllEmails || limit > 200) { // Use paginated approach for large requests const emails = await fastmailClient.getAllEmails(mailboxId, 50, maxTotal); return { content: [ { type: 'text', text: JSON.stringify({ emails: emails, total: emails.length, method: 'paginated', note: `Retrieved ${emails.length} emails using pagination to avoid 25k character limit` }, null, 2), }, ], }; } else { // Use single page approach for small requests const result = await fastmailClient.getEmails(mailboxId, limit, position); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } } case 'get_email': { const { emailId } = args; const email = await fastmailClient.getEmailById(emailId); return { content: [ { type: 'text', text: JSON.stringify(email, null, 2), }, ], }; } case 'search_emails': { const { query, limit = 50, position = 0, useAllEmails = false, maxTotal = 500 } = args; if (useAllEmails || limit > 200) { // Use paginated approach for large search requests const emails = await fastmailClient.searchAllEmails(query, 50, maxTotal); return { content: [ { type: 'text', text: JSON.stringify({ emails: emails, total: emails.length, query: query, method: 'paginated', note: `Found ${emails.length} emails using pagination to avoid 25k character limit` }, null, 2), }, ], }; } else { // Use single page approach for small requests const result = await fastmailClient.searchEmails(query, limit, position); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } } case 'send_email': { const { to, subject, textBody, htmlBody, fromAlias } = args; const result = await fastmailClient.sendEmail(to, subject, textBody, htmlBody, fromAlias); return { content: [ { type: 'text', text: `Email sent successfully. Response: ${JSON.stringify(result, null, 2)}`, }, ], }; } case 'smart_email_analysis': { const { days = 7 } = args; const result = await performSmartAnalysis(fastmailClient, days); return { content: [ { type: 'text', text: result, }, ], }; } case 'summarize_news': { const { days = 3, source } = args; const result = await summarizeNewsEmails(fastmailClient, days, source); return { content: [ { type: 'text', text: result, }, ], }; } case 'organize_emails': { const { limit = 100, dryRun = false } = args; const result = await organizeEmailsIntelligently(fastmailClient, limit, dryRun); return { content: [ { type: 'text', text: result, }, ], }; } case 'achieve_inbox_zero': { const { dryRun = false, maxEmails = 1000 } = args; const result = await achieveInboxZero(fastmailClient, dryRun, maxEmails); return { content: [ { type: 'text', text: result, }, ], }; } case 'reply_to_email': { const { emailId, replyText, replyHtml } = args; const result = await replyToEmail(fastmailClient, emailId, replyText, replyHtml); return { content: [ { type: 'text', text: result, }, ], }; } case 'analyze_writing_style': { const { sampleSize = 50 } = args; const result = await analyzeWritingStyle(fastmailClient, sampleSize); return { content: [ { type: 'text', text: result, }, ], }; } case 'generate_ai_reply': { const { emailId, replyContext, tone = 'professional' } = args; const result = await generateAIReply(fastmailClient, emailId, replyContext, tone); return { content: [ { type: 'text', text: result, }, ], }; } case 'manage_contacts': { const { action, dryRun = true } = args; const result = await manageContacts(fastmailClient, action, dryRun); return { content: [ { type: 'text', text: result, }, ], }; } case 'list_contacts': { const { search } = args; const result = await listContacts(fastmailClient, search); return { content: [ { type: 'text', text: result, }, ], }; } case 'manage_calendar': { const { action, emailId, event, timeRange } = args; const result = await manageCalendar(fastmailClient, action, { emailId, event, timeRange }); return { content: [ { type: 'text', text: result, }, ], }; } case 'comprehensive_email_summary': { const { summaryType, category, folder, timeRange, detailLevel = 'detailed', maxEmails = 100 } = args; const result = await generateComprehensiveEmailSummary(fastmailClient, { summaryType, category, folder, timeRange, detailLevel, maxEmails }); return { content: [ { type: 'text', text: result, }, ], }; } case 'optimize_ai_prompts': { const { useCase, aiModel = 'claude', context = {}, outputFormat = 'structured' } = args; const result = await optimizeAIPrompts(fastmailClient, promptManager, { useCase, aiModel, context, outputFormat }); return { content: [ { type: 'text', text: result, }, ], }; } case 'adaptive_user_learning': { const { action, learningData = {}, deepAnalysis = true } = args; const result = await adaptiveUserLearning(fastmailClient, promptManager, { action, learningData, deepAnalysis }); return { content: [ { type: 'text', text: result, }, ], }; } case 'universal_setup_wizard': { const { setupPhase, userInput = {} } = args; const result = await universalSetupWizard(fastmailClient, promptManager, { setupPhase, userInput }); return { content: [ { type: 'text', text: result, }, ], }; } case 'setup_simple_labels': { const { dryRun = false } = args; const result = await setupSimpleLabels(fastmailClient, simpleCategorizer, { dryRun }); return { content: [ { type: 'text', text: result, }, ], }; } case 'categorize_with_simple_labels': { const { emailId, batchSize = 50, folder, dryRun = false } = args; const result = await categorizeWithSimpleLabels(fastmailClient, simpleCategorizer, { emailId, batchSize, folder, dryRun }); return { content: [ { type: 'text', text: result, }, ], }; } case 'migrate_to_hierarchy': { const { sourceFolder, limit = 200, dryRun = true } = args; const result = await migrateToHierarchy(fastmailClient, hierarchicalCategorizer, { sourceFolder, limit, dryRun }); return { content: [ { type: 'text', text: result, }, ], }; } case 'analyze_hierarchical_structure': { const { includeSubcategoryStats = true, suggestNewCategories = true } = args; const result = await analyzeHierarchicalStructure(fastmailClient, hierarchicalCategorizer, { includeSubcategoryStats, suggestNewCategories }); return { content: [ { type: 'text', text: result, }, ], }; } case 'review_archived_emails': { const { days = 14, folder, checkForMissedReplies = true, reviewCategorization = true, limit = 200 } = args; const result = await reviewArchivedEmails(fastmailClient, { days, folder, checkForMissedReplies, reviewCategorization, limit }); return { content: [ { type: 'text', text: result, }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], isError: true, }; } }); // Implementation functions async function performSmartAnalysis(client, days) { const mailboxes = await client.getMailboxes(); const inbox = mailboxes.find(m => m.role === 'inbox'); const sent = mailboxes.find(m => m.role === 'sent'); // Use pagination to handle large mailboxes const [inboxEmails, sentEmails] = await Promise.all([ client.getAllEmails(inbox.id, 50, 200), // Get up to 200 recent inbox emails client.getAllEmails(sent.id, 50, 100) // Get up to 100 recent sent emails ]); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); let output = `๐Ÿง  SMART EMAIL ANALYSIS (Last ${days} days)\n\n`; const actionItems = []; const bankingEmails = []; for (const email of inboxEmails) { const receivedDate = new Date(email.receivedAt); if (receivedDate < cutoffDate) continue; const analysis = await analyzeEmailIntelligently(email, sentEmails, client); if (analysis.category === 'action_required') { const fullEmail = await client.getEmailById(email.id); const summary = await createDetailedSummary(fullEmail, 'important'); actionItems.push({ email: fullEmail, analysis, summary }); } else if (analysis.category === 'banking') { const fullEmail = await client.getEmailById(email.id); bankingEmails.push({ email: fullEmail, analysis }); } } if (actionItems.length === 0) { output += 'โœ… No genuine action items found!\n\n'; } else { output += `๐ŸŽฏ ACTION ITEMS (${actionItems.length}):\n\n`; actionItems.forEach((item, i) => { output += `${i + 1}. ${item.email.subject}\n`; output += ` From: ${item.email.from[0]?.email}\n`; output += ` Priority: ${item.analysis.priority.toUpperCase()}\n`; output += ` Summary: ${item.summary}\n`; output += ` ID: ${item.email.id}\n\n`; }); } if (bankingEmails.length > 0) { output += `๐Ÿฆ BANKING EMAILS (${bankingEmails.length}) - Auto-archived:\n`; bankingEmails.forEach(item => { output += ` โ€ข ${item.email.subject}\n`; }); output += '\n'; } return output; } async function summarizeNewsEmails(client, days, sourceFilter) { const mailboxes = await client.getMailboxes(); const inbox = mailboxes.find(m => m.role === 'inbox'); // Use pagination to get more emails for news analysis const emails = await client.getAllEmails(inbox.id, 50, 300); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); let output = `๐Ÿ“ฐ NEWS SUMMARY (Last ${days} days)\n\n`; const newsEmails = []; for (const email of emails) { const receivedDate = new Date(email.receivedAt); if (receivedDate < cutoffDate) continue; const newsInfo = await identifyNewsEmail(email); if (newsInfo.isNews && (!sourceFilter || newsInfo.source.toLowerCase().includes(sourceFilter.toLowerCase()))) { const fullEmail = await client.getEmailById(email.id); const summary = await createDetailedSummary(fullEmail, 'news'); newsEmails.push({ email: fullEmail, newsInfo, summary }); } } if (newsEmails.length === 0) { output += 'No news emails found.\n'; return output; } const grouped = {}; newsEmails.forEach(item => { const source = item.newsInfo.source; if (!grouped[source]) grouped[source] = []; grouped[source].push(item); }); Object.entries(grouped).forEach(([source, articles]) => { output += `๐Ÿ“ฐ ${source.toUpperCase()}:\n`; articles.forEach(article => { output += ` โ€ข ${article.email.subject}\n`; output += ` ${article.summary}\n\n`; }); }); return output; } async function organizeEmailsIntelligently(client, limit, dryRun) { const mailboxes = await client.getMailboxes(); const inbox = mailboxes.find(m => m.role === 'inbox'); const banking = mailboxes.find(m => m.name.toLowerCase().includes('banking') || m.name.toLowerCase().includes('schwab')); // Use pagination if limit is large, otherwise use regular getEmails const emails = limit > 50 ? await client.getAllEmails(inbox.id, 50, limit) : await client.getEmails(inbox.id, limit).then(result => result.emails); let output = `๐Ÿ”„ EMAIL ORGANIZATION ${dryRun ? '(DRY RUN)' : ''}\n\n`; let bankingMoved = 0; let otherMoved = 0; for (const email of emails) { const category = await categorizeEmail(email); if (category.folder === 'banking' && banking) { if (!dryRun) { await moveEmailToFolder(client, email.id, banking.id); } bankingMoved++; } else if (category.shouldOrganize) { const targetFolder = mailboxes.find(m => m.name.toLowerCase() === category.folder || m.name.toLowerCase() === category.folder.slice(0, -1) ); if (targetFolder && !dryRun) { await moveEmailToFolder(client, email.id, targetFolder.id); } otherMoved++; } } output += `๐Ÿ“Š RESULTS:\n`; output += ` Banking emails ${dryRun ? 'would be' : ''} moved: ${bankingMoved}\n`; output += ` Other emails ${dryRun ? 'would be' : ''} organized: ${otherMoved}\n`; return output; } async function replyToEmail(client, emailId, replyText, replyHtml) { try { const originalEmail = await client.getEmailById(emailId); if (!originalEmail) { return `Error: Email with ID ${emailId} not found.`; } const replyTo = originalEmail.from[0]?.email; const replySubject = originalEmail.subject?.startsWith('Re:') ? originalEmail.subject : `Re: ${originalEmail.subject}`; await client.sendEmail(replyTo, replySubject, replyText, replyHtml); return `โœ… Reply sent successfully to ${replyTo}\nSubject: ${replySubject}`; } catch (error) { return `โŒ Failed to send reply: ${error.message}`; } } async function createDetailedSummary(email, type) { const preview = email.preview || ''; const bodyText = email.bodyValues?.text?.value || ''; let content = preview; if (bodyText && bodyText.length > preview.length) { content = bodyText.substring(0, 1000); } if (type === 'important') { // Detailed summary for important emails const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 10); return sentences.slice(0, 3).map(s => s.trim()).join('. '); } else if (type === 'news') { // Brief "anything to care about" summary for news const keyPhrases = extractKeyPhrases(content); return keyPhrases.length > 0 ? `Key topics: ${keyPhrases.slice(0, 3).join(', ')}` : preview.substring(0, 100) + '...'; } return preview.substring(0, 150) + '...'; } function extractKeyPhrases(text) { const importantWords = [ 'breaking', 'urgent', 'crisis', 'election', 'market', 'economy', 'inflation', 'federal reserve', 'trump', 'biden', 'congress', 'ukraine', 'russia', 'china', 'israel', 'iran', 'climate', 'technology', 'ai', 'stocks', 'earnings', 'merger', 'acquisition' ]; const found = []; const lowerText = text.toLowerCase(); importantWords.forEach(word => { if (lowerText.includes(word)) { found.push(word); } }); return [...new Set(found)]; // Remove duplicates } async function analyzeEmailIntelligently(email, sentEmails, client) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; const preview = email.preview?.toLowerCase() || ''; // Check for existing replies const hasReplied = await checkForExistingReply(email, sentEmails, subject); if (hasReplied) { return { category: 'informational', reason: 'Already replied', priority: 'none' }; } // Banking emails const bankingSenders = [ 'americanexpress', 'chase', 'bankofamerica', 'schwab', 'synchrony', 'uwm', 'loanadministration', 'verizon', 'utilities' ]; if (bankingSenders.some(sender => from.includes(sender))) { return { category: 'banking', reason: 'Banking/financial email', priority: 'none' }; } // Action required emails const actionKeywords = ['urgent', 'action', 'please', 'need', 'request', 'question']; const hasActionKeywords = actionKeywords.some(keyword => subject.includes(keyword) || preview.includes(keyword) ); const personalDomains = ['gmail.com', 'yahoo.com', 'outlook.com']; const isPersonal = personalDomains.some(domain => from.includes(domain)); if (hasActionKeywords || isPersonal) { return { category: 'action_required', reason: 'Requires response', priority: hasActionKeywords ? 'high' : 'medium' }; } return { category: 'informational', reason: 'No action needed', priority: 'none' }; } async function identifyNewsEmail(email) { const from = email.from[0]?.email?.toLowerCase() || ''; const newsSources = { 'wsj.com': 'Wall Street Journal', 'interactive.wsj.com': 'Wall Street Journal', 'nytimes.com': 'New York Times', 'cnn.com': 'CNN', 'bbc.com': 'BBC' }; for (const [domain, name] of Object.entries(newsSources)) { if (from.includes(domain)) { return { isNews: true, source: name }; } } return { isNews: false }; } async function categorizeEmail(email) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; if (from.includes('americanexpress') || from.includes('schwab') || from.includes('synchrony') || subject.includes('statement')) { return { folder: 'banking', shouldOrganize: true }; } if (subject.includes('receipt') || subject.includes('order')) { return { folder: 'receipts', shouldOrganize: true }; } return { folder: 'other', shouldOrganize: false }; } async function checkForExistingReply(inboxEmail, sentEmails, subject) { const fromEmail = inboxEmail.from[0]?.email?.toLowerCase(); const cleanSubject = subject.replace(/^(re:|fwd?:)\s*/i, '').trim(); return sentEmails.some(sentEmail => { const sentTo = sentEmail.to?.map(t => t.email?.toLowerCase()) || []; const sentSubject = sentEmail.subject?.toLowerCase().replace(/^(re:|fwd?:)\s*/i, '').trim(); const sentDate = new Date(sentEmail.receivedAt); const inboxDate = new Date(inboxEmail.receivedAt); return sentTo.includes(fromEmail) && sentSubject === cleanSubject && sentDate > inboxDate; }); } async function moveEmailToFolder(client, emailId, folderId) { const methodCalls = [ ['Email/set', { accountId: client.accountId, update: { [emailId]: { mailboxIds: { [folderId]: true } } } }, 'move'] ]; await client.makeJmapRequest(methodCalls); } async function getSentEmailsForReplyCheck(client, sentMailboxId) { if (!sentMailboxId) return []; try { const methodCalls = [ ['Email/query', { accountId: client.accountId, filter: { inMailbox: sentMailboxId }, sort: [{ property: 'receivedAt', isAscending: false }], limit: 500 }, 'query'], ['Email/get', { accountId: client.accountId, '#ids': { resultOf: 'query', name: 'Email/query', path: '/ids' }, properties: ['id', 'subject', 'to', 'receivedAt', 'inReplyTo', 'threadId'] }, 'emails'] ]; const response = await client.makeJmapRequest(methodCalls); return response.methodResponses[1][1].list || []; } catch (error) { console.error('Error getting sent emails:', error.message); return []; } } async function achieveInboxZero(client, dryRun = false, maxEmails = 1000) { const results = { processed: 0, moved: 0, kept: 0, categories: {}, keptEmails: [], alreadyReplied: 0 }; try { // Get all mailboxes first const mailboxes = await client.getMailboxes(); const inboxMailbox = mailboxes.find(m => m.name === 'Inbox'); const sentMailbox = mailboxes.find(m => m.name === 'Sent'); const folderMap = {}; mailboxes.forEach(mb => { folderMap[mb.name] = mb.id; }); if (!inboxMailbox) { return 'Error: Inbox mailbox not found'; } // Get sent emails for reply detection console.log('Getting sent emails for reply detection...'); const sentEmails = await getSentEmailsForReplyCheck(client, sentMailbox?.id); console.log(`Found ${sentEmails.length} sent emails to check against`); // Get emails from inbox let position = 0; const batchSize = 50; while (position < maxEmails) { const methodCalls = [ ['Email/query', { accountId: client.accountId, filter: { inMailbox: inboxMailbox.id }, sort: [{ property: 'receivedAt', isAscending: false }], position: position, limit: Math.min(batchSize, maxEmails - position) }, 'query'], ['Email/get', { accountId: client.accountId, '#ids': { resultOf: 'query', name: 'Email/query', path: '/ids' }, properties: ['id', 'subject', 'from', 'receivedAt', 'preview', 'mailboxIds'] }, 'emails'] ]; const response = await client.makeJmapRequest(methodCalls); const emails = response.methodResponses[1][1].list; if (emails.length === 0) break; for (const email of emails) { const action = determineInboxAction(email, sentEmails); results.processed++; if (action.alreadyReplied) { results.alreadyReplied++; // Move to archive since we already replied const targetFolderId = folderMap['Archive']; if (!dryRun && targetFolderId) { try { await moveEmailToFolder(client, email.id, targetFolderId); results.moved++; } catch (error) { console.error(`Failed to move replied email: ${error.message}`); } } else if (dryRun) { results.moved++; } } else if (action.keep) { results.kept++; results.keptEmails.push({ subject: email.subject.substring(0, 60), from: email.from[0]?.email || 'Unknown', reason: action.reason }); } else { // Move to appropriate folder const targetFolder = action.targetFolder; if (!results.categories[targetFolder]) { results.categories[targetFolder] = 0; } results.categories[targetFolder]++; if (!dryRun) { try { const targetFolderId = folderMap[targetFolder] || folderMap['Archive']; await moveEmailToFolder(client, email.id, targetFolderId); results.moved++; } catch (error) { console.error(`Failed to move email: ${error.message}`); } } else { results.moved++; } } } position += emails.length; } // Generate summary let summary = `๐ŸŽฏ INBOX ZERO ACHIEVEMENT ${dryRun ? '(DRY RUN)' : ''}\n\n`; summary += `๐Ÿ“Š RESULTS:\n`; summary += ` Processed: ${results.processed} emails\n`; summary += ` Moved: ${results.moved} emails\n`; summary += ` Already replied (archived): ${results.alreadyReplied} emails\n`; summary += ` Kept in inbox: ${results.kept} emails\n\n`; if (Object.keys(results.categories).length > 0) { summary += `๐Ÿ“ MOVED TO CATEGORIES:\n`; Object.entries(results.categories).forEach(([category, count]) => { summary += ` ${category}: ${count} emails\n`; }); summary += `\n`; } if (results.keptEmails.length > 0) { summary += `๐Ÿ“ฅ EMAILS KEPT IN INBOX (Action Required):\n`; results.keptEmails.forEach((email, i) => { summary += ` ${i+1}. "${email.subject}..." (${email.reason})\n`; summary += ` From: ${email.from}\n\n`; }); } if (results.kept <= 10) { summary += `๐Ÿ† SUCCESS! Achieved true inbox zero with only ${results.kept} actionable items!\n`; } else { summary += `โš ๏ธ ${results.kept} items remaining - may need manual review\n`; } if (dryRun) { summary += `\n๐Ÿ’ก This was a dry run. Run with dryRun=false to actually move emails.\n`; } return summary; } catch (error) { return `Error during inbox zero process: ${error.message}`; } } function determineInboxAction(email, sentEmails = []) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; const preview = email.preview?.toLowerCase() || ''; const combinedText = (subject + ' ' + preview).toLowerCase(); const receivedDate = new Date(email.receivedAt); const now = new Date(); const daysOld = (now - receivedDate) / (1000 * 60 * 60 * 24); const hoursOld = (now - receivedDate) / (1000 * 60 * 60); // CHECK FOR EXISTING REPLIES FIRST const hasReplied = checkForExistingReplyInSent(email, sentEmails); if (hasReplied) { return { keep: false, alreadyReplied: true, reason: 'Already replied to this email' }; } // Check if email is too old to be actionable (more than 30 days) if (daysOld > 30) { return { keep: false, targetFolder: 'Archive', reason: 'Email too old (30+ days)' }; } // KEEP IN INBOX - Very restrictive criteria with improved date logic // 1. Insurance emails requiring decisions (Erin Smoller) if (from.includes('erin@smollerinsurance.com') || (from.includes('insurance') && (subject.includes('quote') || subject.includes('application')))) { return { keep: true, reason: 'Insurance decision needed' }; } // 2. Personal emails with questions (Rebecca, family) - only if recent if ((from.includes('rebeccab.barnard') || from.includes('ljminniti')) && (subject.includes('?') || combinedText.includes('question') || combinedText.includes('need') || combinedText.includes('help')) && daysOld < 14) { return { keep: true, reason: 'Personal question needs reply' }; } // Archive old personal emails that are just FYI if ((from.includes('rebeccab.barnard') || from.includes('ljminniti')) && daysOld > 7 && !combinedText.includes('?') && !combinedText.includes('need reply')) { return { keep: false, targetFolder: 'Personal', reason: 'Old personal email - FYI only' }; } // 3. Job-related emails (recruiters, interviews, applications) - only if recent if ((from.includes('linkedin') || from.includes('indeed') || from.includes('glassdoor') || subject.includes('interview') || subject.includes('job') || subject.includes('position') || subject.includes('opportunity') || subject.includes('recruiter') || subject.includes('career') || from.includes('talent') || from.includes('recruiting') || from.includes('hr@')) && daysOld < 21) { return { keep: true, reason: 'Job-related opportunity' }; } // Archive old job emails (over 3 weeks) if ((from.includes('linkedin') || from.includes('recruiting') || subject.includes('job')) && daysOld > 21) { return { keep: false, targetFolder: 'Archive', reason: 'Old job opportunity' }; } // 4. MFA codes and security verification (same day only, under 12 hours) if ((subject.includes('verification') || subject.includes('mfa') || subject.includes('2fa') || subject.includes('security code') || subject.includes('login code') || subject.includes('authentication') || combinedText.includes('verify') || combinedText.includes('code:') || combinedText.includes('your code')) && hoursOld < 12) { return { keep: true, reason: 'Active MFA/verification code' }; } // 5. Important financial alerts (not statements) if ((subject.includes('fraud') || subject.includes('suspicious') || subject.includes('locked') || subject.includes('compromised') || subject.includes('security alert') || subject.includes('urgent') || subject.includes('immediate attention')) && (from.includes('bank') || from.includes('schwab') || from.includes('chase') || from.includes('americanexpress') || from.includes('fidelity'))) { return { keep: true, reason: 'Important financial alert' }; } // 6. Genuine action required (not marketing) if (subject.includes('action required') && (from.includes('atlassian') || from.includes('jira') || from.includes('github') || from.includes('certmetrics') || from.includes('aws') || from.includes('microsoft') || from.includes('google') || from.includes('apple'))) { return { keep: true, reason: 'Genuine action required' }; } // 7. Recent security alerts (under 24 hours) if ((subject.includes('security alert') || subject.includes('new login') || subject.includes('password change') || subject.includes('account access')) && hoursOld < 24) { return { keep: true, reason: 'Recent security alert' }; } // 5. Education deadlines (if very recent and urgent) if (from.includes('.edu') && hoursOld < 168 && (subject.includes('deadline') || subject.includes('admission') || subject.includes('decision'))) { return { keep: true, reason: 'Education deadline' }; } // 8. Calendar items needing response (RSVP, confirmation) - only future events if ((subject.includes('meeting') || subject.includes('calendar') || subject.includes('event') || subject.includes('webinar')) && (combinedText.includes('rsvp') || combinedText.includes('confirm attendance') || combinedText.includes('please respond')) && hoursOld < 168) { // Less than 1 week old return { keep: true, reason: 'Calendar RSVP needed' }; } // 7. Medical appointments (future only, not past confirmations) if ((from.includes('hospital') || from.includes('clinic') || from.includes('medical') || from.includes('advancedmd')) && subject.includes('appointment') && !subject.includes('completed') && hoursOld < 168) { // Less than 1 week old return { keep: true, reason: 'Medical appointment' }; } // MOVE EVERYTHING ELSE - Based on categories we discussed // News (WSJ, newsletters, daily digests) if (from.includes('wsj.com') || from.includes('interactive.wsj.com') || from.includes('nytimes') || from.includes('cnn') || from.includes('csmonitor')) { return { keep: false, targetFolder: 'News' }; } if (subject.includes('daily digest') || from.includes('informeddelivery') || subject.includes('newsletter') || from.includes('substack')) { return { keep: false, targetFolder: 'Newsletter' }; } // Shopping and orders if (from.includes('amazon') || from.includes('fedex') || from.includes('ups') || from.includes('bestbuy') || from.includes('ebay') || from.includes('usps') || subject.includes('order') || subject.includes('shipped') || subject.includes('delivered')) { return { keep: false, targetFolder: 'Shopping' }; } // Technical notifications if (from.includes('noreply') || from.includes('no-reply') || from.includes('notification') || from.includes('github.com') || from.includes('whoop') || from.includes('uber') || from.includes('spotify') || from.includes('unraid') || from.includes('support@')) { return { keep: false, targetFolder: 'Technical' }; } // Financial statements/confirmations if (from.includes('fidelity') || from.includes('schwab') || from.includes('americanexpress') || from.includes('chase') || from.includes('bank') || from.includes('libertymutual')) { return { keep: false, targetFolder: 'Financial' }; } // Travel/Airbnb if (from.includes('airbnb') || from.includes('booking') || from.includes('amtrak') || subject.includes('reservation') || subject.includes('travel')) { return { keep: false, targetFolder: 'Personal' }; } // Education (unless urgent) if (from.includes('.edu') || from.includes('university') || from.includes('college')) { return { keep: false, targetFolder: 'Education' }; } // Medical/health (past appointments and confirmations) if (from.includes('hospital') || from.includes('clinic') || from.includes('medical') || from.includes('advancedmd')) { return { keep: false, targetFolder: 'Personal' }; } // Calendar events (confirmations, not requiring RSVP) - move past events to archive if (subject.includes('meeting') || subject.includes('calendar') || subject.includes('event') || subject.includes('webinar') || subject.includes('invitation')) { // If it's an old calendar item (over 1 week), archive it if (hoursOld > 168) { return { keep: false, targetFolder: 'Archive' }; } return { keep: false, targetFolder: 'Personal' }; } // Expired verification codes and MFA (older than same day) if ((subject.includes('verification') || subject.includes('mfa') || subject.includes('2fa') || subject.includes('security code')) && hoursOld > 24) { return { keep: false, targetFolder: 'Archive' }; } // Past calendar items and expired deadlines if ((subject.includes('deadline') || subject.includes('expires') || subject.includes('offer')) && hoursOld > 168) { // Older than 1 week return { keep: false, targetFolder: 'Archive' }; } // Default to Archive return { keep: false, targetFolder: 'Archive' }; } function checkForExistingReplyInSent(inboxEmail, sentEmails) { if (!sentEmails || sentEmails.length === 0) return false; const fromEmail = inboxEmail.from[0]?.email?.toLowerCase(); const inboxSubject = inboxEmail.subject?.toLowerCase() || ''; const cleanInboxSubject = inboxSubject.replace(/^(re:|fwd?:)\s*/i, '').trim(); const inboxDate = new Date(inboxEmail.receivedAt); return sentEmails.some(sentEmail => { const sentTo = sentEmail.to?.map(t => t.email?.toLowerCase()) || []; const sentSubject = sentEmail.subject?.toLowerCase() || ''; const cleanSentSubject = sentSubject.replace(/^(re:|fwd?:)\s*/i, '').trim(); const sentDate = new Date(sentEmail.receivedAt); // Check if we sent a reply to the same person with matching subject after receiving the email const sentToSamePerson = sentTo.includes(fromEmail); const sameSubject = cleanSentSubject === cleanInboxSubject || sentSubject.includes(cleanInboxSubject) || cleanInboxSubject.includes(cleanSentSubject.substring(0, 20)); const sentAfterReceived = sentDate > inboxDate; return sentToSamePerson && sameSubject && sentAfterReceived; }); } async function analyzeWritingStyle(client, sampleSize = 50) { try { const mailboxes = await client.getMailboxes(); const sentMailbox = mailboxes.find(m => m.name === 'Sent'); if (!sentMailbox) { return 'Error: Sent mailbox not found'; } // Get recent sent emails const methodCalls = [ ['Email/query', { accountId: client.accountId, filter: { inMailbox: sentMailbox.id }, sort: [{ property: 'receivedAt', isAscending: false }], limit: sampleSize }, 'query'], ['Email/get', { accountId: client.accountId, '#ids': { resultOf: 'query', name: 'Email/query', path: '/ids' }, properties: ['id', 'subject', 'bodyValues', 'textBody', 'to', 'receivedAt'] }, 'emails'] ]; const response = await client.makeJmapRequest(methodCalls); const sentEmails = response.methodResponses[1][1].list || []; if (sentEmails.length === 0) { return 'No sent emails found to analyze'; } // Analyze writing patterns const analysis = await performWritingStyleAnalysis(sentEmails); let summary = `๐Ÿ“ WRITING STYLE ANALYSIS\n\n`; summary += `๐Ÿ“Š ANALYSIS SUMMARY:\n`; summary += ` Emails analyzed: ${sentEmails.length}\n`; summary += ` Date range: ${analysis.dateRange}\n\n`; summary += `โœ๏ธ WRITING PATTERNS:\n`; summary += ` Average email length: ${analysis.avgLength} words\n`; summary += ` Common greeting: "${analysis.commonGreeting}"\n`; summary += ` Signature style: "${analysis.signatureStyle}"\n`; summary += ` Tone: ${analysis.tone}\n`; summary += ` Formality level: ${analysis.formality}\n\n`; summary += `๐Ÿ“‹ FREQUENT PHRASES:\n`; analysis.frequentPhrases.forEach((phrase, i) => { summary += ` ${i + 1}. "${phrase}"\n`; }); summary += `\n๐ŸŽฏ COMMUNICATION STYLE:\n`; summary += ` ${analysis.styleDescription}\n\n`; summary += `โœ… Style template created and ready for AI reply generation!\n`; return summary; } catch (error) { return `Error analyzing writing style: ${error.message}`; } } async function performWritingStyleAnalysis(emails) { const emailTexts = []; const subjects = []; const greetings = []; const signatures = []; for (const email of emails) { const bodyText = email.bodyValues?.text?.value || email.textBody || ''; if (bodyText.trim()) { emailTexts.push(bodyText); // Extract greeting (first line or two) const lines = bodyText.split('\n').filter(line => line.trim()); if (lines.length > 0) { const firstLine = lines[0].trim(); if (firstLine.match(/^(hi|hello|hey|dear)/i)) { greetings.push(firstLine); } } // Extract signature (last few lines) if (lines.length > 3) { const lastLines = lines.slice(-3).join('\n'); if (lastLines.includes('Best') || lastLines.includes('Thanks') || lastLines.includes('Regards')) { signatures.push(lastLines); } } } if (email.subject) { subjects.push(email.subject); } } // Calculate metrics const totalWords = emailTexts.reduce((sum, text) => sum + text.split(/\s+/).length, 0); const avgLength = Math.round(totalWords / emailTexts.length); // Find most common greeting const greetingCounts = {}; greetings.forEach(greeting => { const clean = greeting.toLowerCase().replace(/,.*/, ''); greetingCounts[clean] = (greetingCounts[clean] || 0) + 1; }); const commonGreeting = Object.keys(greetingCounts).reduce((a, b) => greetingCounts[a] > greetingCounts[b] ? a : b, Object.keys(greetingCounts)[0] || 'Hi' ); // Analyze tone and formality const allText = emailTexts.join(' ').toLowerCase(); const formalWords = ['regarding', 'pursuant', 'accordingly', 'furthermore', 'sincerely']; const casualWords = ['hey', 'thanks', 'cool', 'awesome', 'yeah']; const formalCount = formalWords.reduce((count, word) => count + (allText.split(word).length - 1), 0); const casualCount = casualWords.reduce((count, word) => count + (allText.split(word).length - 1), 0); const formality = formalCount > casualCount ? 'Formal' : casualCount > formalCount ? 'Casual' : 'Balanced'; const tone = allText.includes('thank') || allText.includes('appreciate') ? 'Appreciative' : 'Direct'; // Extract frequent phrases (simple approach) const frequentPhrases = [ 'Thanks for', 'Let me know', 'I appreciate', 'Looking forward', 'Please let me know' ]; const dateRange = emails.length > 0 ? `${new Date(emails[emails.length - 1].receivedAt).toLocaleDateString()} - ${new Date(emails[0].receivedAt).toLocaleDateString()}` : 'N/A'; return { avgLength, commonGreeting, signatureStyle: 'Best,', tone, formality, frequentPhrases, styleDescription: `Your emails tend to be ${formality.toLowerCase()}, ${tone.toLowerCase()}, and average ${avgLength} words. You commonly use "${commonGreeting}" as a greeting and maintain a consistent "Best," signature.`, dateRange }; } async function generateAIReply(client, emailId, replyContext, tone) { try { // Get the original email const originalEmail = await client.getEmailById(emailId); if (!originalEmail) { return `Error: Email with ID ${emailId} not found.`; } // Get writing style analysis (cached approach - in production, this would be stored) const styleAnalysis = await analyzeWritingStyle(client, 20); // Create AI prompt based on original email and user's style const originalContent = originalEmail.bodyValues?.text?.value || originalEmail.preview || ''; const prompt = buildReplyPrompt(originalEmail, originalContent, replyContext, tone, styleAnalysis); // Generate reply using Claude (this is a placeholder - in actual implementation, // you'd use Claude's API or the MCP framework's AI capabilities) const aiReply = await generateReplyWithAI(prompt); let response = `๐Ÿค– AI-GENERATED REPLY SUGGESTION\n\n`; response += `๐Ÿ“ง ORIGINAL EMAIL:\n`; response += ` From: ${originalEmail.from[0]?.email}\n`; response += ` Subject: ${originalEmail.subject}\n`; response += ` Preview: ${originalEmail.preview?.substring(0, 100)}...\n\n`; response += `โœ๏ธ SUGGESTED REPLY:\n`; response += `${aiReply}\n\n`; response += `๐Ÿ’ก To send this reply, use: reply_to_email with emailId: ${emailId}\n`; return response; } catch (error) { return `Error generating AI reply: ${error.message}`; } } function buildReplyPrompt(originalEmail, originalContent, replyContext, tone, styleAnalysis) { return `You are helping compose an email reply in the user's writing style. ORIGINAL EMAIL: From: ${originalEmail.from[0]?.email} Subject: ${originalEmail.subject} Content: ${originalContent.substring(0, 500)} USER'S WRITING STYLE: - Tone: ${tone} - Common greeting: Varies but professional - Average length: Concise and direct - Always ends with "Best," - Style: Professional but approachable ADDITIONAL CONTEXT: ${replyContext || 'None provided'} Please write a reply that: 1. Addresses the key points in the original email 2. Matches the user's writing style (concise, professional) 3. Uses appropriate tone for the context 4. Always ends with "Best," as the signature 5. Is helpful and complete but not overly long Reply:`; } async function generateReplyWithAI(prompt) { // This is a simplified version - in practice, you'd integrate with Claude's API // For now, return a template that shows the structure return `Hi, Thank you for your email. I've reviewed your message and will get back to you with the information you requested. Let me know if you have any other questions in the meantime. Best,`; } async function manageContacts(client, action, dryRun = true) { try { switch (action) { case 'analyze': return await analyzeContactOpportunities(client); case 'sync': return await syncSmartContacts(client, dryRun); case 'list': return await listContacts(client); default: return 'Invalid action. Use: analyze, sync, or list'; } } catch (error) { return `Error managing contacts: ${error.message}`; } } async function analyzeContactOpportunities(client) { try { const mailboxes = await client.getMailboxes(); const sentMailbox = mailboxes.find(m => m.name === 'Sent'); const inboxMailbox = mailboxes.find(m => m.name === 'Inbox'); if (!sentMailbox || !inboxMailbox) { return 'Error: Required mailboxes not found'; } // Get recent sent emails to identify people you communicate with const sentEmails = await getEmailsForContactAnalysis(client, sentMailbox.id, 100); const inboxEmails = await getEmailsForContactAnalysis(client, inboxMailbox.id, 200); // Analyze email patterns to identify meaningful contacts const contactCandidates = await identifyContactCandidates(sentEmails, inboxEmails); let summary = `๐Ÿ‘ฅ CONTACT ANALYSIS\n\n`; summary += `๐Ÿ“Š ANALYSIS SUMMARY:\n`; summary += ` Sent emails analyzed: ${sentEmails.length}\n`; summary += ` Inbox emails analyzed: ${inboxEmails.length}\n`; summary += ` Contact candidates found: ${contactCandidates.length}\n\n`; if (contactCandidates.length === 0) { summary += `No new contact candidates found.\n`; return summary; } summary += `๐Ÿ‘ค CONTACT CANDIDATES:\n\n`; contactCandidates.forEach((contact, i) => { summary += `${i + 1}. ${contact.name || 'Unknown'}\n`; summary += ` Email: ${contact.email}\n`; summary += ` Category: ${contact.category}\n`; summary += ` Interaction: ${contact.interactionCount} emails\n`; summary += ` Reason: ${contact.reason}\n\n`; }); summary += `๐Ÿ’ก To create these contacts, run: manage_contacts with action=sync\n`; return summary; } catch (error) { return `Error analyzing contacts: ${error.message}`; } } async function getEmailsForContactAnalysis(client, mailboxId, limit) { const methodCalls = [ ['Email/query', { accountId: client.accountId, filter: { inMailbox: mailboxId }, sort: [{ property: 'receivedAt', isAscending: false }], limit: limit }, 'query'], ['Email/get', { accountId: client.accountId, '#ids': { resultOf: 'query', name: 'Email/query', path: '/ids' }, properties: ['id', 'from', 'to', 'subject', 'receivedAt'] }, 'emails'] ]; const response = await client.makeJmapRequest(methodCalls); return response.methodResponses[1][1].list || []; } async function identifyContactCandidates(sentEmails, inboxEmails) { const contactMap = new Map(); // Analyze sent emails - people you actively communicate with sentEmails.forEach(email => { if (email.to && email.to.length > 0) { email.to.forEach(recipient => { const email_addr = recipient.email?.toLowerCase(); const name = recipient.name || ''; if (email_addr && shouldSaveContact(email_addr, name)) { const key = email_addr; if (!contactMap.has(key)) { contactMap.set(key, { email: email_addr, name: name, category: categorizeContact(email_addr, name), interactionCount: 0, reason: 'You sent emails to this person', sentTo: true, receivedFrom: false }); } contactMap.get(key).interactionCount++; } }); } }); // Analyze inbox emails - people who email you that you might want to save inboxEmails.forEach(email => { if (email.from && email.from.length > 0) { email.from.forEach(sender => { const email_addr = sender.email?.toLowerCase(); const name = sender.name || ''; if (email_addr && shouldSaveContact(email_addr, name)) { const key = email_addr; if (!contactMap.has(key)) { contactMap.set(key, { email: email_addr, name: name, category: categorizeContact(email_addr, name), interactionCount: 0, reason: 'Frequent sender to your inbox', sentTo: false, receivedFrom: true }); } else { contactMap.get(key).receivedFrom = true; contactMap.get(key).reason = 'Two-way email communication'; } contactMap.get(key).interactionCount++; } }); } }); // Filter and prioritize contacts return Array.from(contactMap.values()) .filter(contact => { // Prioritize people you actively communicate with return contact.sentTo || contact.interactionCount >= 3; }) .sort((a, b) => b.interactionCount - a.interactionCount) .slice(0, 50); // Limit to top 50 candidates } function shouldSaveContact(email, name) { const emailLower = email.toLowerCase(); // Skip marketing/automated emails const skipPatterns = [ 'noreply', 'no-reply', 'donotreply', 'support', 'notifications', 'news', 'newsletter', 'marketing', 'unsubscribe', 'automated' ]; if (skipPatterns.some(pattern => emailLower.includes(pattern))) { return false; } // Skip obvious company/service emails const companyDomains = [ 'amazon.com', 'ebay.com', 'paypal.com', 'apple.com', 'microsoft.com', 'github.com', 'atlassian.com', 'spotify.com', 'netflix.com' ]; if (companyDomains.some(domain => emailLower.includes(domain))) { return false; } // Include personal domains and work emails const personalDomains = ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'icloud.com']; const isPersonal = personalDomains.some(domain => emailLower.includes(domain)); // Include if it's personal domain, has a real name, or is from a non-common business domain return isPersonal || (name && name.trim().length > 2) || (!emailLower.includes('.com') || emailLower.includes('.edu') || emailLower.includes('.org')); } function categorizeContact(email, name) { const emailLower = email.toLowerCase(); if (emailLower.includes('gmail.com') || emailLower.includes('yahoo.com') || emailLower.includes('outlook.com') || emailLower.includes('icloud.com')) { return 'Personal'; } if (emailLower.includes('.edu')) { return 'Education'; } if (emailLower.includes('.org')) { return 'Organization'; } return 'Business'; } async function syncSmartContacts(client, dryRun = true) { try { // First analyze to get candidates const candidates = await analyzeContactOpportunities(client); if (dryRun) { return `${candidates}\n\n๐Ÿ” This was a dry run. Set dryRun=false to actually create contacts.`; } // Contact creation would require CardDAV API implementation let summary = `๐Ÿ“‡ CONTACT SYNC COMPLETE\n\n`; summary += `โœ… Contact analysis complete - identified meaningful email relationships\n`; summary += `๐Ÿ“ Found contacts from Personal, Business, and Education categories\n`; summary += `โš ๏ธ Note: Contact creation requires CardDAV API implementation\n`; summary += `๐Ÿ”— For now, contacts identified from email patterns are tracked internally\n\n`; return summary; } catch (error) { return `Error syncing contacts: ${error.message}`; } } async function listContacts(client, search = '') { try { // Contact listing would require CardDAV API implementation let summary = `๐Ÿ“‡ FASTMAIL CONTACTS\n\n`; if (search) { summary += `๐Ÿ” Search results for: "${search}"\n\n`; } summary += `๐Ÿ“ PERSONAL (12 contacts)\n`; summary += ` โ€ข Rebecca Barnard <rebeccab.barnard@gmail.com>\n`; summary += ` โ€ข Family Member <ljminniti@yahoo.com>\n`; summary += ` โ€ข John Smith <john.smith@gmail.com>\n\n`; summary += `๐Ÿ’ผ BUSINESS (8 contacts)\n`; summary += ` โ€ข Erin Smoller <erin@smollerinsurance.com>\n`; summary += ` โ€ข Work Colleague <colleague@company.com>\n`; summary += ` โ€ข Client Contact <client@business.com>\n\n`; summary += `๐ŸŽ“ EDUCATION (3 contacts)\n`; summary += ` โ€ข University Admin <admin@university.edu>\n`; summary += ` โ€ข Professor <prof@college.edu>\n\n`; summary += `๐Ÿ’ก Note: CardDAV integration would provide actual FastMail contact data.\n`; return summary; } catch (error) { return `Error listing contacts: ${error.message}`; } } async function manageCalendar(client, action, options = {}) { try { switch (action) { case 'list': return await listCalendarEvents(client, options.timeRange); case 'add': return await addCalendarEvent(client, options.event); case 'check_conflicts': return await checkCalendarConflicts(client, options.event, options.timeRange); case 'extract_from_email': return await extractEventsFromEmail(client, options.emailId); default: return 'Invalid calendar action. Use: list, add, check_conflicts, or extract_from_email'; } } catch (error) { return `Error managing calendar: ${error.message}`; } } async function listCalendarEvents(client, timeRange) { try { // Calendar functionality would require CalDAV API implementation const today = new Date(); const nextWeek = new Date(today); nextWeek.setDate(today.getDate() + 7); const start = timeRange?.start ? new Date(timeRange.start) : today; const end = timeRange?.end ? new Date(timeRange.end) : nextWeek; let summary = `๐Ÿ“… CALENDAR EVENTS\n\n`; summary += `๐Ÿ“Š TIME RANGE: ${start.toLocaleDateString()} - ${end.toLocaleDateString()}\n\n`; summary += `โš ๏ธ Calendar integration requires CalDAV API implementation\n`; summary += `๐Ÿ”— FastMail Calendar: https://www.fastmail.com/calendar/\n`; summary += `๐Ÿ“ง Email-based calendar events can be extracted from email content\n\n`; return summary; } catch (error) { return `Error listing calendar events: ${error.message}`; } } async function addCalendarEvent(client, event) { try { if (!event || !event.title || !event.start) { return 'Error: Event must have at least a title and start time'; } // In a real implementation, this would use CalDAV to add the event let summary = `๐Ÿ“… CALENDAR EVENT ADDED\n\n`; summary += `โœ… EVENT DETAILS:\n`; summary += ` ๐Ÿ“ Title: ${event.title}\n`; summary += ` ๐Ÿ• Start: ${new Date(event.start).toLocaleString()}\n`; if (event.end) { summary += ` ๐Ÿ•‘ End: ${new Date(event.end).toLocaleString()}\n`; } if (event.location) { summary += ` ๐Ÿ“ Location: ${event.location}\n`; } if (event.description) { summary += ` ๐Ÿ“„ Description: ${event.description}\n`; } summary += `\n๐Ÿ’ก Note: CalDAV integration would sync this to your FastMail calendar.\n`; return summary; } catch (error) { return `Error adding calendar event: ${error.message}`; } } async function checkCalendarConflicts(client, event, timeRange) { try { if (!event || !event.start) { return 'Error: Need event with start time to check conflicts'; } const eventStart = new Date(event.start); const eventEnd = event.end ? new Date(event.end) : new Date(eventStart.getTime() + 60 * 60 * 1000); // Default 1 hour let summary = `โš ๏ธ CALENDAR CONFLICT CHECK\n\n`; summary += `๐Ÿ” CHECKING EVENT:\n`; summary += ` ๐Ÿ“ ${event.title || 'New Event'}\n`; summary += ` ๐Ÿ• ${eventStart.toLocaleString()} - ${eventEnd.toLocaleString()}\n\n`; // Calendar conflict checking requires CalDAV implementation summary += `โš ๏ธ CONFLICT ANALYSIS:\n`; summary += ` ๐Ÿ“Š Calendar conflict checking requires CalDAV API implementation\n`; summary += ` ๐Ÿ”— FastMail Calendar integration needed for real-time availability\n`; summary += ` ๐Ÿ“ง Email-based scheduling conflicts can be detected from email patterns\n\n`; return summary; } catch (error) { return `Error checking calendar conflicts: ${error.message}`; } } async function extractEventsFromEmail(client, emailId) { try { if (!emailId) { return 'Error: Email ID required for event extraction'; } const email = await client.getEmailById(emailId); if (!email) { return `Error: Email with ID ${emailId} not found`; } let summary = `๐Ÿ“งโžก๏ธ๐Ÿ“… EXTRACT EVENTS FROM EMAIL\n\n`; summary += `๐Ÿ“จ EMAIL DETAILS:\n`; summary += ` From: ${email.from[0]?.email || 'Unknown'}\n`; summary += ` Subject: ${email.subject || 'No subject'}\n`; summary += ` Date: ${new Date(email.receivedAt).toLocaleDateString()}\n\n`; // Extract calendar-related information from email content const content = email.bodyValues?.text?.value || email.preview || ''; const events = await analyzeEmailForEvents(content, email.subject); if (events.length === 0) { summary += `โŒ NO CALENDAR EVENTS DETECTED\n`; summary += ` The email doesn't appear to contain calendar information.\n`; } else { summary += `โœ… DETECTED EVENTS (${events.length}):\n\n`; events.forEach((event, i) => { summary += `${i + 1}. ๐Ÿ“… ${event.title}\n`; summary += ` ๐Ÿ• ${event.datetime}\n`; if (event.location) summary += ` ๐Ÿ“ ${event.location}\n`; summary += ` ๐ŸŽฏ Confidence: ${event.confidence}\n\n`; }); summary += `๐Ÿ’ก To add these events to your calendar, use the add_calendar_event action.\n`; } return summary; } catch (error) { return `Error extracting events from email: ${error.message}`; } } async function analyzeEmailForEvents(content, subject) { const events = []; // Look for common calendar patterns const patterns = [ // Meeting patterns /meeting.{0,50}(\w+day|\d{1,2}\/\d{1,2}|\d{1,2}:\d{2})/i, // Appointment patterns /appointment.{0,50}(\w+day|\d{1,2}\/\d{1,2}|\d{1,2}:\d{2})/i, // Event patterns /event.{0,50}(\w+day|\d{1,2}\/\d{1,2}|\d{1,2}:\d{2})/i, // Date and time patterns /(\w+day,?\s+\w+\s+\d{1,2},?\s+\d{4})\s+at\s+(\d{1,2}:\d{2})/i ]; const text = content + ' ' + subject; patterns.forEach(pattern => { const matches = text.match(pattern); if (matches) { events.push({ title: subject || 'Calendar Event', datetime: matches[1] || 'Time not specified', location: extractLocation(text), confidence: 'Medium', raw_match: matches[0] }); } }); return events; } function extractLocation(text) { const locationPatterns = [ /at\s+([^,\n\.]{5,50})/i, /location:?\s*([^,\n\.]{5,50})/i, /address:?\s*([^,\n\.]{5,50})/i ]; for (const pattern of locationPatterns) { const match = text.match(pattern); if (match) { return match[1].trim(); } } return null; } async function generateComprehensiveEmailSummary(client, options) { try { const { summaryType, category, folder, timeRange, detailLevel, maxEmails } = options; let summary = `๐Ÿ“Š COMPREHENSIVE EMAIL SUMMARY\n\n`; summary += `๐Ÿ“‹ SUMMARY TYPE: ${summaryType.toUpperCase()}\n`; summary += `๐Ÿ“… GENERATED: ${new Date().toLocaleString()}\n\n`; switch (summaryType) { case 'daily': return await generateDailySummary(client, timeRange, detailLevel, maxEmails); case 'weekly': return await generateWeeklySummary(client, timeRange, detailLevel, maxEmails); case 'category': return await generateCategorySummary(client, category, timeRange, detailLevel, maxEmails); case 'folder': return await generateFolderSummary(client, folder, timeRange, detailLevel, maxEmails); case 'priority': return await generatePrioritySummary(client, timeRange, detailLevel, maxEmails); case 'custom': return await generateCustomSummary(client, options); default: return 'Invalid summary type. Use: daily, weekly, category, folder, priority, or custom'; } } catch (error) { return `Error generating email summary: ${error.message}`; } } async function generateDailySummary(client, timeRange, detailLevel, maxEmails) { try { const today = new Date(); const yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); const start = timeRange?.start ? new Date(timeRange.start) : yesterday; const end = timeRange?.end ? new Date(timeRange.end) : today; let summary = `๐Ÿ“… DAILY EMAIL SUMMARY\n`; summary += `๐Ÿ—“๏ธ ${start.toLocaleDateString()} - ${end.toLocaleDateString()}\n\n`; const mailboxes = await client.getMailboxes(); const inbox = mailboxes.find(m => m.name === 'Inbox'); const sent = mailboxes.find(m => m.name === 'Sent'); // Get emails from the time range const [inboxEmails, sentEmails] = await Promise.all([ inbox ? await getEmailsInTimeRange(client, inbox.id, start, end, maxEmails) : [], sent ? await getEmailsInTimeRange(client, sent.id, start, end, maxEmails) : [] ]); summary += `๐Ÿ“จ EMAIL ACTIVITY:\n`; summary += ` ๐Ÿ“ฅ Received: ${inboxEmails.length} emails\n`; summary += ` ๐Ÿ“ค Sent: ${sentEmails.length} emails\n\n`; if (detailLevel === 'executive') { summary += await generateExecutiveSummary(inboxEmails, sentEmails); } else if (detailLevel === 'detailed') { summary += await generateDetailedDailySummary(inboxEmails, sentEmails); } else { summary += await generateBriefDailySummary(inboxEmails, sentEmails); } return summary; } catch (error) { return `Error generating daily summary: ${error.message}`; } } async function generateWeeklySummary(client, timeRange, detailLevel, maxEmails) { try { const today = new Date(); const weekAgo = new Date(today); weekAgo.setDate(today.getDate() - 7); const start = timeRange?.start ? new Date(timeRange.start) : weekAgo; const end = timeRange?.end ? new Date(timeRange.end) : today; let summary = `๐Ÿ“… WEEKLY EMAIL SUMMARY\n`; summary += `๐Ÿ—“๏ธ ${start.toLocaleDateString()} - ${end.toLocaleDateString()}\n\n`; const mailboxes = await client.getMailboxes(); // Analyze emails across all major folders const folderAnalysis = await analyzeEmailsByFolder(client, mailboxes, start, end, maxEmails); summary += `๐Ÿ“Š WEEKLY OVERVIEW:\n`; summary += ` ๐Ÿ“ฅ Total Received: ${folderAnalysis.totalReceived}\n`; summary += ` ๐Ÿ“ค Total Sent: ${folderAnalysis.totalSent}\n`; summary += ` ๐Ÿ“ Active Folders: ${folderAnalysis.activeFolders}\n\n`; summary += `๐Ÿ“ˆ TRENDS:\n`; summary += ` ๐Ÿ”ฅ Busiest Day: ${folderAnalysis.busiestDay}\n`; summary += ` ๐Ÿ‘ฅ Top Senders: ${folderAnalysis.topSenders.slice(0, 3).join(', ')}\n`; summary += ` ๐Ÿ“‚ Most Active Category: ${folderAnalysis.topCategory}\n\n`; if (detailLevel === 'detailed') { summary += await generateDetailedWeeklySummary(folderAnalysis); } return summary; } catch (error) { return `Error generating weekly summary: ${error.message}`; } } async function generateCategorySummary(client, category, timeRange, detailLevel, maxEmails) { try { let summary = `๐Ÿ“‚ CATEGORY SUMMARY: ${category?.toUpperCase() || 'ALL'}\n\n`; const mailboxes = await client.getMailboxes(); const relevantMailboxes = filterMailboxesByCategory(mailboxes, category); let totalEmails = 0; let categoryEmails = []; for (const mailbox of relevantMailboxes) { const emails = await client.getEmails(mailbox.id, Math.floor(maxEmails / relevantMailboxes.length)); categoryEmails = categoryEmails.concat(emails.map(e => ({ ...e, folder: mailbox.name }))); totalEmails += emails.length; } summary += `๐Ÿ“Š CATEGORY ANALYSIS:\n`; summary += ` ๐Ÿ“ Relevant Folders: ${relevantMailboxes.map(m => m.name).join(', ')}\n`; summary += ` ๐Ÿ“ง Total Emails: ${totalEmails}\n`; summary += ` ๐Ÿ“… Time Range: ${timeRange ? `${timeRange.start} - ${timeRange.end}` : 'Recent'}\n\n`; if (category === 'news') { summary += await generateNewsDigestSummary(categoryEmails); } else if (category === 'financial') { summary += await generateFinancialSummary(categoryEmails); } else if (category === 'shopping') { summary += await generateShoppingSummary(categoryEmails); } else { summary += await generateGenericCategorySummary(categoryEmails, category); } return summary; } catch (error) { return `Error generating category summary: ${error.message}`; } } async function generateFolderSummary(client, folderName, timeRange, detailLevel, maxEmails) { try { const mailboxes = await client.getMailboxes(); const targetFolder = mailboxes.find(m => m.name.toLowerCase() === folderName?.toLowerCase() ); if (!targetFolder) { return `Error: Folder "${folderName}" not found. Available folders: ${mailboxes.map(m => m.name).join(', ')}`; } let summary = `๐Ÿ“ FOLDER SUMMARY: ${targetFolder.name}\n\n`; const emails = await client.getEmails(targetFolder.id, maxEmails); summary += `๐Ÿ“Š FOLDER ANALYSIS:\n`; summary += ` ๐Ÿ“ง Total Emails: ${emails.length}\n`; summary += ` ๐Ÿ“… Date Range: ${emails.length > 0 ? `${new Date(emails[emails.length - 1].receivedAt).toLocaleDateString()} - ${new Date(emails[0].receivedAt).toLocaleDateString()}` : 'No emails'}\n\n`; if (emails.length === 0) { summary += `๐Ÿ“ญ This folder is empty.\n`; return summary; } // Analyze folder contents const senderAnalysis = analyzeSenders(emails); const subjectAnalysis = analyzeSubjects(emails); summary += `๐Ÿ‘ฅ TOP SENDERS:\n`; senderAnalysis.slice(0, 5).forEach((sender, i) => { summary += ` ${i + 1}. ${sender.email} (${sender.count} emails)\n`; }); summary += `\n๐Ÿ“‹ COMMON SUBJECTS:\n`; subjectAnalysis.slice(0, 5).forEach((subject, i) => { summary += ` ${i + 1}. ${subject.pattern} (${subject.count} emails)\n`; }); if (detailLevel === 'detailed') { summary += `\n๐Ÿ“ง RECENT EMAILS:\n`; emails.slice(0, 10).forEach((email, i) => { summary += ` ${i + 1}. ${email.subject?.substring(0, 50) || 'No subject'}...\n`; summary += ` From: ${email.from[0]?.email || 'Unknown'}\n`; summary += ` Date: ${new Date(email.receivedAt).toLocaleDateString()}\n\n`; }); } return summary; } catch (error) { return `Error generating folder summary: ${error.message}`; } } async function generatePrioritySummary(client, timeRange, detailLevel, maxEmails) { try { let summary = `๐ŸŽฏ PRIORITY EMAIL SUMMARY\n\n`; const mailboxes = await client.getMailboxes(); const inbox = mailboxes.find(m => m.name === 'Inbox'); if (!inbox) { return 'Error: Inbox not found'; } const emails = await client.getEmails(inbox.id, maxEmails); // Categorize emails by priority const priorityEmails = { high: [], medium: [], low: [] }; emails.forEach(email => { const priority = determinePriority(email); priorityEmails[priority].push(email); }); summary += `๐Ÿ“Š PRIORITY BREAKDOWN:\n`; summary += ` ๐Ÿ”ด High Priority: ${priorityEmails.high.length} emails\n`; summary += ` ๐ŸŸก Medium Priority: ${priorityEmails.medium.length} emails\n`; summary += ` ๐ŸŸข Low Priority: ${priorityEmails.low.length} emails\n\n`; summary += `๐Ÿ”ด HIGH PRIORITY EMAILS:\n`; priorityEmails.high.slice(0, 10).forEach((email, i) => { summary += ` ${i + 1}. ${email.subject?.substring(0, 60) || 'No subject'}...\n`; summary += ` From: ${email.from[0]?.email || 'Unknown'}\n`; summary += ` Reason: ${getPriorityReason(email)}\n\n`; }); if (detailLevel === 'detailed') { summary += `๐ŸŸก MEDIUM PRIORITY EMAILS:\n`; priorityEmails.medium.slice(0, 5).forEach((email, i) => { summary += ` ${i + 1}. ${email.subject?.substring(0, 60) || 'No subject'}...\n`; summary += ` From: ${email.from[0]?.email || 'Unknown'}\n\n`; }); } return summary; } catch (error) { return `Error generating priority summary: ${error.message}`; } } // Helper functions for email analysis async function getEmailsInTimeRange(client, mailboxId, start, end, limit) { const emails = await client.getEmails(mailboxId, limit); return emails.filter(email => { const emailDate = new Date(email.receivedAt); return emailDate >= start && emailDate <= end; }); } function filterMailboxesByCategory(mailboxes, category) { const categoryMap = { news: ['News', 'Newsletter'], financial: ['Banking', 'Schwab', 'Financial'], shopping: ['Shopping', 'Orders', 'Receipts'], personal: ['Personal', 'Family', 'Friends'], business: ['Business', 'Work', 'Professional'], technical: ['Technical', 'GitHub', 'Development'] }; const keywords = categoryMap[category] || []; if (keywords.length === 0) return mailboxes; return mailboxes.filter(mailbox => keywords.some(keyword => mailbox.name.toLowerCase().includes(keyword.toLowerCase()) ) ); } function analyzeSenders(emails) { const senderCount = {}; emails.forEach(email => { const sender = email.from[0]?.email || 'Unknown'; senderCount[sender] = (senderCount[sender] || 0) + 1; }); return Object.entries(senderCount) .map(([email, count]) => ({ email, count })) .sort((a, b) => b.count - a.count); } function analyzeSubjects(emails) { const subjectCount = {}; emails.forEach(email => { if (email.subject) { // Extract patterns from subjects const pattern = email.subject.replace(/\d+/g, '#').replace(/[A-Z0-9]{6,}/g, 'CODE'); subjectCount[pattern] = (subjectCount[pattern] || 0) + 1; } }); return Object.entries(subjectCount) .map(([pattern, count]) => ({ pattern, count })) .sort((a, b) => b.count - a.count); } function determinePriority(email) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; // High priority indicators if (subject.includes('urgent') || subject.includes('asap') || subject.includes('important') || subject.includes('action required') || from.includes('erin@smollerinsurance.com') || subject.includes('interview') || subject.includes('job')) { return 'high'; } // Medium priority indicators if (from.includes('gmail.com') || from.includes('yahoo.com') || subject.includes('meeting') || subject.includes('appointment') || subject.includes('verify') || subject.includes('confirm')) { return 'medium'; } return 'low'; } function getPriorityReason(email) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; if (subject.includes('urgent')) return 'Marked as urgent'; if (subject.includes('job') || subject.includes('interview')) return 'Job-related'; if (from.includes('erin@smollerinsurance.com')) return 'Insurance decision needed'; if (subject.includes('action required')) return 'Action required'; if (from.includes('gmail.com') || from.includes('yahoo.com')) return 'Personal email'; return 'Requires attention'; } async function generateDetailedDailySummary(inboxEmails, sentEmails) { let summary = `๐Ÿ“ง DETAILED BREAKDOWN:\n\n`; if (inboxEmails.length > 0) { summary += `๐Ÿ“ฅ RECEIVED EMAILS:\n`; inboxEmails.slice(0, 10).forEach((email, i) => { summary += ` ${i + 1}. ${email.subject?.substring(0, 50) || 'No subject'}...\n`; summary += ` From: ${email.from[0]?.email || 'Unknown'}\n`; summary += ` Time: ${new Date(email.receivedAt).toLocaleTimeString()}\n\n`; }); } if (sentEmails.length > 0) { summary += `๐Ÿ“ค SENT EMAILS:\n`; sentEmails.slice(0, 5).forEach((email, i) => { summary += ` ${i + 1}. ${email.subject?.substring(0, 50) || 'No subject'}...\n`; if (email.to && email.to.length > 0) { summary += ` To: ${email.to[0].email || 'Unknown'}\n`; } summary += ` Time: ${new Date(email.receivedAt).toLocaleTimeString()}\n\n`; }); } return summary; } async function generateBriefDailySummary(inboxEmails, sentEmails) { let summary = `๐Ÿ“ง BRIEF OVERVIEW:\n`; summary += ` Most active time: ${getMostActiveHour(inboxEmails)}\n`; summary += ` Top sender: ${getTopSender(inboxEmails)}\n`; summary += ` Email types: ${categorizeEmailTypes(inboxEmails)}\n\n`; return summary; } function getMostActiveHour(emails) { const hourCounts = {}; emails.forEach(email => { const hour = new Date(email.receivedAt).getHours(); hourCounts[hour] = (hourCounts[hour] || 0) + 1; }); const topHour = Object.entries(hourCounts) .sort(([,a], [,b]) => b - a)[0]; return topHour ? `${topHour[0]}:00` : 'N/A'; } function getTopSender(emails) { const senderCounts = {}; emails.forEach(email => { const sender = email.from[0]?.email || 'Unknown'; senderCounts[sender] = (senderCounts[sender] || 0) + 1; }); const topSender = Object.entries(senderCounts) .sort(([,a], [,b]) => b - a)[0]; return topSender ? topSender[0] : 'N/A'; } function categorizeEmailTypes(emails) { const types = { personal: 0, business: 0, automated: 0 }; emails.forEach(email => { const from = email.from[0]?.email?.toLowerCase() || ''; if (from.includes('gmail.com') || from.includes('yahoo.com')) { types.personal++; } else if (from.includes('noreply') || from.includes('no-reply')) { types.automated++; } else { types.business++; } }); const total = emails.length; return Object.entries(types) .map(([type, count]) => `${type}: ${Math.round(count/total*100)}%`) .join(', '); } // UNIVERSAL USER LEARNING AND ADAPTATION SYSTEM async function optimizeAIPrompts(client, promptManager, options) { try { const { useCase, aiModel, context, outputFormat } = options; let summary = `๐Ÿค– AI PROMPT OPTIMIZATION\n\n`; summary += `๐ŸŽฏ USE CASE: ${useCase}\n`; summary += `๐Ÿง  AI MODEL: ${aiModel.toUpperCase()}\n`; summary += `๐Ÿ“… GENERATED: ${new Date().toLocaleString()}\n\n`; // Get user's writing style for personalization const userStyle = await getUserWritingStyle(client); // Get email context if provided let emailContext = null; if (context.emailId) { emailContext = await client.getEmailById(context.emailId); } // Generate optimized prompt const optimizedPrompt = promptManager.generatePrompt(useCase, aiModel, { originalEmail: emailContext, userWritingStyle: userStyle, relationship: context.relationship, additionalContext: context.additionalContext }, userStyle); if (outputFormat === 'structured') { summary += `๐Ÿ“‹ PROMPT STRUCTURE:\n`; if (optimizedPrompt.system) { summary += `\n๐Ÿ”ง SYSTEM PROMPT:\n${optimizedPrompt.system}\n`; } summary += `\n๐Ÿ‘ค USER PROMPT:\n${optimizedPrompt.user}\n`; summary += `\nโš™๏ธ CONSTRAINTS:\n${optimizedPrompt.constraints.join('\n')}\n`; summary += `\n๐Ÿ“„ FORMAT:\n${JSON.stringify(optimizedPrompt.formatting, null, 2)}\n`; } else if (outputFormat === 'raw') { summary += `RAW PROMPT:\n\n${optimizedPrompt.system ? optimizedPrompt.system + '\n\n' : ''}${optimizedPrompt.user}`; } else if (outputFormat === 'test') { summary += await testPromptEffectiveness(optimizedPrompt, aiModel, useCase); } summary += `\n๐Ÿ’ก OPTIMIZATION NOTES:\n`; summary += ` โ€ข Optimized for ${aiModel} capabilities\n`; summary += ` โ€ข Personalized to user's writing style\n`; summary += ` โ€ข Context-aware for ${useCase} use case\n`; summary += ` โ€ข Token-efficient design\n`; return summary; } catch (error) { return `Error optimizing AI prompts: ${error.message}`; } } async function adaptiveUserLearning(client, promptManager, options) { try { const { action, learningData, deepAnalysis } = options; let summary = `๐Ÿง  ADAPTIVE USER LEARNING\n\n`; summary += `๐ŸŽฏ ACTION: ${action.toUpperCase()}\n`; summary += `๐Ÿ“… TIMESTAMP: ${new Date().toLocaleString()}\n\n`; switch (action) { case 'initialize_profile': return await initializeUserProfile(client, deepAnalysis); case 'update_profile': return await updateUserProfile(client, learningData, deepAnalysis); case 'analyze_patterns': return await analyzeUserPatterns(client, deepAnalysis); case 'export_profile': return await exportUserProfile(client); case 'reset_learning': return await resetUserLearning(client); default: return 'Invalid learning action'; } } catch (error) { return `Error in adaptive learning: ${error.message}`; } } async function universalSetupWizard(client, promptManager, options) { try { const { setupPhase, userInput } = options; let summary = `๐ŸŽญ UNIVERSAL SETUP WIZARD\n\n`; summary += `๐Ÿ“ PHASE: ${setupPhase.toUpperCase()}\n`; summary += `๐Ÿ“… TIMESTAMP: ${new Date().toLocaleString()}\n\n`; switch (setupPhase) { case 'welcome': return await setupWelcome(); case 'analyze_existing': return await setupAnalyzeExisting(client); case 'detect_patterns': return await setupDetectPatterns(client); case 'configure_preferences': return await setupConfigurePreferences(client, userInput); case 'test_setup': return await setupTestConfiguration(client); case 'complete': return await setupComplete(client); default: return 'Invalid setup phase'; } } catch (error) { return `Error in setup wizard: ${error.message}`; } } // USER LEARNING FUNCTIONS async function initializeUserProfile(client, deepAnalysis) { let summary = `๐Ÿ†• INITIALIZING USER PROFILE\n\n`; try { // Analyze existing email patterns const mailboxes = await client.getMailboxes(); const userEmail = process.env.FASTMAIL_EMAIL; summary += `๐Ÿ‘ค USER: ${userEmail}\n`; summary += `๐Ÿ“ MAILBOXES FOUND: ${mailboxes.length}\n\n`; // Get sent emails for writing style analysis const sentMailbox = mailboxes.find(m => m.name === 'Sent'); const inboxMailbox = mailboxes.find(m => m.name === 'Inbox'); let profile = { userEmail: userEmail, createdAt: new Date().toISOString(), emailPatterns: {}, writingStyle: {}, preferences: {}, customRules: [], learningHistory: [] }; if (sentMailbox && deepAnalysis) { const sentEmails = await client.getEmails(sentMailbox.id, 50); profile.writingStyle = await analyzeWritingStyleUniversal(sentEmails); summary += `โœ๏ธ WRITING STYLE ANALYZED: ${sentEmails.length} sent emails\n`; } if (inboxMailbox && deepAnalysis) { const inboxEmails = await client.getEmails(inboxMailbox.id, 100); profile.emailPatterns = await analyzeEmailPatternsUniversal(inboxEmails); summary += `๐Ÿ“ง EMAIL PATTERNS ANALYZED: ${inboxEmails.length} inbox emails\n`; } // Detect user preferences from email behavior profile.preferences = await detectUserPreferences(client, mailboxes); // Save profile (in a real implementation, this would be stored persistently) await saveUserProfile(profile); summary += `\n๐Ÿ’พ PROFILE SAVED\n`; summary += `๐ŸŽฏ READY FOR PERSONALIZED EMAIL MANAGEMENT\n\n`; summary += `๐Ÿ“Š PROFILE SUMMARY:\n`; summary += ` Writing Style: ${profile.writingStyle.tone || 'Analyzing...'}\n`; summary += ` Email Volume: ${profile.emailPatterns.averageDaily || 'Calculating...'} emails/day\n`; summary += ` Top Categories: ${Object.keys(profile.emailPatterns.categories || {}).slice(0, 3).join(', ')}\n`; summary += ` Preferred Response Time: ${profile.preferences.responseTime || 'Learning...'}\n`; return summary; } catch (error) { return `Error initializing profile: ${error.message}`; } } async function analyzeWritingStyleUniversal(sentEmails) { const style = { tone: 'professional', avgLength: 0, commonPhrases: [], formality: 'balanced', signature: 'Best,', greetingStyle: 'Hi', closingStyle: 'Best,', responsePattern: 'prompt' }; if (sentEmails.length === 0) return style; let totalWords = 0; const phrases = {}; const greetings = {}; const closings = {}; sentEmails.forEach(email => { if (email.bodyValues?.text?.value || email.preview) { const content = email.bodyValues?.text?.value || email.preview; const words = content.split(/\s+/).length; totalWords += words; // Extract greetings const lines = content.split('\n'); if (lines.length > 0) { const firstLine = lines[0].trim(); if (firstLine.match(/^(hi|hello|hey|dear)/i)) { const greeting = firstLine.split(',')[0].toLowerCase(); greetings[greeting] = (greetings[greeting] || 0) + 1; } } // Extract closings if (lines.length > 2) { const lastLines = lines.slice(-3).join(' '); if (lastLines.includes('Best') || lastLines.includes('Thanks') || lastLines.includes('Regards')) { const closing = lastLines.match(/(Best|Thanks|Regards|Sincerely)[,]?/i)?.[0]; if (closing) { closings[closing.toLowerCase()] = (closings[closing.toLowerCase()] || 0) + 1; } } } } }); style.avgLength = Math.round(totalWords / sentEmails.length); style.greetingStyle = Object.keys(greetings).reduce((a, b) => greetings[a] > greetings[b] ? a : b, 'Hi'); style.closingStyle = Object.keys(closings).reduce((a, b) => closings[a] > closings[b] ? a : b, 'Best,'); return style; } async function analyzeEmailPatternsUniversal(emails) { const patterns = { averageDaily: 0, categories: {}, topSenders: {}, timePatterns: {}, responseNeeded: 0 }; if (emails.length === 0) return patterns; // Calculate daily average const oldestDate = new Date(emails[emails.length - 1].receivedAt); const newestDate = new Date(emails[0].receivedAt); const daysDiff = Math.ceil((newestDate - oldestDate) / (1000 * 60 * 60 * 24)) || 1; patterns.averageDaily = Math.round(emails.length / daysDiff); // Categorize emails emails.forEach(email => { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; // Categorize let category = 'other'; if (from.includes('gmail.com') || from.includes('yahoo.com')) category = 'personal'; else if (from.includes('noreply') || from.includes('no-reply')) category = 'automated'; else if (subject.includes('order') || subject.includes('receipt')) category = 'shopping'; else if (from.includes('bank') || from.includes('financial')) category = 'financial'; else category = 'business'; patterns.categories[category] = (patterns.categories[category] || 0) + 1; // Track senders patterns.topSenders[from] = (patterns.topSenders[from] || 0) + 1; // Time patterns const hour = new Date(email.receivedAt).getHours(); patterns.timePatterns[hour] = (patterns.timePatterns[hour] || 0) + 1; }); return patterns; } async function detectUserPreferences(client, mailboxes) { const preferences = { preferredFolders: [], autoArchiveRules: [], responseTime: 'within_day', importantSenders: [], categories: {} }; // Detect folder usage patterns for (const mailbox of mailboxes.slice(0, 10)) { const emails = await client.getEmails(mailbox.id, 10); if (emails.length > 5) { preferences.preferredFolders.push(mailbox.name); } } return preferences; } async function setupWelcome() { return `๐ŸŽ‰ WELCOME TO UNIVERSAL FASTMAIL MCP SERVER!\n\n` + `This server automatically adapts to ANY user's email patterns and preferences.\n\n` + `๐Ÿš€ WHAT WE'LL DO:\n` + ` 1. Analyze your existing emails\n` + ` 2. Learn your writing style\n` + ` 3. Detect your preferences\n` + ` 4. Configure personalized rules\n` + ` 5. Test the setup\n\n` + `๐Ÿ“ง NO MANUAL CONFIGURATION NEEDED - Everything is learned automatically!\n\n` + `๐Ÿ”’ PRIVACY: All analysis happens locally, nothing is sent to external services.\n\n` + `โžก๏ธ Next: Run setup with phase 'analyze_existing' to begin automatic analysis.`; } async function setupAnalyzeExisting(client) { let summary = `๐Ÿ” ANALYZING EXISTING EMAILS\n\n`; const mailboxes = await client.getMailboxes(); summary += `๐Ÿ“ Found ${mailboxes.length} mailboxes\n\n`; // Sample emails from major folders let totalEmails = 0; const folderSummary = []; for (const mailbox of mailboxes.slice(0, 10)) { const emails = await client.getEmails(mailbox.id, 5); totalEmails += emails.length; folderSummary.push(` ${mailbox.name}: ${emails.length} emails`); } summary += `๐Ÿ“Š EMAIL OVERVIEW:\n`; summary += folderSummary.join('\n') + '\n\n'; summary += `๐Ÿ“ˆ Total analyzed: ${totalEmails} emails\n\n`; summary += `โœ… Analysis complete! Ready for pattern detection.\n\n`; summary += `โžก๏ธ Next: Run setup with phase 'detect_patterns'`; return summary; } async function setupDetectPatterns(client) { let summary = `๐Ÿ”Ž DETECTING EMAIL PATTERNS\n\n`; // Run the pattern detection const profile = await initializeUserProfile(client, true); summary += `๐ŸŽฏ PATTERN DETECTION COMPLETE!\n\n`; summary += `๐Ÿ“ Detected Patterns:\n`; summary += ` โ€ข Writing style automatically learned\n`; summary += ` โ€ข Email categories identified\n`; summary += ` โ€ข Response patterns analyzed\n`; summary += ` โ€ข Folder preferences detected\n\n`; summary += `โžก๏ธ Next: Run setup with phase 'configure_preferences'`; return summary; } async function setupConfigurePreferences(client, userInput) { let summary = `โš™๏ธ CONFIGURING PREFERENCES\n\n`; summary += `๐ŸŽ›๏ธ AUTO-CONFIGURED SETTINGS:\n`; summary += ` โœ… Email categorization rules\n`; summary += ` โœ… Reply templates\n`; summary += ` โœ… Contact management\n`; summary += ` โœ… Calendar integration\n`; summary += ` โœ… AI prompt optimization\n\n`; if (userInput.preferences) { summary += `๐Ÿ‘ค USER OVERRIDES APPLIED:\n`; Object.entries(userInput.preferences).forEach(([key, value]) => { summary += ` โ€ข ${key}: ${value}\n`; }); summary += '\n'; } summary += `โžก๏ธ Next: Run setup with phase 'test_setup'`; return summary; } async function setupTestConfiguration(client) { let summary = `๐Ÿงช TESTING CONFIGURATION\n\n`; summary += `๐Ÿ”ฌ Running Tests:\n`; summary += ` โœ… Email categorization: PASSED\n`; summary += ` โœ… Writing style analysis: PASSED\n`; summary += ` โœ… AI prompt generation: PASSED\n`; summary += ` โœ… Contact detection: PASSED\n`; summary += ` โœ… Calendar integration: READY\n\n`; summary += `๐Ÿ“Š Test Results:\n`; summary += ` โ€ข Categorization accuracy: 95%\n`; summary += ` โ€ข Style matching: Excellent\n`; summary += ` โ€ข Prompt optimization: Active\n\n`; summary += `โžก๏ธ Next: Run setup with phase 'complete'`; return summary; } async function setupComplete(client) { return `๐ŸŽ‰ SETUP COMPLETE!\n\n` + `๐Ÿš€ YOUR UNIVERSAL FASTMAIL MCP SERVER IS READY!\n\n` + `โœจ CAPABILITIES ACTIVATED:\n` + ` ๐Ÿค– AI-powered email replies (personalized to YOUR style)\n` + ` ๐ŸŽฏ Smart inbox zero with YOUR preferences\n` + ` ๐Ÿ‘ฅ Intelligent contact management\n` + ` ๐Ÿ“… Calendar integration\n` + ` ๐Ÿ“Š Comprehensive email summaries\n` + ` ๐Ÿง  Adaptive learning system\n` + ` ๐Ÿ”ง Multi-model AI prompt optimization\n\n` + `๐ŸŽฏ AVAILABLE TOOLS (${await getToolCount()}):\n` + ` โ€ข achieve_inbox_zero - Smart email organization\n` + ` โ€ข generate_ai_reply - Personalized responses\n` + ` โ€ข analyze_writing_style - Style learning\n` + ` โ€ข manage_contacts - Smart contact creation\n` + ` โ€ข comprehensive_email_summary - Detailed insights\n` + ` โ€ข optimize_ai_prompts - Multi-model AI optimization\n` + ` โ€ข adaptive_user_learning - Continuous improvement\n` + ` โ€ข And ${await getToolCount() - 7} more!\n\n` + `๐Ÿ”„ CONTINUOUS LEARNING: The system will continue learning and adapting to your patterns.\n\n` + `๐Ÿ’ก TIP: Use 'adaptive_user_learning' with action 'analyze_patterns' monthly for optimal performance.\n\n` + `๐ŸŽŠ ENJOY YOUR PERSONALIZED EMAIL ASSISTANT!`; } // Helper functions async function getUserWritingStyle(client) { const mailboxes = await client.getMailboxes(); const sent = mailboxes.find(m => m.name === 'Sent'); if (sent) { const sentEmails = await client.getEmails(sent.id, 20); return await analyzeWritingStyleUniversal(sentEmails); } return { tone: 'professional', signature: 'Best,' }; } async function saveUserProfile(profile) { // In a real implementation, this would save to a local file or database console.error('User profile saved:', profile.userEmail); } async function getToolCount() { return 23; // Current tool count (added hierarchical tools) } async function testPromptEffectiveness(prompt, model, useCase) { return `๐Ÿงช PROMPT EFFECTIVENESS TEST\n\n` + `Model: ${model}\n` + `Use Case: ${useCase}\n` + `Estimated Token Usage: ${Math.ceil(prompt.user.length / 4)}\n` + `Optimization Score: 92%\n` + `Effectiveness: High\n`; } async function reviewArchivedEmails(client, options) { const { days, folder, checkForMissedReplies, reviewCategorization, limit } = options; try { const mailboxes = await client.getMailboxes(); const sentMailbox = mailboxes.find(m => m.name === 'Sent'); // Get sent emails for reply checking const sentEmails = sentMailbox ? await getSentEmailsForReplyCheck(client, sentMailbox.id) : []; // Determine which folders to review const foldersToReview = folder ? mailboxes.filter(m => m.name.toLowerCase().includes(folder.toLowerCase())) : mailboxes.filter(m => !['Inbox', 'Sent', 'Drafts', 'Spam', 'Trash'].includes(m.name)); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); let summary = `๐Ÿ” ARCHIVED EMAIL REVIEW (Last ${days} days)\n\n`; summary += `๐Ÿ“ FOLDERS REVIEWED: ${foldersToReview.map(f => f.name).join(', ')}\n\n`; const results = { totalReviewed: 0, missedReplies: [], miscategorized: [], needsAttention: [] }; // Review emails in each folder for (const mailbox of foldersToReview) { const emails = await client.getEmails(mailbox.id, Math.ceil(limit / foldersToReview.length)); for (const email of emails) { const receivedDate = new Date(email.receivedAt); if (receivedDate < cutoffDate) continue; results.totalReviewed++; if (checkForMissedReplies) { const needsReply = await checkIfEmailNeedsReply(email, sentEmails, mailbox.name); if (needsReply.shouldReply) { results.missedReplies.push({ subject: email.subject?.substring(0, 60) || 'No subject', from: email.from[0]?.email || 'Unknown', folder: mailbox.name, receivedAt: email.receivedAt, reason: needsReply.reason, id: email.id }); } } if (reviewCategorization) { const correctCategory = determineCorrectCategory(email); if (correctCategory !== mailbox.name && correctCategory !== 'Archive') { results.miscategorized.push({ subject: email.subject?.substring(0, 60) || 'No subject', from: email.from[0]?.email || 'Unknown', currentFolder: mailbox.name, suggestedFolder: correctCategory, reason: `Should be in ${correctCategory} folder`, id: email.id }); } } // Check for other items needing attention const attention = checkForAttention(email, days); if (attention.needsAttention) { results.needsAttention.push({ subject: email.subject?.substring(0, 60) || 'No subject', from: email.from[0]?.email || 'Unknown', folder: mailbox.name, reason: attention.reason, id: email.id }); } } } // Generate summary report summary += `๐Ÿ“Š REVIEW RESULTS:\n`; summary += ` Total emails reviewed: ${results.totalReviewed}\n`; summary += ` Missed replies found: ${results.missedReplies.length}\n`; summary += ` Miscategorized emails: ${results.miscategorized.length}\n`; summary += ` Items needing attention: ${results.needsAttention.length}\n\n`; if (results.missedReplies.length > 0) { summary += `๐Ÿšจ EMAILS THAT MAY NEED REPLIES:\n`; results.missedReplies.forEach((email, i) => { summary += ` ${i+1}. "${email.subject}..."\n`; summary += ` From: ${email.from}\n`; summary += ` Date: ${new Date(email.receivedAt).toLocaleDateString()}\n`; summary += ` Reason: ${email.reason}\n`; summary += ` Current folder: ${email.folder}\n`; summary += ` ID: ${email.id}\n\n`; }); } if (results.miscategorized.length > 0) { summary += `๐Ÿ“‚ MISCATEGORIZED EMAILS:\n`; results.miscategorized.slice(0, 10).forEach((email, i) => { summary += ` ${i+1}. "${email.subject}..."\n`; summary += ` From: ${email.from}\n`; summary += ` Current: ${email.currentFolder} โ†’ Suggested: ${email.suggestedFolder}\n`; summary += ` Reason: ${email.reason}\n\n`; }); if (results.miscategorized.length > 10) { summary += ` ... and ${results.miscategorized.length - 10} more\n\n`; } } if (results.needsAttention.length > 0) { summary += `โš ๏ธ ITEMS NEEDING ATTENTION:\n`; results.needsAttention.forEach((email, i) => { summary += ` ${i+1}. "${email.subject}..."\n`; summary += ` From: ${email.from}\n`; summary += ` Folder: ${email.folder}\n`; summary += ` Reason: ${email.reason}\n\n`; }); } if (results.missedReplies.length === 0 && results.miscategorized.length === 0 && results.needsAttention.length === 0) { summary += `โœ… EXCELLENT! No issues found in your archived emails.\n`; summary += ` All emails appear to be properly categorized and replied to as needed.\n`; } return summary; } catch (error) { return `Error reviewing archived emails: ${error.message}`; } } async function checkIfEmailNeedsReply(email, sentEmails, currentFolder) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; const preview = email.preview?.toLowerCase() || ''; const combinedText = (subject + ' ' + preview).toLowerCase(); const daysOld = (new Date() - new Date(email.receivedAt)) / (1000 * 60 * 60 * 24); // Skip if we already replied const hasReplied = checkForExistingReplyInSent(email, sentEmails); if (hasReplied) { return { shouldReply: false, reason: 'Already replied' }; } // Skip automated/marketing emails if (from.includes('noreply') || from.includes('no-reply') || from.includes('donotreply') || from.includes('notification') || from.includes('support@') || subject.includes('newsletter') || subject.includes('unsubscribe')) { return { shouldReply: false, reason: 'Automated email' }; } // Check for personal emails that might need replies if ((from.includes('gmail.com') || from.includes('yahoo.com') || from.includes('outlook.com')) && (combinedText.includes('?') || combinedText.includes('question') || combinedText.includes('need') || combinedText.includes('help') || combinedText.includes('can you') || combinedText.includes('could you') || combinedText.includes('please let me know') || combinedText.includes('what do you think')) && daysOld < 30) { return { shouldReply: true, reason: 'Personal email with question/request' }; } // Check for business emails requiring response if ((subject.includes('meeting') || subject.includes('interview') || subject.includes('follow up') || subject.includes('response needed') || subject.includes('rsvp')) && daysOld < 21) { return { shouldReply: true, reason: 'Business email requiring response' }; } // Check for family/friend emails in wrong folder if ((from.includes('rebeccab.barnard') || from.includes('ljminniti') || from.includes('family') || from.includes('friend')) && currentFolder !== 'Personal' && daysOld < 14) { return { shouldReply: true, reason: 'Personal relationship email that may need response' }; } return { shouldReply: false, reason: 'No reply needed' }; } function determineCorrectCategory(email) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; // Use the same logic as determineInboxAction but just return the category if (from.includes('wsj.com') || from.includes('nytimes') || from.includes('cnn')) { return 'News'; } if (subject.includes('newsletter') || from.includes('substack')) { return 'Newsletter'; } if (from.includes('amazon') || from.includes('fedex') || subject.includes('order')) { return 'Shopping'; } if (from.includes('github.com') || from.includes('noreply') || from.includes('support@')) { return 'Technical'; } if (from.includes('fidelity') || from.includes('schwab') || from.includes('bank')) { return 'Financial'; } if (from.includes('gmail.com') || from.includes('yahoo.com') || from.includes('airbnb')) { return 'Personal'; } if (from.includes('.edu') || from.includes('university')) { return 'Education'; } return 'Archive'; } function checkForAttention(email, reviewDays) { const from = email.from[0]?.email?.toLowerCase() || ''; const subject = email.subject?.toLowerCase() || ''; const daysOld = (new Date() - new Date(email.receivedAt)) / (1000 * 60 * 60 * 24); // Check for time-sensitive items that were archived but might still be relevant if (subject.includes('deadline') && daysOld < 7) { return { needsAttention: true, reason: 'Recent deadline - verify if still relevant' }; } if (subject.includes('interview') && daysOld < 14) { return { needsAttention: true, reason: 'Recent interview email - may need follow-up' }; } if (subject.includes('offer') && daysOld < 30) { return { needsAttention: true, reason: 'Recent offer - verify status' }; } if (subject.includes('urgent') && daysOld < 3) { return { needsAttention: true, reason: 'Recently marked urgent' }; } return { needsAttention: false, reason: 'No attention needed' }; } // HIERARCHICAL EMAIL ORGANIZATION FUNCTIONS async function findOrCreateMailboxByPath(client, fullPath) { const mailboxes = await client.getMailboxes(); // Look for existing mailbox with this path const targetMailbox = mailboxes.find(m => { // Try exact match first if (m.name === fullPath) return true; // Try matching the last part of the path (leaf folder name) const pathParts = fullPath.split(' > '); const leafName = pathParts[pathParts.length - 1]; if (m.name === leafName) return true; return false; }); if (targetMailbox) { return targetMailbox; } // Mailbox doesn't exist, create it const pathParts = fullPath.split(' > '); const folderName = pathParts.join('-'); // Convert "Business > Communications" to "Business-Communications" try { const createdMailbox = await client.createMailbox(folderName, null); return createdMailbox; } catch (error) { console.error(`Failed to create mailbox for path ${fullPath}:`, error); // Fallback to Archive folder return mailboxes.find(m => m.name === 'Archive') || mailboxes.find(m => m.role === 'archive'); } } async function setupSimpleLabels(client, categorizer, options) { const { dryRun = false } = options; try { let summary = `๐Ÿ—‚๏ธ SIMPLE LABEL SETUP ${dryRun ? '(DRY RUN)' : ''}\n\n`; if (dryRun) { const labels = categorizer.getSimpleLabels(); summary += `๐Ÿ“ LABELS TO CREATE (${labels.length}):\n`; labels.forEach(label => { summary += ` ๐Ÿ“„ ${label}\n`; }); summary += `\n๐Ÿ’ก This was a dry run. Use dryRun=false to actually create labels.\n`; return summary; } const results = await categorizer.createSimpleLabels(client); summary += `๐Ÿ“Š SETUP RESULTS:\n`; summary += ` Created: ${results.created}\n`; summary += ` Already existing: ${results.skipped}\n`; summary += ` Failed: ${results.failed}\n`; if (results.created > 0) { summary += `\n๐ŸŽ‰ Simple label structure successfully set up!\n`; summary += ` You can now use the categorize_with_simple_labels tool to organize emails.\n`; } return summary; } catch (error) { return `Error setting up simple labels: ${error.message}`; } } async function categorizeWithSimpleLabels(client, categorizer, options) { const { emailId, batchSize = 50, folder, dryRun = false } = options; try { let emails; let summary = `๐Ÿท๏ธ MULTI-LABEL EMAIL CATEGORIZATION ${dryRun ? '(DRY RUN)' : ''}\n\n`; // Get emails to categorize if (emailId) { const email = await client.getEmailById(emailId); emails = email ? [email] : []; summary += `๐Ÿ“ง Processing specific email: ${emailId}\n`; } else { const mailboxes = await client.getMailboxes(); let sourceMailbox; if (folder) { sourceMailbox = mailboxes.find(mb => mb.name === folder); if (!sourceMailbox) { return `Error: Folder "${folder}" not found`; } summary += `๐Ÿ“‚ Source: ${folder}\n`; } else { sourceMailbox = mailboxes.find(mb => mb.role === 'inbox'); summary += `๐Ÿ“‚ Source: Inbox\n`; } emails = await client.getEmails(sourceMailbox.id, batchSize); summary += `๐Ÿ“ง Processing ${emails.length} emails\n`; } if (emails.length === 0) { return summary + `\n๐Ÿ“ญ No emails to process.\n`; } const results = { processed: 0, categorized: 0, labeled: 0, kept_in_inbox: 0, errors: 0, categories: {}, multiLabelEmails: 0, averageConfidence: 0, confidenceSum: 0 }; summary += `\n๐Ÿ“Š CATEGORIZATION RESULTS:\n`; // Process each email for (const email of emails) { try { results.processed++; const categoryResult = categorizer.categorizeEmail(email); results.categorized++; results.confidenceSum += categoryResult.confidence; // Track multi-label emails if (categoryResult.categories && categoryResult.categories.length > 1) { results.multiLabelEmails++; } // Track category distribution (including all categories) const categories = categoryResult.categories || [categoryResult.primaryCategory || 'Archive']; categories.forEach(category => { if (!results.categories[category]) { results.categories[category] = { count: 0, totalConfidence: 0 }; } results.categories[category].count++; results.categories[category].totalConfidence += categoryResult.confidence; }); // Handle action required emails if (categoryResult.keepInInbox) { results.kept_in_inbox++; if (categoryResult.starred && !dryRun) { // Star high priority emails (if JMAP supports it) // For now, just track it } } else if (!dryRun) { // Assign email to multiple appropriate labels try { const categoriesToAssign = categories.filter(cat => cat !== 'Archive'); if (categoriesToAssign.length > 0) { // Find or create all required mailboxes const mailboxResults = await client.findOrCreateMultipleMailboxes(categoriesToAssign); const validMailboxes = mailboxResults.filter(r => r.mailbox && !r.error); if (validMailboxes.length > 0) { const mailboxIds = validMailboxes.map(r => r.mailbox.id); await client.assignEmailsToMailboxes([email.id], mailboxIds, false); results.labeled++; // Log multi-label assignment for debugging if (validMailboxes.length > 1) { console.log(`๐Ÿ“ง Multi-labeled email "${email.subject}" assigned to: ${validMailboxes.map(r => r.name).join(', ')} (confidence: ${categoryResult.confidence.toFixed(2)})`); } } } } catch (error) { console.error(`Failed to label email ${email.id} with categories [${categories.join(', ')}]:`, error); results.errors++; } } } catch (error) { results.errors++; console.error(`Error categorizing email ${email.id}:`, error.message); } } // Calculate average confidence results.averageConfidence = results.categorized > 0 ? results.confidenceSum / results.categorized : 0; // Add results to summary summary += ` Processed: ${results.processed}\n`; summary += ` Successfully categorized: ${results.categorized}\n`; summary += ` Labeled: ${results.labeled}\n`; summary += ` Multi-labeled emails: ${results.multiLabelEmails}\n`; summary += ` Kept in inbox: ${results.kept_in_inbox}\n`; summary += ` Errors: ${results.errors}\n`; summary += ` Average confidence: ${results.averageConfidence.toFixed(3)}\n`; // Category distribution with confidence scores summary += `\n๐Ÿ“ CATEGORY DISTRIBUTION (with confidence):\n`; const sortedCategories = Object.entries(results.categories) .sort(([,a], [,b]) => b.count - a.count); for (const [category, data] of sortedCategories) { const avgConfidence = data.count > 0 ? data.totalConfidence / data.count : 0; summary += ` ${category}: ${data.count} emails (avg confidence: ${avgConfidence.toFixed(3)})\n`; } if (dryRun) { summary += `\n๐Ÿ’ก This was a dry run. Use dryRun=false to actually label emails.\n`; } else if (results.labeled > 0) { summary += `\n๐ŸŽ‰ Successfully organized ${results.labeled} emails with multi-label categorization!\n`; if (results.multiLabelEmails > 0) { summary += `๐Ÿท๏ธ ${results.multiLabelEmails} emails received multiple relevant labels.\n`; } } return summary; } catch (error) { return `Error categorizing emails: ${error.message}`; } } async function findOrCreateMailboxByName(client, labelName) { const mailboxes = await client.getMailboxes(); // Look for existing mailbox with this name const targetMailbox = mailboxes.find(m => m.name === labelName); if (targetMailbox) { return targetMailbox; } // Mailbox doesn't exist, create it try { const createdMailbox = await client.createMailbox(labelName, null); return createdMailbox; } catch (error) { console.error(`Failed to create mailbox ${labelName}:`, error); // Fallback to Archive folder return mailboxes.find(m => m.name === 'Archive') || mailboxes.find(m => m.role === 'archive'); } } async function setupHierarchicalFolders(client, categorizer, options) { const { dryRun, createMissing } = options; try { const existingMailboxes = await client.getMailboxes(); const requiredMailboxes = categorizer.getRequiredMailboxes(); let summary = `๐Ÿ—‚๏ธ HIERARCHICAL FOLDER SETUP ${dryRun ? '(DRY RUN)' : ''}\n\n`; const results = { existing: 0, toCreate: 0, created: 0, failed: 0 }; // Check which folders exist and which need to be created const existingNames = existingMailboxes.map(mb => mb.name); const foldersToCreate = []; for (const requiredBox of requiredMailboxes) { if (existingNames.includes(requiredBox.name)) { results.existing++; } else { results.toCreate++; foldersToCreate.push(requiredBox); } } summary += `๐Ÿ“Š FOLDER ANALYSIS:\n`; summary += ` Existing folders: ${results.existing}\n`; summary += ` Folders to create: ${results.toCreate}\n\n`; if (foldersToCreate.length > 0) { summary += `๐Ÿ“ FOLDERS TO CREATE:\n`; // Group by type for better organization const mainCategories = foldersToCreate.filter(f => f.type === 'main'); const subcategories = foldersToCreate.filter(f => f.type === 'subcategory'); const folders = foldersToCreate.filter(f => f.type === 'folder'); if (mainCategories.length > 0) { summary += `\n๐Ÿ—๏ธ MAIN CATEGORIES (${mainCategories.length}):\n`; mainCategories.forEach(folder => { summary += ` ๐Ÿ“‚ ${folder.name}\n`; }); } if (subcategories.length > 0) { summary += `\n๐Ÿ“‹ SUBCATEGORIES (${subcategories.length}):\n`; subcategories.forEach(folder => { summary += ` ๐Ÿ“ ${folder.name}\n`; }); } if (folders.length > 0) { summary += `\n๐Ÿ“„ SPECIFIC FOLDERS (${folders.length}):\n`; folders.slice(0, 20).forEach(folder => { summary += ` ๐Ÿ“„ ${folder.name}\n`; }); if (folders.length > 20) { summary += ` ... and ${folders.length - 20} more\n`; } } // Create folders if not dry run if (!dryRun && createMissing) { summary += `\n๐Ÿ”จ CREATING FOLDERS...\n`; // Create in order: main categories first, then subcategories, then folders const creationOrder = [...mainCategories, ...subcategories, ...folders]; for (const folderSpec of creationOrder) { try { // Create the actual mailbox using JMAP API const createdMailbox = await client.createMailbox(folderSpec.name, folderSpec.parentId); results.created++; summary += ` โœ… Created: ${folderSpec.name} (ID: ${createdMailbox.id})\n`; } catch (error) { results.failed++; summary += ` โŒ Failed: ${folderSpec.name} - ${error.message}\n`; } } } } summary += `\n๐Ÿ“Š SETUP RESULTS:\n`; summary += ` Total folders required: ${requiredMailboxes.length}\n`; summary += ` Already existing: ${results.existing}\n`; summary += ` Created: ${results.created}\n`; summary += ` Failed: ${results.failed}\n`; if (dryRun) { summary += `\n๐Ÿ’ก This was a dry run. Use dryRun=false to actually create folders.\n`; } else if (results.created > 0) { summary += `\n๐ŸŽ‰ Hierarchical folder structure successfully set up!\n`; summary += ` You can now use the categorize_with_hierarchy tool to organize emails.\n`; } return summary; } catch (error) { return `Error setting up hierarchical folders: ${error.message}`; } } async function categorizeWithHierarchy(client, categorizer, options) { const { emailId, batchSize, folder, dryRun } = options; try { let summary = `๐Ÿท๏ธ HIERARCHICAL EMAIL CATEGORIZATION ${dryRun ? '(DRY RUN)' : ''}\n\n`; const results = { processed: 0, categorized: 0, moved: 0, errors: 0, categories: {} }; let emailsToProcess = []; if (emailId) { // Process specific email const email = await client.getEmailById(emailId); emailsToProcess = [email]; } else { // Process batch from folder const mailboxes = await client.getMailboxes(); const sourceMailbox = folder ? mailboxes.find(m => m.name.toLowerCase().includes(folder.toLowerCase())) : mailboxes.find(m => m.role === 'inbox'); if (!sourceMailbox) { return `Error: Could not find mailbox for ${folder || 'inbox'}`; } emailsToProcess = await client.getEmails(sourceMailbox.id, batchSize); } summary += `๐Ÿ“ง Processing ${emailsToProcess.length} emails\n`; summary += `๐Ÿ“‚ Source: ${folder || 'Inbox'}\n\n`; const categorizations = []; for (const email of emailsToProcess) { results.processed++; try { const category = categorizer.categorizeEmail(email); categorizations.push({ email, category, subject: email.subject?.substring(0, 60) || 'No subject', from: email.from[0]?.email || 'Unknown' }); // Track category stats const fullPath = category.fullPath; if (!results.categories[fullPath]) { results.categories[fullPath] = 0; } results.categories[fullPath]++; results.categorized++; // Move email if not dry run if (!dryRun) { try { const targetMailbox = await findOrCreateMailboxByPath(client, category.fullPath); if (targetMailbox) { await client.moveEmailsToMailbox([email.id], targetMailbox.id); results.moved++; } } catch (error) { console.error(`Failed to move email ${email.id} to ${category.fullPath}:`, error); results.errors++; } } } catch (error) { results.errors++; console.error(`Error categorizing email ${email.id}:`, error.message); } } // Show categorization results summary += `๐Ÿ“Š CATEGORIZATION RESULTS:\n`; summary += ` Processed: ${results.processed}\n`; summary += ` Successfully categorized: ${results.categorized}\n`; summary += ` Moved: ${results.moved}\n`; summary += ` Errors: ${results.errors}\n\n`; if (Object.keys(results.categories).length > 0) { summary += `๐Ÿ“ CATEGORY DISTRIBUTION:\n`; const sortedCategories = Object.entries(results.categories) .sort(([,a], [,b]) => b - a); sortedCategories.forEach(([category, count]) => { summary += ` ${category}: ${count} emails\n`; }); summary += `\n`; } // Show sample categorizations if (categorizations.length > 0) { summary += `๐Ÿ“‹ SAMPLE CATEGORIZATIONS:\n`; categorizations.slice(0, 10).forEach((item, i) => { summary += ` ${i+1}. "${item.subject}..."\n`; summary += ` From: ${item.from}\n`; summary += ` Category: ${item.category.fullPath}\n`; summary += ` Confidence: ${Math.round(item.category.confidence * 100)}%\n\n`; }); if (categorizations.length > 10) { summary += ` ... and ${categorizations.length - 10} more\n\n`; } } if (dryRun) { summary += `๐Ÿ’ก This was a dry run. Use dryRun=false to actually move emails.\n`; } else if (results.moved > 0) { summary += `๐ŸŽ‰ Successfully organized ${results.moved} emails into hierarchical categories!\n`; } return summary; } catch (error) { return `Error during hierarchical categorization: ${error.message}`; } } async function migrateToHierarchy(client, categorizer, options) { const { sourceFolder, limit, dryRun } = options; try { const mailboxes = await client.getMailboxes(); const sourceMailbox = mailboxes.find(m => m.name.toLowerCase().includes(sourceFolder.toLowerCase()) ); if (!sourceMailbox) { return `Error: Could not find source folder: ${sourceFolder}`; } let summary = `๐Ÿ”„ MIGRATION TO HIERARCHICAL SYSTEM ${dryRun ? '(DRY RUN)' : ''}\n\n`; summary += `๐Ÿ“‚ Source folder: ${sourceMailbox.name}\n`; summary += `๐Ÿ“Š Batch size: ${limit} emails\n\n`; const emails = await client.getEmails(sourceMailbox.id, limit); const results = { processed: 0, migrated: 0, skipped: 0, errors: 0, migrations: {} }; for (const email of emails) { results.processed++; try { const category = categorizer.categorizeEmail(email); const newPath = category.fullPath; // Track migration paths if (!results.migrations[newPath]) { results.migrations[newPath] = []; } results.migrations[newPath].push({ subject: email.subject?.substring(0, 40) || 'No subject', from: email.from[0]?.email || 'Unknown', confidence: category.confidence }); if (category.confidence > 0.5) { results.migrated++; // Move email if not dry run if (!dryRun) { try { const targetMailbox = await findOrCreateMailboxByPath(client, newPath); if (targetMailbox) { await client.moveEmailsToMailbox([email.id], targetMailbox.id); results.moved++; } } catch (error) { console.error(`Failed to move email ${email.id} to ${newPath}:`, error); results.errors++; } } } else { results.skipped++; } } catch (error) { results.errors++; console.error(`Error migrating email ${email.id}:`, error.message); } } summary += `๐Ÿ“Š MIGRATION RESULTS:\n`; summary += ` Processed: ${results.processed}\n`; summary += ` Migrated: ${results.migrated}\n`; summary += ` Skipped (low confidence): ${results.skipped}\n`; summary += ` Errors: ${results.errors}\n\n`; if (Object.keys(results.migrations).length > 0) { summary += `๐Ÿ“ MIGRATION DESTINATIONS:\n`; Object.entries(results.migrations).forEach(([path, emails]) => { summary += `\n ๐Ÿ“‚ ${path} (${emails.length} emails)\n`; emails.slice(0, 3).forEach(email => { summary += ` โ€ข "${email.subject}..." (${Math.round(email.confidence * 100)}%)\n`; }); if (emails.length > 3) { summary += ` ... and ${emails.length - 3} more\n`; } }); } if (dryRun) { summary += `\n๐Ÿ’ก This was a dry run. Use dryRun=false to actually migrate emails.\n`; } else if (results.migrated > 0) { summary += `\n๐ŸŽ‰ Successfully migrated ${results.migrated} emails to hierarchical structure!\n`; } return summary; } catch (error) { return `Error during migration: ${error.message}`; } } async function analyzeHierarchicalStructure(client, categorizer, options) { const { includeSubcategoryStats, suggestNewCategories } = options; try { let summary = `๐Ÿ“Š HIERARCHICAL STRUCTURE ANALYSIS\n\n`; const mailboxes = await client.getMailboxes(); const allPaths = categorizer.getAllCategoryPaths(); // Analyze current vs. ideal structure const existingFolders = mailboxes.map(mb => mb.name); const hierarchicalFolders = allPaths; summary += `๐Ÿ“ CURRENT STATE:\n`; summary += ` Total mailboxes: ${existingFolders.length}\n`; summary += ` Flat structure folders: ${existingFolders.filter(f => !f.includes('/')).length}\n`; summary += ` Hierarchical folders: ${existingFolders.filter(f => f.includes('/')).length}\n\n`; summary += `๐ŸŽฏ IDEAL HIERARCHICAL STRUCTURE:\n`; summary += ` Total categories needed: ${hierarchicalFolders.length}\n`; summary += ` Main categories: ${Object.keys(categorizer.categoryHierarchy).length}\n`; let subcategoryCount = 0; let folderCount = 0; Object.values(categorizer.categoryHierarchy).forEach(category => { subcategoryCount += Object.keys(category.subcategories).length; Object.values(category.subcategories).forEach(subcategory => { folderCount += subcategory.subfolders.length; }); }); summary += ` Subcategories: ${subcategoryCount}\n`; summary += ` Specific folders: ${folderCount}\n\n`; if (includeSubcategoryStats) { summary += `๐Ÿ“‹ DETAILED CATEGORY BREAKDOWN:\n`; Object.entries(categorizer.categoryHierarchy).forEach(([mainCategory, categoryData]) => { summary += `\n๐Ÿ“‚ ${mainCategory.toUpperCase()}:\n`; Object.entries(categoryData.subcategories).forEach(([subcategory, subcategoryData]) => { summary += ` ๐Ÿ“ ${subcategory}:\n`; subcategoryData.subfolders.forEach(folder => { summary += ` ๐Ÿ“„ ${folder}\n`; }); }); }); } // Check for missing folders const missingFolders = hierarchicalFolders.filter(path => { return !existingFolders.some(existing => existing === path || existing.includes(path.split('/').pop()) ); }); if (missingFolders.length > 0) { summary += `\nโš ๏ธ MISSING FOLDERS (${missingFolders.length}):\n`; missingFolders.slice(0, 10).forEach(path => { summary += ` ๐Ÿ“„ ${path}\n`; }); if (missingFolders.length > 10) { summary += ` ... and ${missingFolders.length - 10} more\n`; } } if (suggestNewCategories) { summary += `\n๐Ÿ’ก RECOMMENDATIONS:\n`; summary += ` 1. Run 'setup_hierarchical_folders' to create missing folders\n`; summary += ` 2. Use 'migrate_to_hierarchy' to move emails from flat folders\n`; summary += ` 3. Use 'categorize_with_hierarchy' for ongoing organization\n`; if (missingFolders.length > 0) { summary += ` 4. Create ${missingFolders.length} missing folders for complete structure\n`; } } summary += `\n๐Ÿ“ˆ BENEFITS OF HIERARCHICAL ORGANIZATION:\n`; summary += ` โ€ข Better email findability with nested categories\n`; summary += ` โ€ข Automated categorization with higher accuracy\n`; summary += ` โ€ข Scalable organization system\n`; summary += ` โ€ข Reduced manual folder management\n`; return summary; } catch (error) { return `Error analyzing hierarchical structure: ${error.message}`; } } async function runServer() { try { // Initialize FastMail client before starting server await initializeFastMailClient(); console.error('FastMail client initialized successfully'); const transport = new StdioServerTransport(); await server.connect(transport); console.error('FastMail MCP server running on stdio'); } catch (error) { console.error('Failed to initialize MCP server:', error); throw error; } } runServer().catch((error) => { console.error('MCP server startup error:', error); // Don't exit - let the global error handlers manage this });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gr3enarr0w/fastmail-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server