Yes, it has been a really long time since I wrote my last blog entry. I would say I’ve been busy.. but luckily, I’ve made space again to publish some technical writings / blog posts and get back into the spirit of the web as an open space to create content.



Let me get back to tonight’s topic: E-mail automation using Google Apps Scripting

So I have for the last year and a half maintained by own personal domain for e-mail which keeps my personal development projects and consulting seperate from any work email and my @gmail.com. Having an entire Google Workspace to myself just gives a lot of benefit: it allows me to tie up a lot of loose ends and authentication between the realms you are expected to juggle.

But the coolest thing with having your domain, is that you have unlimited mailboxes - and I wanted to be able to partition my emails with some labelling magic.

I called the script, APPLYDEMLABELS.gs

The Google Workspace MX delegation is the same as this blog, rod.codes . When I configured the mail I only set up one mailbox called me@rod.codes and then setup a catch-all to basically catch all emails sent to <anything>@rod.codes and deliver it to me@rod.codes.

Google has the following steps to configure the catch-all. https://support.google.com/a/answer/2685650?product_name=UnuFlow&visit_id=637861669862774545-4194007274&rd=1&src=supportwidget0#zippy=%2Cset-up-a-catch-all-address and I’ve provided images of my configuration and the advice at the bottom of this document (if you are interested).

This is how it works

On Initialization, a global dictionary (LABEL_DICT) of all labels are retrieved for this mailbox.

First, I created a Gmail Rule as follows that whenever an email not destinated for either me@rod.codes or my older gmail.com email address, it adds the unicode label “🆕” (indicatorLabelString).

Note the negative operator in front of the email address

Second, the AppScript basically filters by that Label and then either attaches a label (if it exists in the global LABEL_DICT) or creates it and then attaches the label to the mail.

The indicatorLabelString 🆕 is then removed and the enumeration continues until there are no more emails tagged with 🆕.


The App Script Code

// Configure this trigger to run often
// (*how* often depends on the desired response time *and* how willing you are to risk hitting Google Apps Script’s rate limits;
// 10 minutes is good enough for me generally)

var LABEL_DICT = {}
var EMAIL_WORKSPACE_DOMAIN = "" // do not include the @ sign, so for XYZ@joebloggs.com, put joebloggs.com

function getUserLabels() {
  GmailApp.getUserLabels().map((value) => {
    LABEL_DICT[value.getName().toString()] = value;
    return { "label": value, "name": value.getName() };
  });

}

function getLabelByName(name) {
  return LABEL_DICT.hasOwnProperty(name) ? LABEL_DICT[name] : undefined;
};

function createLabelIfNotExists(name) {
  var newLabel = null;
  var label = getLabelByName(name);
  if (!label) {
    newLabel = GmailApp.createLabel(name.toString());
    LABEL_DICT[name.toString()] = newLabel;
    label = newLabel;
  }
  return label;
};

function getLabelByEmailType(emailAddress) {
  if (!emailAddress) {
    return;
  }
  let emailLower = emailAddress.toString().toLowerCase();
  if (emailLower.toString().endsWith(EMAIL_WORKSPACE_DOMAIN)) {
    let futureLabel = emailLower.toString().split("@");
    return createLabelIfNotExists("📬 " + futureLabel[0]);
  };
};

function triggerScriptForEmail() {
  getUserLabels();
  let indicatorLabelString = "🆕";
  var LABEL_TO_REMOVE = LABEL_DICT[indicatorLabelString];
  var GET_THREADS_AT_TIME = 5;
  var threads = new Array();
  while (Array.isArray(threads)) {
    Logger.log(`Calling getUserLabelByName = ${indicatorLabelString}`);
    threads = GmailApp.getUserLabelByName(indicatorLabelString).getThreads(0, GET_THREADS_AT_TIME);
    Logger.log(`Thread length == ${threads.length}`);
    if (threads.length == 0) {
      Logger.log('All gone');
      return;
    }
    threads.forEach((thread) => {
      var messages = GmailApp.getMessagesForThread(thread);
      Logger.log(`${messages}`);
      var emailAddresses = messages.map((msg) => (msg.getTo ? [msg.getTo()] : [])).map((value) => value.toString().match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi));
      [...new Set(emailAddresses)].forEach((recipient) => {
        Logger.log(`Checking email address ${recipient}`);
        var labelToAttach = getLabelByEmailType(recipient);
        if (labelToAttach) {
          Logger.log(`Attaching label ${labelToAttach.getName()}`);
          labelToAttach.addToThread(thread);
        }

      });

      Logger.log(`Removing indicator label`);
      LABEL_TO_REMOVE.removeFromThread(thread);
      return null;
    })
  }

}
function doGet(e) {
  triggerScriptForEmail();
  return `OK`;
}

How to configure the Google Workspace Catch-all

At the time of writing this blog post, the current steps were defined.

Interesting, I ended up with something different to that under https://admin.google.com/ac/apps/gmail/defaultrouting.

This is how my configuration looks:




Regardless, I believe the outcome is the same is similar. If not, let me know…