#!/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
});