-
-
Save gene1wood/0f455239490e5342fa49 to your computer and use it in GitHub Desktop.
/* | |
This script, when used with Google Apps Scripts, will delete 400 emails and | |
can be triggered to run every few minutes without user interaction enabling you | |
to bulk delete email in Gmail without getting the #793 error from Gmail. | |
Google returns a maximum of 500 email threads in a single API call. | |
This script fetches 400 threads in case 500 threads is causing timeouts | |
Configure the search query in the code below to match the type of emails | |
you want to delete | |
See - https://developers.google.com/apps-script/reference/gmail/gmail-app#search(String) | |
and https://support.google.com/mail/answer/7190 | |
Browse to https://script.google.com/ | |
Start a script and paste in the code below. | |
After you paste it in, save it. | |
Now you need to set the authorized scopes for the script ( https://developers.google.com/apps-script/concepts/scopes ) | |
On the left side of the screen, hover over the gear symbol and then click on Project Settings. | |
Enable 'Show "appsscript.json" manifest file in editor' | |
On the left side of the screen, click on the < > to return to the code editor | |
You should see a new file appear, appscript.json. Click on it. | |
Edit the file to add an oauthScopes entry for mail.google.com. It should look something like this. | |
{ | |
"oauthScopes": [ | |
"https://mail.google.com/", | |
"https://www.googleapis.com/auth/gmail.readonly", | |
"https://www.googleapis.com/auth/gmail.modify" | |
], | |
"timeZone": "America/New_York", | |
"dependencies": { | |
"enabledAdvancedServices": [ | |
{ | |
"userSymbol": "Gmail", | |
"version": "v1", | |
"serviceId": "gmail" | |
} | |
] | |
}, | |
"exceptionLogging": "STACKDRIVER", | |
"runtimeVersion": "V8" | |
} | |
In the drop down at the top select the function you want | |
to run. For example, you could run the batchDeleteEmail function. | |
This gist contains a few different functions to give examples of how to do other actions | |
besides deleting emails. For example if you wanted to mark all mail with the "work" label as read | |
you could run the markReadLabelWork function | |
Next click the little clock looking button. | |
This is for your triggers. You can set up how frequently you want the script | |
to run (I did mine for every minute but others are seeing execution take longer than | |
a minute in which case you may want to run every 5 or 15 minutes). | |
This writeup from @timur-tabi goes into more detail : https://docs.google.com/document/d/1PLfAnNus-B87gHS1pkbmzFTkWckAPNcqmvO7hFo_gBc/edit | |
Source : # https://productforums.google.com/d/msg/gmail/YeQVDuPIQzA/kpZPDDj8TXkJ | |
This gist includes additions by @kulemantu found in their fork : https://gist.github.com/kulemantu/84682cfebe72eb925cfe/revisions | |
*/ | |
function batchDeleteEmail() { | |
processEmail('label:inbox from:user@example.com', 'moveThreadsToTrash'); | |
} | |
function markReadLabelWork() { | |
processEmail('label:work', 'markThreadsRead'); | |
} | |
function markReadFromInfoExample() { | |
processEmail('from:user@example.com', 'markThreadsRead'); | |
} | |
function processEmail(search, batchAction) { | |
var batchSize = 100; // Process up to 100 threads at once | |
var searchSize = 400; // Limit search result to a max of 400 threads. Use this if you encounter the "Exceeded maximum execution time" error | |
var threads = GmailApp.search(search, 0, searchSize); | |
for (j = 0; j < threads.length; j += batchSize) { | |
GmailApp[batchAction](threads.slice(j, j + batchSize)); | |
} | |
} |
This is fantastic! Thank you!
@Hectorhammett, @asifiqbal, what I noticed is that this worked/works perfectly:
function batchDeleteEmail() {
processEmail('in:inbox after:2018/01/01 before:2022/09/01', 'moveThreadsToTrash');
}
function processEmail(search, batchAction) {
var batchSize = 100; // Process up to 100 threads at once
var searchSize = 500; // Limit search result to a max of 400 threads. Use this if you encounter the "Exceeded maximum execution time" error
console.log('starting with search size: ' + searchSize);
var threads = GmailApp.search(search, 0, searchSize);
for (j = 0; j < threads.length; j += batchSize) {
console.log('removing batch: ' + j + '-' + Number(j + batchSize));
GmailApp[batchAction](threads.slice(j, j + batchSize));
}
console.log('done with search size: ' + threads.length);
}
9:03:13 AM Notice Execution started
9:03:15 AM Info starting with search size: 500
9:03:15 AM Info done with search size: 0
9:03:14 AM Notice Execution completed
But this fails:
function batchDeleteEmail() {
processEmail('in:inbox after:2022/01/01 before:2022/12/01', 'moveThreadsToTrash');
}
function processEmail(search, batchAction) {
var batchSize = 100; // Process up to 100 threads at once
var searchSize = 500; // Limit search result to a max of 400 threads. Use this if you encounter the "Exceeded maximum execution time" error
console.log('starting with search size: ' + searchSize);
var threads = GmailApp.search(search, 0, searchSize);
for (j = 0; j < threads.length; j += batchSize) {
console.log('removing batch: ' + j + '-' + Number(j + batchSize));
GmailApp[batchAction](threads.slice(j, j + batchSize));
}
console.log('done with search size: ' + threads.length);
}
9:06:53 AM Notice Execution started
9:06:54 AM Info starting with search size: 500
9:07:15 AM Error
Exception: Gmail operation not allowed.
processEmail @ Code.gs:10
batchDeleteEmail @ Code.gs:2
I ran it with 1 year at a time and it removed vast amounts of emails, but at 2022/2023 it borked, by reducing to before:2022-06-01 it worked again up until before:2022-10-01 which fails again.
Update some more results:
before:2022/09/15
9:20:25 AM Notice Execution started
9:20:27 AM Info starting with search size: 500
9:20:33 AM Info removing batch: 0-100
9:21:48 AM Info removing batch: 100-200
9:23:02 AM Info removing batch: 200-300
9:24:18 AM Info removing batch: 300-400
9:25:33 AM Info removing batch: 400-500
9:26:51 AM Info done with search size: 500
9:26:50 AM Notice Execution completed
before:2022/09/25
9:27:50 AM Notice Execution started
9:27:52 AM Info starting with search size: 500
9:27:54 AM Info removing batch: 0-100
9:29:10 AM Info removing batch: 100-200
9:30:25 AM Info removing batch: 200-300
9:31:20 AM Info removing batch: 300-400
9:31:47 AM Info removing batch: 400-500
9:32:12 AM Info done with search size: 500
9:32:11 AM Notice Execution completed
before:2022/09/28
9:35:23 AM Notice Execution started
9:35:25 AM Info starting with search size: 500
9:35:27 AM Info removing batch: 0-100
9:36:02 AM Info removing batch: 100-200
9:36:34 AM Info removing batch: 200-300
9:37:07 AM Info removing batch: 300-400
9:37:38 AM Info removing batch: 400-500
9:38:09 AM Info done with search size: 500
9:38:08 AM Notice Execution completed
before:2022/09/29:
9:38:57 AM Notice Execution started
9:38:58 AM Info starting with search size: 500
9:39:51 AM Error
Exception: Gmail operation not allowed.
processEmail @ Code.gs:10
batchDeleteEmail @ Code.gs:2
Confirmed failure on before:2022/09/29 3 times more and then ran before:2022/09/28 again which was succesfull again.
Head batchDeleteEmail Editor Oct 4, 2023, 9:41:44 AM 285.671 s Completed
Head batchDeleteEmail Editor Oct 4, 2023, 9:40:36 AM 23.158 s Failed
Head batchDeleteEmail Editor Oct 4, 2023, 9:40:04 AM 22.636 s Failed
Head batchDeleteEmail Editor Oct 4, 2023, 9:38:58 AM 54.405 s Failed
Head batchDeleteEmail Editor Oct 4, 2023, 9:35:24 AM 164.667 s Completed
Head batchDeleteEmail Editor Oct 4, 2023, 9:27:51 AM 261.174 s Completed
Head batchDeleteEmail Editor Oct 4, 2023, 9:20:26 AM 384.693 s Completed
Thanks for this code!
I added a try/catch to keep running despite the very frequent "Gmail operation not allowed" exceptions, and some random delays to try to reduce those exceptions. This gets my triggered runs to execute for the full 1800 seconds allowed, and a new one is kicked off every half an hour. I have managed to move some 8,000,000 messages to the trash in a few days.
-ben
//source: https://gist.github.com/gene1wood/0f455239490e5342fa49
var email_search = 'label:<mylabel> from:an@email.address'
function batchDeleteEmail() {
try {
processEmail(email_search, 'moveThreadsToTrash');
}
catch(error) {
Logger.log(error)
Logger.log('Re-trying...')
batchDeleteEmail();
}
}
function processEmail(search, batchAction) {
var removed = 0;
var batchSize = 100; // Process up to 100 threads at once
var searchSize = 500; // Maximum search result size is 500
var threads = GmailApp.search(search, 0, searchSize);
var rando = 0;
Logger.log('Found '+threads.length+' threads matching search: "'+search+'".')
while (threads) {
for (j = 0; j < threads.length; j += batchSize) {
rando = Math.floor(Math.random() * 3000);
Logger.log('Removing threads '+j+' to '+(j + batchSize)+'...');
GmailApp[batchAction](threads.slice(j, j + batchSize));
Logger.log('Sleeping for '+rando+' milliseconds.');
Utilities.sleep(rando);
removed += batchSize;
}
if ( ( removed % 2000 ) == 0 ) {
rando = Math.floor(Math.random() * 40000);
while ( rando < 20000 ) {
rando = Math.floor(Math.random() * 40000);
}
Logger.log('Long sleeping for '+rando+' milliseconds.');
Utilities.sleep(rando); // long sleep to try to keep api calls working...
}
threads = GmailApp.search(search, 0, searchSize);
Logger.log('Total removed so far: '+removed+'.');
Logger.log('Found '+threads.length+' more threads, removing...');
}
}
I want to create a script to delete emails from a specific bank with a transaction amount of less than some number, once a day or a couple of times per week. Any suggestions will help.
Thank you all for sharing your scripts! They have been super helpful.
I just wanted to mention that there seems to be a daily limitation now on how many times a script can be ran per day. I canceled some of the simultaneous scripts in the beginning when testing things out, so I'm not exactly sure if some of the canceled scripts are counted in the daily total or not, but the limit is somewhere between 40 to 50 scripts per day.
Note: GMAIL has a script execution limit of 6 minutes (not sure) and daily limits (positive).
function deleteAllEmailsContinuouslyEnhanced() {
var batchSize = 20; // Number of threads per batch
var maxExecutionTime = 300000; // 5 minutes (300,000 ms)
var startTime = new Date().getTime(); // Track overall start time
var batchStartTime, batchEndTime, batchDuration; // Variables for batch timing
var totalEmailsProcessed = 0; // Track total emails processed
var totalTimeSpent = 0; // Track cumulative time spent processing
while (new Date().getTime() - startTime < maxExecutionTime) {
batchStartTime = new Date().getTime(); // Record batch start time
// Fetch threads not in Trash
var threads = GmailApp.search("-in:trash", 0, batchSize);
if (threads.length === 0) {
Logger.log("All emails processed.");
Logger.log("Total emails processed: " + totalEmailsProcessed);
Logger.log("Total time spent: " + formatTime(totalTimeSpent));
Logger.log("Average time per batch: " + formatTime(totalTimeSpent / totalEmailsProcessed * batchSize));
return; // Stop if no more threads to process
}
// Move threads to Trash
GmailApp.moveThreadsToTrash(threads);
// Record batch end time and calculate duration
batchEndTime = new Date().getTime();
batchDuration = batchEndTime - batchStartTime; // Duration in milliseconds
// Update stats
totalEmailsProcessed += threads.length;
totalTimeSpent += batchDuration;
// Log details for the batch
Logger.log(
threads.length +
" threads moved to Trash. Time taken for this batch: " +
formatTime(batchDuration)
);
}
// Final stats when time limit is reached
Logger.log("Execution time limit reached. Run script again to continue.");
Logger.log("Total emails processed: " + totalEmailsProcessed);
Logger.log("Total time spent: " + formatTime(totalTimeSpent));
Logger.log("Average time per batch: " + formatTime(totalTimeSpent / totalEmailsProcessed * batchSize));
}
// Helper function to format time into human-readable format
function formatTime(milliseconds) {
var totalSeconds = Math.floor(milliseconds / 1000);
var hours = Math.floor(totalSeconds / 3600);
var minutes = Math.floor((totalSeconds % 3600) / 60);
var seconds = totalSeconds % 60;
return (hours > 0 ? hours + "h " : "") +
(minutes > 0 ? minutes + "m " : "") +
seconds + "s";
}
Execution log
12:08:04 PM Notice Execution started
12:08:05 PM Info 🔄 Starting to delete all emails...
12:08:39 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 33s
12:09:17 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 37s
12:09:52 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 34s
12:10:26 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 34s
12:11:01 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 33s
12:11:35 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 34s
12:12:10 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 34s
12:12:44 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 33s
12:13:18 PM Info 🗑️ Processed 100 emails in this batch. Time taken: 33s
12:13:19 PM Info ⏳ Execution time limit reached. Run the script again to continue.
12:13:19 PM Info 📊 Summary:
12:13:19 PM Info - Total emails processed: 900
12:13:19 PM Info - Total time spent: 5m 9s
12:13:19 PM Info - Average time per batch: 34s
12:13:18 PM Notice Execution completed
Thank you! Deleted 1.6M emails in one night.