// script forked from https://gist.github.com/jamesramsay/9298cf3f4ac584a3dc05 // to start timed execution: select "Install" from the menu above and hit "run" // to stop timed execution: select "Uninstall" from the menu above and hit "run" // to initialize a batch run ad-hoc and immediately: select "_processNextBatch" from the menu above and hit "run" const LABELS_TO_DELETE = [ // ... list of labels ... // 'test-delete-after-label', 'spammy-alerts', // 'delete-after-30d', // TODO - add support for different time bombs // 'delete-after-7d', // 'delete-after-1d', // TODO - add support for archive // 'archive-after-30d', // 'archive-after-7d', ]; // Constants for trigger names const DAILY_TRIGGER_NAME = "dailyProcessTrigger"; const DELAYED_BATCH_TRIGGER_NAME = "delayedBatchTrigger"; // timezone probably doesn't really matter if we're only working in days. const TIMEZONE = "America/Los Angeles"; // Batch size and timing configuration const MAX_THREADS_TO_PROCESS_PER_BATCH = 500; // max argument size is 500 const MINUTES_TO_WAIT_BETWEEN_BATCHES = 5; // TODO - why? const DELETE_AFTER_DAYS = 30; function _getPurgeBeforeDate() { const today = new Date(); // TODO - change DELETE_AFTER_DAYS based on the label const age = new Date(today.setDate(today.getDate() - DELETE_AFTER_DAYS)); return Utilities.formatDate(age, TIMEZONE, "yyyy-MM-dd"); } function _processNextBatch() { console.log('START process next batch'); // Clear all batch triggers _clearAllDelayedBatchTriggers(); _processEmails(); console.log('FINISH process batch'); } function _processEmails() { let threadsProcessedCount = 0; const threadsProcessedPerLabel = {}; const purgeBeforeDate = _getPurgeBeforeDate() LABELS_TO_DELETE.forEach(function(label) { const threadBudgetLeftForThisBatch = MAX_THREADS_TO_PROCESS_PER_BATCH - threadsProcessedCount; if (threadBudgetLeftForThisBatch <= 0) { return; // Skip this label if the maximum number of threads to process has been reached } const search = 'label:' + label + ' before:' + purgeBeforeDate; console.log('Searching for threads with label: ' + label); const threads = GmailApp.search(search, 0, threadBudgetLeftForThisBatch); const threadsToDeleteCount = threads.length; console.log('Found ' + threadsToDeleteCount + ' threads to delete for label: ' + label); for (let i = 0; i < threadsToDeleteCount; i++) { // NB: this moves the thread to the trash. so thread will not get *permanently* deleted // until 30 days *after* it gets moved to the trash. threads[i].moveToTrash(); } threadsProcessedCount += threadsToDeleteCount; threadsProcessedPerLabel[label] = (threadsProcessedPerLabel[label] || 0) + threadsToDeleteCount; }); // Log the deleted email count per label console.log('Deleted emails from labels:'); for (let label in threadsProcessedPerLabel) { console.log(' ' + label + ': ' + threadsProcessedPerLabel[label]); } // Schedule another run if we hit the processing limit. // NB: Since we're only loading up to MAX_THREADS_TO_PROCESS_PER_BATCH at a time, we don't // know if there are more threads to process in the next execution. So, if we reach the limit, // there are *probably* more threads to process. It's possible there's no more left but it's not // a big deal to just run it again. if (threadsProcessedCount >= MAX_THREADS_TO_PROCESS_PER_BATCH) { // TODO - when would threadsProcessedCount be above MAX_THREADS_TO_PROCESS? console.log("Reached processing limit, scheduling next batch..."); ScriptApp.newTrigger(DELAYED_BATCH_TRIGGER_NAME) .timeBased() .after(MINUTES_TO_WAIT_BETWEEN_BATCHES * 60 * 1000) .create(); } else { console.log('Did not reach batch limit, no follow-up needed.'); } } function _clearAllDelayedBatchTriggers() { // cancel all delayed jobs const existingTriggers = ScriptApp.getProjectTriggers(); console.log('Checking for triggers other than the daily trigger...'); let foundDelayedBatchTrigger = false; for (let i = 0; i < existingTriggers.length; i++) { const trigger = existingTriggers[i]; if (trigger.getHandlerFunction() === DELAYED_BATCH_TRIGGER_NAME) { ScriptApp.deleteTrigger(trigger); console.log('Deleted delayed batch trigger: ' + trigger.getUniqueId()); foundDelayedBatchTrigger = true; } } if (!foundDelayedBatchTrigger) { console.log('No delayed batch triggers found.') } } function dailyProcessTrigger() { // run this daily to initiate the auto delete process console.log('START run daily deletion process for Gmail threads...'); _processNextBatch(); console.log('FINISH run daily deletion process.'); } function delayedBatchTrigger() { // run this to start the process again if we didn't finish the first time console.log('START process delayed batch'); _processNextBatch(); console.log('FINISH process delayed batch'); } function Install() { console.log('Installing triggers...'); // Clear all existing triggers before setting up new ones Uninstall(); // Set up the new daily trigger ScriptApp.newTrigger(DAILY_TRIGGER_NAME) .timeBased() .everyDays(1) .create(); console.log('Installation complete.'); } function Uninstall() { console.log('Uninstalling all triggers...'); const triggers = ScriptApp.getProjectTriggers(); for (let i = 0; i < triggers.length; i++) { ScriptApp.deleteTrigger(triggers[i]); } console.log('All triggers have been removed.'); }