Delete old email automatically using Google Apps Script
For the last six months I’ve had my Gmail account configured to automatically delete unarchived mail after 7 days that hasn't been starred or marked as important. And it’s been wonderful!
As of this writing I have less than 150 emails in my inbox and I have spent literally zero time organizing them.
I was able to set this up using a very basic programming script running in Google’s own Apps Script service which allows you to write Javascript to interact with many of google’s services;
#But why?
Over the last five years I’ve noticed the majority of emails hitting my personal inbox have changed. Conversations with friends and family have mostly moved to dedicated chat apps like iMessage and Hangouts. Meanwhile I’ve diligently unsubscribed from any unwanted newsletters.
What’s left is what I categorize as notification emails and they probably make up 95% of the email I receive day to day. Things like:
"Your package has shipped!"
or
"A large credit card transaction has been approved."
I don’t want to disable these notifications as I do get value from being notified in the moment. However, a few years go by and suddenly I've got tens of thousands of notification emails that either need to be archived or deleted.
What’s worse is the few emails I actually want to keep are mixed in with all of the noise described above. By switching things to retain email on an opt-in basis these problems go away with practically zero effort.
#Backup your mailbox before proceeding
Since we’re going to be deleting email in bulk using tools we don’t fully understand, it’s probably a good idea for you to backup your entire Gmail mailbox offline before proceeding. To do so you can use Google's takeout service found at https://takeout.google.com.
By default all of Google's services are pre-selected. But since we’re just interested in Gmail click Deselect All and then navigate down to Mail and select it by checking the box on the right.
You can see it’s going to archive the email into an MBox file. This can then be opened on your computer using free email clients like Apple Mail or Mozilla Thunderbird.
Then scroll down all the way to the bottom and click Next Step, select your delivery method as email, and click Create export.
A few hours later you'll get an email with a link to download your email archive as a zip file.
To verify the backup worked I recommend unzipping the archive and opening it up in the mail client of your choice. If you're using Apple mail go up to File and click Import Mailboxes. Then select Files in mbox format.
Click Continue and navigate to the folder where you unzipped the takeout download. Then inside the mail folder select the .mbox file and click Choose.
#Apps Script Dashboard
Now that we’ve verified our backup, lets’ go ahead and create the script that will automatically purge our old emails. Start by heading over to the Apps Script dashboard found at https://script.google.com.
If you look at the navigation sidebar you’re currently on the "My Projects" tab.
Click on New Project in the upper left and a new project will open in a simple integrated development environment.
In the left hand bar you can see a list of files included in this project. There should be one file present by default called "Code.gs" which is the file we have open right now. Since this project will be relatively simple it's the only file that we’ll need.
#Apps Script Triggers
Before moving on to the script itself let's take a moment to discuss Apps Script triggers. Obviously if we want our email purge script to run every day it will need to be triggered somehow.
Thankfully Apps Script provides triggers which can call project functions. However, one gotcha we'll need to deal with is that Google imposes a 20 trigger limit and completed triggers are not removed automatically. Therefore, we'll need to manually garbage collect any completed triggers.
#Purge email script
All of the code we’ll be using is available at https://gist.github.com/benbjurstrom/00cdfdb... which I've also embedded at the bottom of the article.
At the top of the script we define a constant called PAGE_SIZE
which I’ve set to 150 by default. This is the number of messages that our purge function will attempt to delete each time it’s invoked.
1// Maximum number of message threads to process per run.2var PAGE_SIZE = 150
Deleting emails is kind of a slow process and Apps Script sets a 6 minute timeout on all function calls. So if you need to delete thousands of emails you can't do that in a single execution. In my experience deleting 150 messages takes about 30 seconds making for a very safe default.
Next we define a the setPurgeTrigger
function which creates a new Apps Script trigger that will call the purge
function daily. This is the function you will need to execute manually to install the script later on once we have everything setup.
1function setPurgeTrigger() {2 ScriptApp.newTrigger('purge').timeBased().everyDays(1).create()3}
After that we define the setPurgeMoreTrigger
function which will trigger the purgeMore
function two minutes later.
1function setPurgeMoreTrigger() {2 ScriptApp.newTrigger('purgeMore')3 .timeBased()4 .at(new Date(new Date().getTime() + 1000 * 60 * 2))5 .create()6}
Next we have the removePurgeMoreTriggers
function. This is necessary because a large inbox could end up with way more than 20 triggers being set, which if not deleted, would cause errors due to the project’s 20 trigger limit. Here we're only deleting those triggers who's handler function is purgeMore
.
1function removePurgeMoreTriggers() {2 var triggers = ScriptApp.getProjectTriggers()3 for (var i = 0; i < triggers.length; i++) {4 var trigger = triggers[i]5 if (trigger.getHandlerFunction() === 'purgeMore') {6 ScriptApp.deleteTrigger(trigger)7 }8 }9}
Next is a function that will delete all triggers of any type for this project. This is the function you should manually execute if you ever want to uninstall the script to stop it from running every day.
1function removeAllTriggers() {2 var triggers = ScriptApp.getProjectTriggers()3 for (var i = 0; i < triggers.length; i++) {4 ScriptApp.deleteTrigger(triggers[i])5 }6}
After that is our purgeMore
function definition. Which is just a wrapper for our main purge
function. We need to keep purge
and purgeMore
separate so we can delete our purgeMore
more triggers without deleting the daily purge
trigger.
1function purgeMore() {2 purge()3}
Finally we have our main purge function that encapsulates all of the actual logic we’ll be using to delete old emails. The first thing the purge function does is make a call to the removePurgeMoreTriggers()
function to clean up any completed triggers.
1function purge() {2 removePurgeMoreTriggers()3 ...4}
Next we’re defining our search string and saving it to a variable called search. Then we're passing that search string to the GmailApp.search
method and getting back the matching threads. Any mail that matches this search is the mail that will be deleted by the code that comes next.
1function purge() {2 ...3 var search = 'in:inbox -in:starred -in:important older_than:' + DELETE_AFTER_DAYS + 'd'4 var threads = GmailApp.search(search, 0, PAGE_SIZE)5 ...6}
Next we check to see if the number of message threads returned matches our PAGE_SIZE
constant. If so we’ll assume there are more messages that need deleting and set a trigger for our purgeMore
function.
1function purge() {2 ...3 if (threads.length === PAGE_SIZE) {4 console.log('PAGE_SIZE exceeded. Setting a trigger to call the purgeMore function in 2 minutes.')5 setPurgeMoreTrigger()6 }7 ...8}
Finally for each GmailThread object that matched our search we’ll check to see if the most recent message in the thread is older than our DELETE_AFTER_DAYS
constant. If so we’ll move the entire thread to the trash by calling the moveToTrash
method on the object.
1function purge() { 2 ... 3 var cutoff = new Date() 4 cutoff.setDate(cutoff.getDate() - DELETE_AFTER_DAYS) 5 6 // For each thread matching our search 7 for (var i = 0; i < threads.length; i++) { 8 var thread = threads[i] 9 10 // Only delete if the newest message in the thread is older then DELETE_AFTER_DAYS11 if (thread.getLastMessageDate() < cutoff) {12 thread.moveToTrash();13 }14 }15}
#Installing the script
The next step is to manually execute our purge
function to make sure it works. You can do that by selecting the purge
function from the execution toolbar and clicking on the play button.
When you do so for the first time you’ll be asked to grant your app permission to access your email. Because your app isn’t published or reviewed by Google you’ll get a warning that You should avoid this app. But remember this is your own app so you can safely ignore the warning.
As I mentioned before, because we’re only deleting 150 emails at a time, it may take a number of cycles to delete everything. Keep an eye on the executions tab in the Apps Script dashboard to make sure the script is still re-triggering itself to grab a fresh 150 messages every couple of minutes.
For my inbox with thousands of emails this went on for quite a while with each invocation of the purgeMore
function happening two minutes after the last. Until all of the emails in my inbox older then 7 days that haven’t been starred or marked as important were deleted.
#Managing Email
One note, because we programmed our script to move matching threads to the trash, the message won’t be permanently deleted for another 30 days. So if you need to free up space right away you will need to go to the trash and permanently delete the items that have been moved there.
The rest of the time this extra 30 day buffer is nice to have. There’s been a couple of times where I forgot to star, archive, or mark something I needed as important and I luckily caught it before 30 days were up and was able to pull it from the trash.
What’s great about this system is that starred or important but unarchived email starts to stack up at the end of your inbox. Then it becomes super easy to sort through by simply archiving anything you want to keep or removing the important designation from any email you’d like to be deleted during the next daily purge.
I typically spend less then a minute sorting through these about once a month since there’s usually less than 50 emails that have accumulated.
https://gist.github.com/benbjurstrom/00cdfdb24e39c59c124e812d5effa39a