5
votes

I've been fooling around with GAS for a month or so now, and I've become fairly familiar with using batch operations to read/write to/from spreadsheets (e.g. getValues(), setValues()). However, I'm currently writing a script that pulls a sizable amount of data out of Gmail using class GmailApp, my code is running very slowly (and even timing out), and I can't seem to figure out how to use batch operations for what I'm trying to do. Here's my code thus far (with the email address and name changed):

 function fetchEmails(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var threads = GmailApp.search('in: label:searchedLabel');
  var messages = new Array();

  function Email(message){
  this.date = new Date(message.getDate());
  this.body = message.getBody();
  }

  for(var i=0;i<threads.length;i++){
    for(var j=0;j<threads[i].getMessageCount();j++){
      if(threads[i].getMessages()[j].getFrom()=="firstName lastName <[email protected]>"){
       var message = new Email(threads[i].getMessages()[j]);
       messages.push(message);
      }
    }
  }  
}

As you can see, I'm querying my email for all threads with the given label, making an object constructor for a custom Email object (which will have the body and date of an email as properties). Then I'm looping through each thread and when a given email matches the sender I'm looking for, I create an instance of the Email object for that email and place that Email object into an array. The goal is that in the end I'll have an array of Email objects that are all from my desired sender. However as you've probably noticed the code calls Google's APIs way too often, but I can't seem to figure out batch operations for interfacing with Gmail. Any ideas? Thanks so much.

1
Only a doubt. In the 2nd loop (for (var j=...)), you retrieve all messages for i-th thread (i.e. threads[i].getMessages()). Try to move this part before the 2nd loop, i.e. something like var lstMsg = thread[i].getMessages()... More over you retrieve the messages twice. It can be the slowness cause. - megabyte1024
Oops! There's a typo in my code, but the question still stands: In the query the "in:" shouldn't be there. - PriceHardman
My doubt is wrong. I measured execution times of your original code and modified code as I suggested. Your code works faster! - megabyte1024
Only an idea. :) How about to add the from: operator to the search query, i.e. var threads = GmailApp.search('in: label:searchedLabel from:[email protected]');. There is such possibility according the help. - megabyte1024
I think the only problem with that is GmailApp.search returns a thread rather than individual messages. getMessages() is a method of class GmailThread. I'd still have to go through each thread and weed out the messages that aren't from the desired sender. - PriceHardman

1 Answers

12
votes

I think you are looking for GmailApp.getMessagesForThreads().

function fetchEmails(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var threads = GmailApp.search('label:searchedLabel');
  var messages = new Array();

  function Email(message){
    this.date = new Date(message.getDate());
    this.body = message.getBody();
  }

  var gmailMessages = GmailApp.getMessagesForThreads(threads);

  for(var i=0;i<thread.length;i++){
    var messagesForThread = gmailMessages[i];
    for(var j=0;j<messagesForThread.length;j++){
      if(messagesForThread[j].getFrom()=="firstName lastName <[email protected]>"){
       var message = new Email(messagesForThread[j]);
       messages.push(message);
      }
    }
  }  
}

Of course you can also write this a little more concisely (sorry, I can't turn up an opportunity to educate about the wonders of JavaScript):

function fetchEmails(){
  var messages = Array.prototype.concat.apply([], GmailApp.getMessagesForThreads(
      GmailApp.search('label:searchedLabel')).map(function(messagesForThread) {
    return messagesForThread.filter(function(message) {
      return message.getFrom() == "firstName lastName <[email protected]>";
    }).map(function(message) {
      return { date: new Date(message.getDate()), body: message.getBody() };
    });}));
}

This makes a grand total of 2 calls to Gmail, so it's going to be fast.

In fact, if you integrate the 'from' part into the search as was suggested above, all you need is:

function fetchEmails(){
  var messages = Array.prototype.concat.apply([], GmailApp.getMessagesForThreads(
    GmailApp.search('label:searchedLabel from:[email protected]')).map(
      function(messagesForThread) {
        return messagesForThread.map(function(message) {
          return { date: new Date(message.getDate()), body: message.getBody() };
      });}));
}

Finally, since you don't actually care about the thread structure, you can just concat the arrays before the map, which leads to this:

function fetchEmails(){
  var messages = GmailApp.getMessagesForThreads(
        GmailApp.search('label:searchedLabel from:[email protected]'))
      .reduce(function(a, b){ return a.concat(b); })
      .map(function(message) {
         return { date: new Date(message.getDate()), body: message.getBody() }; 
      });
}

(I left in the earlier samples in case you do care about the thread structure and you were just giving a minimal example).