Last active
July 26, 2025 03:09
-
-
Save mariduv/86fa75c95a2441c03170af7ea8ec2681 to your computer and use it in GitHub Desktop.
Google Apps Script for improving my gmail experience
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
// use Google Apps Script, server-side automation for Google Workspace | |
// accounts, to archive messages that are read and not flagged, after 30 days. | |
// | |
// Add a trigger that runs `archiveInbox()` overnight, and it will loop | |
// archiving up to 50 threads at a time, until no matches for `archive_filter` | |
// are found. | |
"use strict"; | |
const archive_filter = "in:inbox is:read -is:starred older_than:30d"; | |
function archiveInbox() { | |
let count = 0; | |
do { | |
count = archiveInboxIncremental(); | |
} while (count !== 0); | |
} | |
function archiveInboxIncremental() { | |
let threads = GmailApp.search(archive_filter, 0, 50); | |
GmailApp.moveThreadsToArchive(threads); | |
Logger.log("Archived %s threads.", threads.length); | |
return threads.length; | |
} |
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
// My configuration: I'm filing messages that match | |
// 'from:([email protected]) subject:(-"[bugzid:" "Fogbugz Case")' | |
// into Sort/fogbugz-new, skipping inbox. `search` returns just what's | |
// currently in the label, and `work_label` is the internal name for this | |
// label. Messages get a simple thread reference, adjusted subject, and are | |
// reinserted set as unread and in inbox. A simple munge is applied to | |
// Message-ID to keep gmail from regarding it as a duplicate before the | |
// original is removed. | |
// This was later adjusted for the change to the name "manuscript" and also to | |
// flag messages on tickets assigned to me. | |
"use strict"; | |
var search = "label:sort-fogbugz-new"; | |
var work_label = "Label_19"; | |
var fogbugz_me = "Meredith"; | |
// set to trigger every minute | |
function threadFogBugz() { | |
var msgList = Gmail.Users.Messages.list("me", { q: search }); | |
if (msgList.resultSizeEstimate === 0) return; | |
Logger.log("Found %s messages", msgList.messages.length); | |
for (var i in msgList.messages) { | |
checkAndRewrite(msgList.messages[i].id); | |
} | |
} | |
function checkAndRewrite(msgid) { | |
var message = Gmail.Users.Messages.get("me", msgid, { format: "raw" }); | |
var labels = editLabels(message.labelIds); | |
var split_msg = header_split(ab_to_string(message.raw)); | |
var headers = split_msg[0]; | |
var body = split_msg[1]; | |
if (isThreaded(headers)) return; | |
var bugzid = findBugzid(headers); | |
if (!bugzid) return; | |
var ref_msgid = "<bugzid@" + bugzid + ">"; | |
var new_message = [ | |
headers | |
.replace(/^Message-ID: <([^>]+)>/im, "Message-ID: <$1-->") | |
.replace( | |
/^Subject: +Manuscript \(Case \d+\)/im, | |
"Subject: [bugzid:" + bugzid + "]", | |
), | |
"References: " + ref_msgid, | |
"In-Reply-To: " + ref_msgid, | |
"", | |
body, | |
].join("\r\n"); | |
var new_labels = editLabels(message.labelIds); | |
if (isAssignedToMe(body)) new_labels.push("IMPORTANT", "STARRED"); | |
var resp = Gmail.Users.Messages.insert( | |
{ | |
labelIds: new_labels, | |
threadId: message.threadId, | |
}, | |
"me", | |
Utilities.newBlob(new_message, "message/rfc822"), | |
{ uploadType: "multipart", internalDateSource: "dateHeader" }, | |
); | |
if (resp && resp.id) { | |
Gmail.Users.Messages.remove("me", msgid); | |
} | |
} | |
function editLabels(labels) { | |
return labels | |
.filter(function (label) { | |
return label !== work_label; | |
}) | |
.concat(["INBOX", "UNREAD"]); | |
} | |
function isThreaded(headers) { | |
return headers.match(/^References:|^In-Reply-To:/im) !== null; | |
} | |
function isAssignedToMe(body) { | |
return body.match(new RegExp("^Assigned To:\\s+" + fogbugz_me + "$", "m")) !== | |
null; | |
} | |
function findBugzid(headers) { | |
var re = /^Subject: +Manuscript \(Case (\d+)\)/im; | |
var match; | |
if ((match = re.exec(headers)) !== null) { | |
return match[1]; | |
} | |
return null; | |
} | |
function header_split(message) { | |
var split = message.indexOf("\r\n\r\n"); | |
return [message.slice(0, split), message.slice(split + 4)]; | |
} | |
function ab_to_string(ab) { | |
return Utilities.newBlob(ab).getDataAsString(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I've been using these scripts for a few years now and wanted to publish them beyond sharing with coworkers. The first just archives read and unflagged messages out of my inbox after 30 days, so the inbox folder in IMAP doesn't grow forever. The second rewrites messages from FogBugz tickets to actually thread together (flatly), have a less verbose subject line, and flag them if it's actually assigned to me at the time.