Last active
November 22, 2025 21:55
-
-
Save dwightmulcahy/646519777b1283dfeb0cc1f0a6b148c6 to your computer and use it in GitHub Desktop.
Google app script to empty the trash. A time out of 5 minutes is built in to avoid the "Function execution has timed out" error if it runs too long.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ---------------------------- | |
| // --- GLOBAL CONFIGURATION --- | |
| // ---------------------------- | |
| // Define the list of Gmail folders (labels) to be cleaned. | |
| // To specify a custom label that is nested under another, use a forward slash '/'. | |
| const FOLDERS_TO_CLEAN = ["Trash", "Spam", "My Custom Label"]; // <--- CONFIGURE YOUR FOLDERS HERE | |
| // Define a list of email addresses from which to delete messages. | |
| // If this list is empty, the script will delete messages from ALL senders | |
| // found within the FOLDERS_TO_CLEAN. | |
| const SENDERS_TO_DELETE = ["unwanted.sender@example.com", "another.spam@domain.net"]; // <--- CONFIGURE SENDERS TO DELETE HERE | |
| // Define a list of email addresses to explicitly exclude from deletion. | |
| // Messages from these senders will NOT be deleted, even if they are in the | |
| // FOLDERS_TO_CLEAN or from a sender on the SENDERS_TO_DELETE list. | |
| const SENDERS_TO_EXCLUDE = ["important.person@work.com", "do-not-touch@family.net"]; // <--- CONFIGURE SENDERS TO EXCLUDE HERE | |
| // Define the maximum duration (in minutes) for the script to run. | |
| const MAX_CLEANUP_DURATION_MINUTES = 5; // <--- CONFIGURE MAX DURATION IN MINUTES HERE | |
| // ---------------------------- | |
| /** | |
| * Permanently deletes messages from specified Gmail folders (labels). | |
| * The script can be configured to delete from specific senders, all senders, | |
| * and to exclude certain senders from the deletion process. | |
| */ | |
| function emptyGmailTrash() { | |
| const MAX_DURATION_MS = MAX_CLEANUP_DURATION_MINUTES * 60 * 1000; | |
| const startTime = Date.now(); | |
| var folderThreadCounts = {}; | |
| var totalThreadsProcessed = 0; | |
| var messagesCollectedCount = 0; | |
| var messagesSuccessfullyDeletedCount = 0; | |
| var timeLimitHit = false; | |
| var deletionAttempted = false; | |
| var deletionSuccessful = false; | |
| var deletionErrorMessage = "No error."; | |
| // --- Step 1: Construct the Search Query --- | |
| Logger.log("Constructing the search query..."); | |
| let searchQuery = ""; | |
| // Combine folders into an 'in:' or 'label:' operator string | |
| if (FOLDERS_TO_CLEAN && FOLDERS_TO_CLEAN.length > 0) { | |
| let folderQueries = FOLDERS_TO_CLEAN.map(folder => `in:${folder.toLowerCase()}`).join(" OR "); | |
| searchQuery += `(${folderQueries})`; | |
| } else { | |
| // If no folders are specified, we can't perform the action. | |
| Logger.log("Error: No folders specified in FOLDERS_TO_CLEAN. Exiting."); | |
| logSummary(); | |
| return; | |
| } | |
| // Add senders to include, if any | |
| if (SENDERS_TO_DELETE && SENDERS_TO_DELETE.length > 0) { | |
| let senderQueries = SENDERS_TO_DELETE.map(sender => `from:"${sender}"`).join(" OR "); | |
| searchQuery += ` AND (${senderQueries})`; | |
| Logger.log("Action: Deleting messages from specific senders."); | |
| } else { | |
| Logger.log("Action: Deleting all messages within specified folders."); | |
| } | |
| // Add senders to exclude, if any | |
| if (SENDERS_TO_EXCLUDE && SENDERS_TO_EXCLUDE.length > 0) { | |
| let excludeQueries = SENDERS_TO_EXCLUDE.map(sender => `-from:"${sender}"`).join(" "); | |
| searchQuery += ` ${excludeQueries}`; | |
| Logger.log("Excluding messages from specific senders."); | |
| } | |
| Logger.log(`Final Search Query: ${searchQuery}`); | |
| // --- Step 2: Retrieve Threads using the Constructed Query --- | |
| var threadsToProcess = []; | |
| try { | |
| const threads = GmailApp.search(searchQuery); | |
| totalThreadsProcessed = threads.length; | |
| if (totalThreadsProcessed > 0) { | |
| Logger.log(`Found ${totalThreadsProcessed} threads matching the query.`); | |
| threadsToProcess = threads; | |
| } else { | |
| Logger.log("No threads found matching the search criteria. Exiting."); | |
| logSummary(); | |
| return; | |
| } | |
| } catch (e) { | |
| Logger.log(`Error searching for threads: ${e.toString()}`); | |
| deletionErrorMessage = `Error searching for threads: ${e.toString()}`; | |
| logSummary(); | |
| return; | |
| } | |
| // --- Step 3: Collect Message IDs from All Identified Threads --- | |
| var messageIdsToDelete = []; | |
| Logger.log("Collecting message IDs from identified threads..."); | |
| for (var i = 0; i < threadsToProcess.length; i++) { | |
| if ((Date.now() - startTime) > MAX_DURATION_MS) { | |
| Logger.log("Time limit reached during thread iteration. Processing collected IDs so far."); | |
| timeLimitHit = true; | |
| break; | |
| } | |
| var messages = threadsToProcess[i].getMessages(); | |
| for (var j = 0; j < messages.length; j++) { | |
| if ((Date.now() - startTime) > MAX_DURATION_MS) { | |
| Logger.log("Time limit reached during message ID collection. Collected " + messageIdsToDelete.length + " message IDs so far."); | |
| timeLimitHit = true; | |
| break; | |
| } | |
| messageIdsToDelete.push(messages[j].getId()); | |
| } | |
| if (timeLimitHit) { | |
| break; | |
| } | |
| } | |
| messagesCollectedCount = messageIdsToDelete.length; | |
| // --- Step 4: Perform Batch Deletion --- | |
| if (messagesCollectedCount > 0) { | |
| Logger.log("Attempting to permanently delete " + messagesCollectedCount + " messages using batch delete..."); | |
| deletionAttempted = true; | |
| try { | |
| Gmail.Users.Messages.batchDelete({ | |
| ids: messageIdsToDelete | |
| }, 'me'); | |
| messagesSuccessfullyDeletedCount = messagesCollectedCount; | |
| deletionSuccessful = true; | |
| Logger.log("Successfully deleted " + messagesSuccessfullyDeletedCount + " messages."); | |
| } catch (e) { | |
| Logger.log("FAILED to delete messages. Error: " + e.toString()); | |
| deletionErrorMessage = "Deletion failed: " + e.toString(); | |
| } | |
| } else { | |
| Logger.log("No message IDs found to delete after collection process."); | |
| } | |
| // --- Final Summary Log --- | |
| function logSummary() { | |
| Logger.log("\n--- Gmail Cleanup Summary ---"); | |
| Logger.log(`Script Start Time: ${new Date(startTime).toLocaleString()}`); | |
| Logger.log(`Configured Max Duration: ${MAX_CLEANUP_DURATION_MINUTES} minutes (${MAX_DURATION_MS / 1000} seconds)`); | |
| Logger.log(`Folders Configured for Cleanup: ${FOLDERS_TO_CLEAN.join(', ')}`); | |
| Logger.log(`Senders to Delete: ${SENDERS_TO_DELETE.length > 0 ? SENDERS_TO_DELETE.join(', ') : 'All'}`); | |
| Logger.log(`Senders to Exclude: ${SENDERS_TO_EXCLUDE.length > 0 ? SENDERS_TO_EXCLUDE.join(', ') : 'None'}`); | |
| Logger.log(`Total Threads Identified for Deletion: ${totalThreadsProcessed}`); | |
| Logger.log(`Messages Identified for Deletion: ${messagesCollectedCount}`); | |
| Logger.log(`Deletion Attempted: ${deletionAttempted ? 'Yes' : 'No'}`); | |
| Logger.log(`Messages Successfully Deleted: ${messagesSuccessfullyDeletedCount}`); | |
| Logger.log(`Time Limit Reached During Execution: ${timeLimitHit ? 'Yes' : 'No'}`); | |
| Logger.log(`Actual Execution Duration: ${(Date.now() - startTime) / 1000} seconds`); | |
| Logger.log(`Status: ${deletionSuccessful ? 'SUCCESS' : (deletionAttempted && !deletionSuccessful ? 'FAILED' : 'NO_MESSAGES_TO_DELETE')}`); | |
| if (!deletionSuccessful && deletionErrorMessage !== "No error.") { | |
| Logger.log(`Error Details: ${deletionErrorMessage}`); | |
| } | |
| Logger.log("------------------------------------------"); | |
| } | |
| logSummary(); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@Aanusha-G , I recently considered the same idea for excluding senders. With a shared search mechanism, adding it was straightforward.
Below is the updated configuration required. Although I feel the search is faster at this point, you might also want to increase the
MAX_CLEANUP_DURATION_MINUTESby a couple of minutes, too.Gmail Cleanup Script Configuration
This script helps you automatically and permanently delete emails from your Gmail account based on specific criteria. You can customize the folders to clean, the senders to target for deletion, and the senders to always keep.
Here's a breakdown of the configuration settings:
Folders and Labels to Clean
This array specifies which Gmail folders or labels the script will scan for messages to delete.
/(e.g.,"Projects/Subproject").Senders to Delete
This array lists the email addresses of senders whose messages you want to delete.
Senders to Exclude
This array defines a list of "safe" senders. The script will never delete messages from these email addresses, regardless of whether they are in the
FOLDERS_TO_CLEANor on theSENDERS_TO_DELETElist. This is a powerful safeguard to prevent accidental deletion of important emails.Script Execution Time Limit
This setting ensures the script doesn’t run indefinitely by stopping it gracefully after the specified number of minutes.
Since Google Apps Script has a maximum execution time of 6 minutes per run, setting this above 6 minutes may lead to forced termination. It’s best to leave it at the default value to avoid hitting that limit.