#!/usr/bin/env node
import { FastMailClient } from './src/fastmail-client.js';
import dotenv from 'dotenv';
dotenv.config({ path: '../.env' });
async function migrateAllEmails() {
console.log('π§ MIGRATING ALL EMAILS TO HIERARCHICAL STRUCTURE\n');
const client = new FastMailClient(
process.env.FASTMAIL_API_TOKEN,
'clark@clarkeverson.com',
'clark@clarkeverson.com',
'clarkeverson.com',
'https://api.fastmail.com/jmap/session'
);
try {
await client.authenticate();
console.log('β
Authenticated\n');
const mailboxes = await client.getMailboxes();
console.log(`π Found ${mailboxes.length} mailboxes\n`);
// Define all migrations - old folders to new hierarchical structure
const migrations = [
// Information migrations
{ from: 'News', to: 'Information/News', reason: 'News content' },
{ from: 'Newsletter', to: 'Information/Newsletters', reason: 'Newsletter content' },
{ from: 'screened-newsletters', to: 'Information/Newsletters', reason: 'Screened newsletters' },
{ from: 'Updates', to: 'Information/Newsletters', reason: 'Update notifications' },
{ from: 'Education', to: 'Information/Education', reason: 'Educational content' },
// Financial migrations
{ from: 'Banking', to: 'Financial/Banking', reason: 'Banking notifications' },
{ from: 'Transactions', to: 'Financial/Receipts', reason: 'Transaction receipts' },
// Commerce migrations
{ from: 'Shopping', to: 'Commerce/Orders', reason: 'Shopping orders' },
// Professional migrations
{ from: 'Technical', to: 'Professional/GitHub', reason: 'Technical content' },
// Cleanup migrations
{ from: 'Unsubscribed', to: 'Archive', reason: 'Archive unsubscribed emails' }
];
console.log('π§ STARTING EMAIL MIGRATIONS:');
console.log('='.repeat(60));
let totalEmailsMigrated = 0;
for (const migration of migrations) {
try {
// Find source mailbox
const sourceMailbox = mailboxes.find(mb => mb.name === migration.from);
if (!sourceMailbox) {
console.log(`β οΈ Source '${migration.from}' not found`);
continue;
}
if (sourceMailbox.totalEmails === 0) {
console.log(`β
${migration.from} β ${migration.to} (already empty)`);
continue;
}
// Find target mailbox (handle hierarchical paths)
let targetMailbox;
if (migration.to.includes('/')) {
const [parentName, childName] = migration.to.split('/');
const parent = mailboxes.find(mb => mb.name === parentName);
if (parent) {
targetMailbox = mailboxes.find(mb =>
mb.parentId === parent.id && mb.name === childName
);
}
} else {
targetMailbox = mailboxes.find(mb => mb.name === migration.to);
}
if (!targetMailbox) {
console.log(`β Target '${migration.to}' not found for ${migration.from}`);
continue;
}
console.log(`\nπ§ Migrating: ${migration.from} β ${migration.to}`);
console.log(` π Emails to move: ${sourceMailbox.totalEmails}`);
console.log(` π Reason: ${migration.reason}`);
// Get all emails from source mailbox in batches
let allEmailIds = [];
let position = 0;
const batchSize = 100;
while (true) {
const emailBatch = await client.getEmails({
filter: { inMailbox: sourceMailbox.id },
properties: ['id'],
position: position,
limit: batchSize
});
if (!emailBatch || emailBatch.length === 0) {
break;
}
allEmailIds.push(...emailBatch.map(email => email.id));
position += batchSize;
console.log(` π₯ Loaded ${allEmailIds.length} email IDs...`);
if (emailBatch.length < batchSize) {
break; // Last batch
}
}
if (allEmailIds.length > 0) {
console.log(` π Moving ${allEmailIds.length} emails...`);
// Move emails in smaller batches to avoid timeouts
const moveBatchSize = 50;
let moved = 0;
for (let i = 0; i < allEmailIds.length; i += moveBatchSize) {
const batch = allEmailIds.slice(i, i + moveBatchSize);
await client.moveEmailsToMailbox(batch, targetMailbox.id);
moved += batch.length;
console.log(` β
Moved ${moved}/${allEmailIds.length} emails`);
// Small delay between batches
await new Promise(resolve => setTimeout(resolve, 1000));
}
totalEmailsMigrated += allEmailIds.length;
console.log(` π Successfully migrated all ${allEmailIds.length} emails`);
} else {
console.log(` βΉοΈ No emails found to move`);
}
} catch (error) {
console.log(` β Migration error: ${error.message}`);
}
}
// Special handling for Transactions sub-folders
console.log('\nπ¦ Handling Transactions sub-folders...');
const transactionsParent = mailboxes.find(mb => mb.name === 'Transactions');
if (transactionsParent) {
const transactionChildren = mailboxes.filter(mb => mb.parentId === transactionsParent.id);
for (const child of transactionChildren) {
if (child.totalEmails > 0) {
console.log(`\nπ§ Moving from Transactions/${child.name} (${child.totalEmails} emails)`);
const targetPath = child.name === 'Orders' ? 'Commerce/Orders' : 'Financial/Receipts';
const [parentName, childName] = targetPath.split('/');
const parent = mailboxes.find(mb => mb.name === parentName);
const target = parent ? mailboxes.find(mb =>
mb.parentId === parent.id && mb.name === childName
) : null;
if (target) {
try {
const emails = await client.getEmails({
filter: { inMailbox: child.id },
properties: ['id']
});
if (emails && emails.length > 0) {
await client.moveEmailsToMailbox(emails.map(e => e.id), target.id);
console.log(` β
Moved ${emails.length} emails to ${targetPath}`);
totalEmailsMigrated += emails.length;
}
} catch (error) {
console.log(` β Error moving from Transactions/${child.name}: ${error.message}`);
}
}
}
}
}
console.log('\nπ ALL EMAIL MIGRATIONS COMPLETE!');
console.log('='.repeat(60));
console.log(`π Total emails migrated: ${totalEmailsMigrated}`);
console.log('β
All emails now organized in hierarchical structure');
console.log('β
Old folders should now be empty and ready for deletion');
// Show final email counts in hierarchical structure
console.log('\nπ FINAL HIERARCHICAL STRUCTURE EMAIL COUNTS:');
console.log('='.repeat(60));
const parents = ['Information', 'Financial', 'Commerce', 'Professional', 'Personal'];
for (const parentName of parents) {
const parent = mailboxes.find(mb => mb.name === parentName);
if (parent) {
// Refresh mailbox data to get updated counts
const updatedMailboxes = await client.getMailboxes();
const updatedParent = updatedMailboxes.find(mb => mb.id === parent.id);
console.log(`π ${parentName} (${updatedParent.totalEmails} emails)`);
const children = updatedMailboxes.filter(mb => mb.parentId === parent.id);
children.forEach(child => {
console.log(` βββ ${child.name} (${child.totalEmails} emails)`);
});
}
}
console.log('\nβ
Ready for final cleanup and sieve rule updates!');
} catch (error) {
console.log('β Error:', error.message);
console.error(error.stack);
}
}
migrateAllEmails().catch(console.error);