Delayed Gmail Filtering
I subscribe to a lot of email newsletters (Vox, We're Here, Tom Scott, Money Stuff and Heatmap News 1 Their podcast Shift Key is one of the best listens around climate change and decarbonization (albeit with a US-centric POV). among many others) all of which I enjoy...but maybe don't always read. I don't want to unsubscribe because I do read them, but I don't need Dropout.tv's episode announcement from three weeks ago floating around in my inbox. Ideally if I don't get to them they could quietly be purged in the background, but the default Gmail and Apple mail filters don't support delayed filtering. But Google Scripts does!
First go to https://script.google.com/ and create a new project 2 At least until the graveyard claims this too. . The code is split into three sections. First, a definition of "rules" corresponding to the specific email addresses that I want automatically filtered (and some optional modifiers like subject filtering or a longer grace period).
/**
* List of rules
*
* @typedef Array<string | {email: string, stale?: int, subject?: string}> Rules
*
* @constant {Rules}
*/
const RULES = [
// Simplest email format
'editors@heatmap.news',
// Will only apply if the subject contains a string
{'email': 'venmo@venmo.com', 'subject': 'transaction history'},
// By default it will archive emails after 14 days, but you can extend this
{'email': 'PayPal@emails.paypal.com', 'stale': 28},
];
The second chunk of code handles processing the rules (O(1)
lookups helps the script not time out) and validating whether or not a given message matches any of the rules.
/**
* Turn the shorthand rules list into a dict for quicker lookups
*
* @typedef {{stale?: int, subject?: string}} Rule
*
* @param {Rules} rules
* @return {Object.<string, Rule>}
*/
function preProcessRules(rules) {
var newRules = {};
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (typeof rule === 'string') {
newRules[rule.toLowerCase()] = {};
} else {
var email = rule['email'];
delete rule.email;
newRules[email.toLowerCase()] = rule;
}
}
return newRules;
}
/**
* Given a list of rules (indexed on from email), check if the message matches
*
* @param {Object.<string, Rule>} rules
* @param {GmailApp.GmailMessage} message
* @return {boolean}
*/
function matchesRule(rules, message) {
var fromEmail = message.getFrom().toLowerCase().match(/<([^>]+)>/);
if (fromEmail != null) {
fromEmail = fromEmail[1];
} else {
fromEmail = message.getFrom();
}
if (!(fromEmail in rules)) {
return false;
}
var rule = rules[fromEmail];
// Matches the subject contains
if ('subject' in rule) {
if (!message.getSubject().includes(rule['subject'])) {
return false;
}
// How old the email can be
} else if ('stale' in rule) {
var threshold = new Date();
threshold.setDate(threshold.getDate() - rule['stale']);
if (message.getDate() >= threshold) {
return false;
}
}
return true;
}
And finally there's the main function that loads the emails in your inbox that are older than 14 days,
3
Note that the inbox search is implicitly capped at 500. If you’re working through a backlog you can manually paginate with before:
.
checks if they match a rule, and if so marks it as read and moves it to the archive.
/**
* Actually does the processing. Looks at emails that are in your inbox and are
* older than 14 days, and archives/marks as read those that pass the rules
*/
function autoProcessOldEmails() {
var threads = GmailApp.search('in:inbox older_than:14d');
var autoArchiveRules = preProcessRules(RULES);
console.log('Monitoring threads: ', threads.length);
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
if (messages.length > 1) {
// For now just skip threads
continue;
}
// If it matches then print out the message, and then archive
if (matchesRule(autoArchiveRules, messages[0])) {
console.log(`Marking as read [${new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit'
}).format(messages[0].getDate())}] ${messages[0].getFrom()}: ${messages[0].getSubject()}`);
threads[i].markRead();
threads[i].moveToArchive();
} else {
// Every so often just give a progress check in
if (i % 30 === 0) {
console.log('Ignoring check in', messages[0].getFrom(), messages[0].getSubject());
}
}
}
}
Then we just need to set it up to run regularly. Under the "Triggers" tab on the left you can select "Add Trigger". Make sure that the function is set to autoProcessOldEmails
and choose "Time-driven" on a "Day timer" at whatever time you think you won't notice. And you're done!
4
Wow, it's so easy to handle newsletters, why not subscribe to the blog and get notified when I make new posts?
-
Their podcast Shift Key is one of the best listens around climate change and decarbonization (albeit with a US-centric POV). ↩︎
-
At least until the graveyard claims this too. ↩︎
-
Note that the inbox search is implicitly capped at 500. If you’re working through a backlog you can manually paginate with
before:
. ↩︎ -
Wow, it's so easy to handle newsletters, why not subscribe to the blog and get notified when I make new posts? ↩︎