Managing an E-mail Account - Google Apps Script for Beginners (2014)

Google Apps Script for Beginners

Chapter 3. Managing an E-mail Account

One of the reasons I began using Google Drive and Google Apps Script is that I had a Gmail account for my personal mail service, and the school I work for decided to get Google Apps for educational accounts in 2009.

This choice was probably made because not only did it seem complete and efficient, but also because it was free.

If you are interested in Google Apps Script, you are most probably a Gmail user too, so this chapter might interest you a lot.

The Gmail Browser UI has a lot of built-in tools that work pretty well; you can search, sort, archive, and delete about everything you want in a couple of clicks; you can create filters, labels, and automatically forward mails very easily too; and to be honest with you, you won't be able to do a lot more with Google Apps Script!

Bad news?

Not sure. However, there are two things that you will be able to control: how and when to use the built-in tools offered by the Google Apps Script.

Let's take a simple example; you want to set up a filter that sets a label on every message coming from your boss. However, you also want to keep an archive of it in a document along with a link to the attachments stored in your drive to be able to quickly search any reference he might have sent you. You would prefer that his e-mail messages remain in your main mailbox for the whole day to be sure that you don't forget to read them.

How would you achieve this workflow in Gmail? Could you do it in your mail-client application/program? Not sure of either? Well, a script can do this quite easily and we will take that use case as a practical example.

We need the following:

· A way to know who the sender of the mail is (

· A way to get the message content as text (

· A place to store that text (

· A way to get the attachments and store them in a drive folder (

· A link to each of these files at hand near the message (

· A tool to set labels (

· A tool to create a label when it's missing (

· A timer or a clock to filter the messages at night when you sleep, so they are clearly visible during your working day (

Note that all the links in the preceding list take you to the relevant services and methods that we shall use; you may want to read it beforehand or keep reading and get to it later on. But, in any case, I'd recommend that you take the time to read it thoroughly so that everything becomes clear and looks logical to you. Understanding exactly what we do is, in my humble opinion, the only way to learn something and remember it (just saying that I'm certainly not innovating).

I could have presented a piece of code that does all this in one go and tried to explain it step-by-step, but I'm afraid that it would have become confusing and, most of all, I'm not sure if this workflow will be useful for anyone as it is. So, let's try and solve each piece of the puzzle separately and assemble them at the end of this chapter to get a fully-working demo.

Retrieving Gmail data

After a couple of years, a normal user's Gmail account is often a compilation of thousands of threads and a lot more messages of all kinds. Some have labels, some remain unread sometimes; I even know people who never delete or archive anything and it can easily become a real mess.

Let us assume you are not one of these people and that you already have a couple of labels to sort your messages in a clever way.

The Gmail app is a service that provides methods to retrieve messages in many ways. We can get all threads without any criteria, threads with a specific label, starred threads, and a few other threads but in this first example, we will focus on our inbox threads (

If you look at the documentation, you'll find two occurrences of the getInboxThreads() method; one has no parameter and the other has two parameters—start and max—to retrieve partial data so that we can process them in small batches.

The reason for this is that it takes some time to retrieve data from Gmail and if we have a lot of messages, it could easily take too long.


I haven't yet mentioned a very important aspect of Google Apps Script: the execution time of any script is limited to about five minutes, no matter what you are trying to do or which Google account you have. After five minutes, the execution will stop and you will get a message: Exceeded maximum execution time.

Knowing this constraint, we will limit our script to as few threads as possible for now, so we don't exceed any limit. The getInboxThreads(start, max) method with parameters will be helpful.

A bit later, when we need to process a lot of data, we'll discover how to handle this situation (see the Execution time versus time limit section).

Once we have a thread object (, we can apply a method to get a message object ( Remember that JavaScript is an object-oriented language; each object has properties, and the autocomplete feature (and the documentation also, of course) will show us all the available methods to get these properties.

We want to get the message's sender, text, and the attachments. The following code shows you how it works in a function that gets the most recent message to your inbox and sends you back its content as a PDF file for the body, along with a copy of the attachment in its original format:

function getAttachAndBody(){

var firstThread = GmailApp.getInboxThreads(0,1)[0];

// get only the most recent thread

var message = firstThread.getMessages()[0];

// get the first message in that thread,getMessages() returns an array of message objects

var sender = message.getFrom();

// get the sender email

var attach = message.getAttachments();

// get the attachment

var body = message.getBody();

//body as a string

var bodyDocHtml = DocsList.createFile('body.html', body, "text/html");

// create a html file with the body content

var bodyId=bodyDocHtml.getId();// get its id

var bodyDocPdf = bodyDocHtml.getAs('application/pdf').getBytes();

// convert this html in pdf

var bodyToSend = {fileName: 'body.pdf',content:bodyDocPdf, mimeType:'application/pdf'};

// bodyToSend is the object containing the pdf


// if attachment in original message send it along with the msg body to the current user.

MailApp.sendEmail(Session.getEffectiveUser().getEmail(), 'transfer email text received from '+sender+' as pdf + body & attachment in attachment', 'see attachments',



MailApp.sendEmail(Session.getEffectiveUser().getEmail(), 'transfer email text received from '+sender+' as pdf in attachment', 'see attachments', {attachments:[bodyToSend]});

// if no attachment just send the pdf version of message text


// delete the html message from your drive as it was a temporary item we don't need anymore.


In this simple example, we intentionally neglected a few parameters to keep it as simple as possible; we considered only the first message of the first thread and assumed that there was only one attached file in this message. We'll see later how to develop the script, so as to be more close to real situations.

We also made use of a service we haven't mention yet, DocsList, which is still marked as experimental and is probably going to be replaced by the more recent Drive service, which offers similar methods but uses another approach in some aspects. This will be examined in the next chapter.

Now, let us focus on one of the most useful Gmail features—labels.

Creating labels

Labels in Gmail can be considered as an equivalent to folders; they allow us to sort messages in as many categories as we want, to make them easier to find when we need to.

There is already, by default, a spam filter in your Gmail account and a newly introduced feature that shows promotions, social networks, and forum threads separately, but we might want to be more selective and have separate labels for all our mail activities.

As far as I am concerned, I like that every thread has a label. My computer (and its related online data) is the only thing that I keep tidy; too bad that's not the the case with my desktop.

So, I created labels for every possible type of e-mails: Facebook, Stack Overflow forum, computer spare parts distributors, audio equipment ads, and so on.

I might have done that using Gmail filters of course, or using my favorite mail-client application, but I did it with Google Apps Script; go figure out why.

Creating a label is very straightforward, as shown in the documentation (

GmailApp.createLabel('Label Name');

If we want to check whether a label already exists before creating it, we can write a more sophisticated function as follows (along with a function to test it):

function createLabel(labelName){

if( ! GmailApp.getUserLabelByName(labelName)){



var info = 'Label "'+labelName+'" now exists and has '+GmailApp.getUserLabelByName(labelName).getThreads().length

+' threads, '+GmailApp.getUserLabelByName(labelName).getUnreadCount()+' unread threads' ;// prepare a readable message

return info


function testLabelNameCreation(){

Logger.log(createLabel('testLabelTodelete'));// open the logger to read the message


This is all very simple and it wouldn't be too hard for you to adapt it to your needs. If you expect to create a lot of labels, use the preceding code in a loop and define your label names in an array of strings:

function createManyLabels(){

var labels = ['test1','test2','test3','test4'];

for(var n in labels){




Be careful when you use this script because it can take some time to execute; the preceding example with four labels took 15 seconds to run, so don't try to create hundreds of labels at once!

If you have tried this code and want to delete the test labels, you can use the delete method of the GmailLabel class, I'll let you find how to change the preceding code to delete the labels it created (

Counting, sorting, archiving, and deleting e-mails and threads

We have seen in the earlier examples that it is quite easy to manipulate threads and messages with regard to labels.

Once we get a thread object, we can do what we want with it. A quick look at the documentation shows the many methods available but as I have already mentioned, these operations might take some time and we may easily face issues with the maximum execution time limit of five minutes.

Execution time versus time limit

The solution to this is to proceed with relatively small bunches of threads. A careful approach is to treat 100 threads at a time and continue as long as we need or until it is finished.

Google Apps Script has all the tools we need:

· A programmable time trigger to automate the process

· A convenient place to store the number of processed items

· A way to delete the trigger when it is finished

The following code is an example that will examine all your e-mails and add labels inside every thread according to the thread's original label; it will send you an e-mail with a logger view of its activity (an activity report) and the current progress of the task and stop automatically when everything is done. Then, you'll receive a last mail to warn you that everything is over.

The script is fairly long but it has (hopefully) enough comments in every step to help you understand how it works. It will of course ask for a few authorizations: Gmail, Script Properties, and session identity:

function inboxLabeller() {

// check if the script runs for the first time or not,

// if so, create the trigger and ScriptProperties the script will use

// a start index and a total counter for processed items

// else continue the task


ScriptProperties.setProperties({'threadStart':0, 'itemsprocessed':0, 'notFinished':true});



// initialize all variables when we start a new task, "notFinished" is the main loop condition

var items = Number(ScriptProperties.getProperty('itemsprocessed'));

var tStart = Number(ScriptProperties.getProperty('threadStart'));

var notFinishedinished = ScriptProperties.getProperty('notFinished')=='true';

Logger.clear();// be sure the Logger is empty

while (notFinishedinished){ // the main loop

var threads = GmailApp.getInboxThreads(tStart,100);

getAds(tStart);// check if it's an ad and move it to "Ads" folder if necessary

Logger.log('Number of threads='+Number(tStart+threads.length));

if(threads.length==0){ // if no threads anymore the we're done

notFinishedinished=false; // reset the flag

break;//and exit the loop



var mCount = threads[t].getMessageCount();// how many messages ?

var mSubject = threads[t].getFirstMessageSubject();

var labels = threads[t].getLabels();

var labelsNames = '';

for(var l in labels){labelsNames+=labels[l].getName()};

// get all the labels even if more than one for each message and write this in the logger

Logger.log('subject '+mSubject+' has '+mCount+' msgs with labels '+labelsNames);

for(var l in labels){

// assign the label to every message in the thread




tStart = tStart+100;// prepare for next execution

items = items+100;// to start from the next bunch

ScriptProperties.setProperties({'threadStart':tStart, 'itemsprocessed':items});// store this value



if(notFinishedinished){ ;// send an intermediate activity report along with the Logger's content

GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), 'inboxLabeller progress report', 'Still working, '+items+' processed \n - see logger below \n \n'+Logger.getLog());


// if the task has completed, send a final report mail and delete ScriptProperties and trigger

GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), 'inboxLabeller End report', 'Job completed : '+items+' processed');


var trigger = ScriptApp.getProjectTriggers()[0];




function getAds(tStart){

var pub = GmailApp.getUserLabelByName('Ads');


pub = GmailApp.createLabel('Ads');// if you don't have an "Ads" label then create it


var threads ='category:promotions', tStart, 100);// check the category Gmail added to the thread

for(var n in threads){

var tnr = Number(tStart)+Number(n);

threads[n].addLabel(Ads);// move every message in the Ads folder/label

Logger.log('threads n° '+tnr+' = '+threads[n].getFirstMessageSubject()+' has '+threads[n].getMessageCount()+' messages ending on '+threads[n].getLastMessageDate());

// show what we have done explicitly



The following is a view of the message that the script will send every 10 minutes:

Execution time versus time limit

Creating e-mail messages

There are two Google services available to send e-mails: Gmail app and MailApp. The latter has only methods to send e-mails, while we have seen that Gmail app can do much more! When simply sending a message, we can use either of them interchangeably; I used to type MailApp more frequently but it is nothing more than a personal habit.

The basic process is really simple as described in the documentation (example taken from Google documentation page):

// Send an email with a file from Google Drive attached as a PDF.

var file = DriveApp.getFileById('1234567890abcdefghijklmnopqrstuvwxyz');

GmailApp.sendEmail('', 'Attachment example', 'Please see the attached file.', {

attachments: [file.getAs(MimeType.PDF)],

name: 'Automatic Emailer Script'


In the optional parameters, you can use some interesting values as follows:

· Attachments: These are an array of blobs (a blob is a data-interchange object for Apps Script services:

· Cc, Bcc, and Reply-To

· Name: This is the sender name as it will appear on screen

· Inline images

· A no reply flag: This will prevent the mail recipient from replying to the mail

· The HTML content of the message: This can be used as a value as the default content is plain text

To illustrate the HTML format, I'd suggest a code that we could use in our form example from the previous chapter by adding it to the form response spreadsheet. It will send us a report with the spreadsheet's content in a pleasant table format. We will provide a plain text version as well for people allergic to HTML-formatted e-mails.

I built the code using two separate functions—one to get data from the spreadsheet and send the mail, and a second one to actually compose the message body so that it will be much easier to re-use in another context:

function sendReport() {

var ss = SpreadsheetApp.getActiveSpreadsheet();

var formResp = ss.getSheetByName('Form Responses');// get the sheet that contains data

var data = formResp.getDataRange().getValues();// get all data in an array of arrays aka 2D array)

var htmlContent = createMsg(data)[0];// the function returns 2 results in an array, get the first one

var textBody = createMsg(data)[1];// get the second one (text only, tab separated items

var htmlMsgObject = {'htmlBody' : htmlContent};// create an object to use as optional parameter below

GmailApp.sendEmail(Session.getActiveUser().getEmail(),'Daily report of form responses',textBody,htmlMsgObject);// send to the active user


function createMsg(dataArray){

var color='#CCF';// a blueish color for headers

var textMsg = 'Overview of last form responses :\n\n';// text title

var html = '<b>Overview of last form responses :</b><br><br><table style="border-collapse:collapse;"border = 1 cellpadding = 5 >';// html tags with styles

var table = '';

for(var tt=0;tt<dataArray[0].length;tt++){

table+= '<th bgcolor="'+color+'" cellpadding=5>'+dataArray[0][tt]+'</th>';


};// first loop is for the headers

color='#FFC';// yellowish color for the table content


table+='</th><tr valign="middle" bgcolor="'+color+'" cellpadding=5>';

for(var n=1;n<dataArray.length;n++){

var row = dataArray[n];


table+= '<td>'+row[tt]+'</td>';


};// second loop is for table items

table+='</tr><tr valign="middle" bgcolor="'+color+'" cellpadding=5>' ;




var msghtml = html+table;


Logger.log(msghtml);// check in the logger (optional)

Logger.log(textMsg);// check in the logger (optional)

return [msghtml,textMsg];// return 2 values in an array


The following screenshot shows an overview of last form responses:

Creating e-mail messages

Let's admit that it looks more professional, doesn't it?

Automating a complete workflow

Now that we have discovered the most important methods available (not all of them, that's what the documentation is for), we can try and get our hands on the workflow that we suggested in the introductory example.

Just a reminder of that example is as follows:

You want to set up a filter that sets a label on every message coming from your boss, but you also want to keep an archive of it in a document along with a link to the attachments stored in your drive to be able to quickly search any reference he might have sent you. You would prefer that his e-mail messages remain in your main mailbox the whole day to be sure that you don't forget to read them.

I'm warning you, this code is a bit long.

When I imagined the example workflow, I thought it could be quite simple but some of the features need a lot of code lines, particularly the part that shows the attachment data in a table. I also wanted it to be simple to set up, so it creates the folders, the labels, the trigger to make it automatic, the filter to avoid double entries in log data; in one word, everything it needs to run in one single click.

This has an advantage; the code is not so simple but I have commented every part of it, so anyone can follow its logic.

Here it is. Starting with global variable definitions, there are style definitions too, which are shown at the end of the following code:

/* Global variables declaration

/ You should define the email address in this first variable (theEmailOfYourBoss)

/ other parameters and variable are also defined as global variables

/ like the folderName and Gmail Label,

/ these can be customized to your need below

/ code should be embedded in a document.


var theEmailOfYourBoss = '';

var boss = ScriptProperties.getProperty('bossEmail');

if(boss == null){ // test if email already exists



var trigger = ScriptProperties.getProperty('bossEmailTrigger');

if(trigger == null){ // test if Trigger already exists

var trigger = ScriptApp.newTrigger('takeCareOfTheseEmails').timeBased().everyDays(1).atHour(2).create();

ScriptProperties.setProperty('bossEmailTrigger',trigger.getUniqueId());// save the trigger ID


var rootFolder = DriveApp.getRootFolder();

var Bossmails = GmailApp.getUserLabelByName('Boss-Emails');


// if you don't have an 'Boss-Emails' label then create it

pub = GmailApp.createLabel('Boss-Emails');


var BossFolder = DriveApp.getFoldersByName('Boss_Files');


BossFolder =;


BossFolder = DriveApp.createFolder('Boss_Files');


// function starts here

function takeCareOfTheseEmails(){

var doc = DocumentApp.getActiveDocument();// this document

var body = doc.getBody();

var inBoxThreads = GmailApp.getInboxThreads(0,100);

// get the 100 most recent threads and loop into it

for(var n=0;n<inBoxThreads.length;n++){

var labelName = '';

for(var l in inBoxThreads[n].getLabels()){labelName+=inBoxThreads[n].getLabels()[l].getName()};

Logger.log(labelName+' labelName.indexOf(Boss-Emails) = '+labelName.indexOf('Boss-Emails')) ;


var reject = false; // just a flag we use in the for loops

// get the messages in each thread and store attachments in an array

var messages = inBoxThreads[n].getMessages();

for(var m=0;m<messages.length;m++){

var attach = [];

// get the sender email and check if it comes from the boss

var sender = messages[m].getFrom();

if(sender.indexOf(boss)==-1){reject = true};

if(reject){n++; break };


// get message date, subject, attachments and body

var msgDate = Utilities.formatDate(messages[m].getDate(), Session.getTimeZone(),'MM-dd-yyyy @HH:mm');

var msgSubject = messages[m].getSubject();

var attachments = messages[m].getAttachments();

var msgBody = messages[m].getBody();

var msgBodyDocHtml = DriveApp.createFile('msg received on '+msgDate,msgBody,MimeType.HTML);

// move the file to its folder and remove it from 'my drive'



// get the corresponding blob if we want to send it as attachment, get the thumbnail if exists

var bodyDocUrl=msgBodyDocHtml.getUrl();

var msgBodyBlob = msgBodyDocHtml.getBlob();

var msgBodyDocThumb = msgBodyDocHtml.getThumbnail();

// retrieve a text version of the message and keep only the 400 first characters

var textSummary = getTextFromHtml(msgBody);

var length = (textSummary.length <400 ? textSummary.length : 400);

textSummary = textSummary.substr(0,length)+'..............';

var attSize = 0;

var attName = 'no attachment';

var attUrl = '---';

// define a header for attachment display

attach.push(['attachment name','attachment Size','attachment url']);

for(var a in attachments){

// get the size, name and URL of the attachment

attSize = attachments[a].getSize()+' bytes';

attName = '/Boss_Files/'+attachments[a].getName();

attUrl = BossFolder.createFile(attachments[a]).getUrl();

// store file/name and size in an array



body.appendParagraph('Thread Nr '+n+', Message nr '+m+'\rReceived on '+msgDate+' from '

+sender.split('<')[0]+' with Subject : "'+msgSubject+'"\rStarting with : \r'+textSummary).setAttributes(bodyStyle);

// show a link to the message stored in drive and show thumbnail & attachment name if exist

body.appendParagraph('View here').setLinkUrl(bodyDocUrl);



var table = body.appendTable(attach).setAttributes(tableStyle).setBorderColor('#aaaaaa');

for(var r = 0 ; r<table.getNumRows() ; r++){

var row = table.getRow(r);

for(var c = 0 ; c < row.getNumCells(); c++){

var cell = row.getCell(c).setAttributes(cellStyle);







// flush all changes and save the doc



// this function borrowed from stackoverflow contributor Corey G

function getTextFromHtml(html) {

return getTextFromNode(Xml.parse(html, true).getElement());


function getTextFromNode(x) {

switch(x.toString()) {

case 'XmlText': return x.toXmlString();

case 'XmlElement': return x.getNodes().map(getTextFromNode).join('');

default: return '';



// style definitions as global variables

var tableStyle = {};// define a tyle for table

tableStyle[DocumentApp.Attribute.FONT_SIZE] = 8;

tableStyle[DocumentApp.Attribute.FONT_FAMILY] =DocumentApp.FontFamily.ARIAL_NARROW;

tableStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = "#FF5555";

tableStyle[DocumentApp.Attribute.BORDER_WIDTH] = 0.5;

var bodyStyle = {};// define a style for body

bodyStyle[DocumentApp.Attribute.FONT_SIZE] = 10;

bodyStyle[DocumentApp.Attribute.FONT_FAMILY] = DocumentApp.FontFamily.ARIAL;

bodyStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = "#555555";

bodyStyle[DocumentApp.Attribute.MARGIN_LEFT] = 20;

bodyStyle[DocumentApp.Attribute.MARGIN_BOTTOM] = 20;

bodyStyle[DocumentApp.Attribute.MARGIN_RIGHT] = 20;

bodyStyle[DocumentApp.Attribute.MARGIN_TOP] = 20;

var cellStyle = {};// define a style for cells individually

cellStyle[DocumentApp.Attribute.PADDING_BOTTOM] = 0;

cellStyle[DocumentApp.Attribute.PADDING_TOP] = 5;

cellStyle[DocumentApp.Attribute.PADDING_LEFT] = 5;

cellStyle[DocumentApp.Attribute.PADDING_RIGHT] = 0;

cellStyle[DocumentApp.Attribute.HEIGHT] = 15;

// end of file

This code has to be pasted in a new text document script's editor (this was also a motivation behind writing this example; now we know that the text document also has scripts) and run once.


Before running the code, please choose a valid e-mail address for the theEmailOfYourBoss variable (first uncommented line in the preceding code).

It will ask for the usual authorizations and begin to work.

Every night, between 2 a.m. and 3 a.m., it will execute again and examine the 100 most recent threads; if ever it's not enough for your use case, you can change it easily in line 24 by changing 100 to a bigger value:

var inBoxThreads = GmailApp.getInboxThreads(0,100);// get the 100 most recent threads

Just keep in mind that the total execution time cannot exceed five minutes, so don't ask for 5000 threads; I'm afraid they won't work and if you receive that many e-mails, I guess you should quit your job—too much pressure for sure!

Automating a complete workflow


In this chapter, we saw that the Gmail service offers an impressive range of methods that allow for almost any possible manipulation of your mailbox content.

Anything that you can do manually but would take a long time to achieve can be done using script, and as this boss' e-mail example shows you, you can even imagine other scenarios without difficulty. Let Google Apps Script do your work for you.

This latter example also gave us an opportunity to use the document service and this will be the main point of interest in the next chapter.