Google gmail script that triggers on incoming email - google-apps-script

I've been reading through gmail addons. They have contextual triggers that trigger when you open an email.
Is it possible to trigger a service when an email is received by me? Best I can find is unconditional but that only triggers when the email is opened.

You can't create a trigger for every email, however you can do something similar as described in this answer.
For example you can:
Set up a filter that puts a special label on incoming emails that you want to process.
Set up a reoccurring script that runs every 10 minutes, or even every minute. In the script, you can pull all of the emails that have the given label, and process them accordingly, removing the label when you are done.
function processEmails() {
var label = GmailApp.getUserLabelByName("Need To Process");
var threads = label.getThreads();
for (var i = threads.length - 1; i >= 0; i--) {
//Process them in the order received
threads[i].removeLabel(label).refresh();
}
}
You can then set this on a time based trigger to have it run as often as you would like.
If you want to keep track of the emails you have processed, you can create another "processed" label and add that to the message when you are done processing.

I had a little trouble with getting the labels right so I'm including code to log your labels. I modified user3312395's code also to add new label also.
Thanks for the original answer too!
function emailTrigger() {
var label = GmailApp.getUserLabelByName('Name of Label to Process');
var newLabel = GmailApp.getUserLabelByName('New Label Name');
if(label != null){
var threads = label.getThreads();
for (var i=0; i<threads.length; i++) {
//Process them in the order received
threads[i].removeLabel(label);
threads[i].addLabel(newLabel);
//run whatever else here
}
}
}
function getLabels(){
var labels = GmailApp.getUserLabels();
for(i=0; i<labels.length; i++){
Logger.log(labels[i].getName());
}
}

Yes, you can trigger a function for every new email!
Just use the search query newer_than:1h. Have your trigger run every 10 minutes for example. Then you will essentially be running logic for every new email (with up to 10 minutes delay, which is probably fine).
var TRIGGER_NAME = 'handleNewEmails'
// Maximum number of threads to process per run.
var PAGE_SIZE = 150
var INTERVAL = 10
function Install() {
// First run 2 mins after install
ScriptApp.newTrigger(TRIGGER_NAME)
.timeBased()
.at(new Date(new Date().getTime() + 1000 * 60 * 2))
.create()
// Run every 10 minutes there after
ScriptApp.newTrigger(TRIGGER_NAME)
.timeBased().everyMinutes(INTERVAL).create()
}
function Uninstall() {
var triggers = ScriptApp.getProjectTriggers()
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() === TRIGGER_NAME) ScriptApp.deleteTrigger(triggers[i])
}
}
function handleNewEmails() {
var threads = GmailApp.search("newer_than:1h", 0, PAGE_SIZE)
for (var i = 0; i < threads.length; i++) {
var thread = threads[i]
// Do something with the thread.
}
}

Actually, they have somewhat complex pub/sub service that requires setting up OAuth authentication. With this service you'll must be able to get push notifications. But I also think its easier to set up a trigger to run every 10 or even 1 minute.

Related

Is there a way to set up a trigger for when I receive an email? [duplicate]

I've been reading through gmail addons. They have contextual triggers that trigger when you open an email.
Is it possible to trigger a service when an email is received by me? Best I can find is unconditional but that only triggers when the email is opened.
You can't create a trigger for every email, however you can do something similar as described in this answer.
For example you can:
Set up a filter that puts a special label on incoming emails that you want to process.
Set up a reoccurring script that runs every 10 minutes, or even every minute. In the script, you can pull all of the emails that have the given label, and process them accordingly, removing the label when you are done.
function processEmails() {
var label = GmailApp.getUserLabelByName("Need To Process");
var threads = label.getThreads();
for (var i = threads.length - 1; i >= 0; i--) {
//Process them in the order received
threads[i].removeLabel(label).refresh();
}
}
You can then set this on a time based trigger to have it run as often as you would like.
If you want to keep track of the emails you have processed, you can create another "processed" label and add that to the message when you are done processing.
I had a little trouble with getting the labels right so I'm including code to log your labels. I modified user3312395's code also to add new label also.
Thanks for the original answer too!
function emailTrigger() {
var label = GmailApp.getUserLabelByName('Name of Label to Process');
var newLabel = GmailApp.getUserLabelByName('New Label Name');
if(label != null){
var threads = label.getThreads();
for (var i=0; i<threads.length; i++) {
//Process them in the order received
threads[i].removeLabel(label);
threads[i].addLabel(newLabel);
//run whatever else here
}
}
}
function getLabels(){
var labels = GmailApp.getUserLabels();
for(i=0; i<labels.length; i++){
Logger.log(labels[i].getName());
}
}
Yes, you can trigger a function for every new email!
Just use the search query newer_than:1h. Have your trigger run every 10 minutes for example. Then you will essentially be running logic for every new email (with up to 10 minutes delay, which is probably fine).
var TRIGGER_NAME = 'handleNewEmails'
// Maximum number of threads to process per run.
var PAGE_SIZE = 150
var INTERVAL = 10
function Install() {
// First run 2 mins after install
ScriptApp.newTrigger(TRIGGER_NAME)
.timeBased()
.at(new Date(new Date().getTime() + 1000 * 60 * 2))
.create()
// Run every 10 minutes there after
ScriptApp.newTrigger(TRIGGER_NAME)
.timeBased().everyMinutes(INTERVAL).create()
}
function Uninstall() {
var triggers = ScriptApp.getProjectTriggers()
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() === TRIGGER_NAME) ScriptApp.deleteTrigger(triggers[i])
}
}
function handleNewEmails() {
var threads = GmailApp.search("newer_than:1h", 0, PAGE_SIZE)
for (var i = 0; i < threads.length; i++) {
var thread = threads[i]
// Do something with the thread.
}
}
Actually, they have somewhat complex pub/sub service that requires setting up OAuth authentication. With this service you'll must be able to get push notifications. But I also think its easier to set up a trigger to run every 10 or even 1 minute.

Is possible query the current status of quotas limitations from a App Script code?

I have wrote some codes in Google App Script and I get recurrently exceptions because I reach execution quota limits.
I want to avoid this exceptions but I not found any class or method to query the current status of the quotas.
Unfortunately, there's no single function that would allow you to skip any quota limitations outright.
However, you can build a simple architecture where once you know what kind of rate limits you'd run into (say, either time or the count of hits etc.), you can build the logic of your code to accommodate such scenarios.
Example: here's how you can bypass script execution timeout, programmatically -
Code to detect the threshold -
// var today = new Date();
// 'today' is declard in the original script
function isTimeUp(today) {
var now = new Date();
return now.getTime() - today.getTime() > 30000;
// 30000 = 30 seconds; this is the threshold limit
// you are free to setup your own threshold limit
}
Code to intentionally break the code (before the script does it for you) so you can programatically setup triggers to pick things up right where it were broken off -
function firstFunction() {
var today = new Date();
for (var i = 0; i < something.length; i++) {
// check if the script has exceeded threshold
if (isTimeUp(today)) {
// schedule a trigger for a different function
ScriptApp.newTrigger("repeatFunction")
.timeBased()
.everyMinutes(1)
.create();
break;
}
}
}
function repeatFunction() {
for (/* condition */) {
// do stuff; maybe not everything from firstFunction()
if (/* test condition that defines the last action */) {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
// delete all triggers
ScriptApp.deleteTrigger(triggers[i]);
}
break;
}
}
}
I realise you might've not needed too much of the coding part of this but I'd be happy to help as required :)

Logging (lots of) Gmail Data into a Google Sheet

The object of my Google App Script is to log all basic data from email within a certain range of time (not yet implemented), from within all labels, into a Google Sheet.
This currently works to a limited degree. It will collect a small number of emails, but if I increase this (I have a lot of emails to log), this code will either ‘Exceeded maximum execution time’ if I pause for 500 or 1000 milliseconds (as below), otherwise I hit the other ratemax quota Service invoked too many times in a short time: gmail rateMax. Try Utilities.sleep(1000) between calls.
In the code below, I believe I attempted to repeat the main FOR loop 20 times with a WHILE loop, this was a way of seeing if I could perform this single grab n load var labelThreads = GmailApp.getUserLabelByName(label).getThreads(start, 1), 20 times. This would be one way to begin tracking "batches" -- It didn't quite work and I believe there is a better way to approach this, need some help.
function whenV24() {
function setColumnNames(range, columnNames){
var cell = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange(range);
cell.setValues(columnNames);
}
setColumnNames("A1:G1", [["Date","Label","To","From","Subject","Body","File Names"]]);
betterGetV24();
}
function betterGetV24() {
var myspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var mysheet = myspreadsheet.getSheets()[0];
var threads = GmailApp.getInboxThreads();
var messages = GmailApp.getMessagesForThreads(threads);
// ** LABEL NAME COLLECTION **
var labels = GmailApp.getUserLabels();
// ** CREATE EMPTY DATA ARRAYS **
var emailFrom = [];
var emailTo = [];
var emailBody = [];
var emailDate = [];
var emailLabel = [];
var emailSubject = [];
// ** LOAD "INBOX EMAIL DATA" INTO EMPTY ARRAYS **
for(var i = 0; i < threads.length; i++) {
emailBody.push([" "]);
emailFrom.push([messages[i][0].getFrom()]);
emailTo.push([messages[i][0].getTo()]);
emailSubject.push([messages[i][0].getSubject()]);
emailDate.push([Utilities.formatDate(messages[i][0].getDate(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")]);
emailLabel.push(["Inbox"]);
};
// ** LOAD "LABELED EMAIL DATA" INTO EMPTY ARRAYS **
for (var l = 0; l < labels.length; l++) { // ** Runs a for-loop over "labels array".
var label = labels[l].getName(); // Gets "this" label name.
var start = 0; // sets start number as 0
var tracker = 0;
// this section of code has to loop based on a separate set of logic
while (start < 20){
tracker++;
Logger.log("tracker :" + tracker);
var labelThreads = GmailApp.getUserLabelByName(label).getThreads(start, 1); // Gets threads in "this" label. (*Set Limits Here*)
var labelMessages = GmailApp.getMessagesForThreads(labelThreads); // Gets array with each email from "this" thread.
Utilities.sleep(500); // pause in the loop for 500 milliseconds
for (var t = 0; t <labelThreads.length; t++){ // ** Runs a for-loop over threads in a label.
Logger.log("part 1 - inside for-loop over message number: " + labelMessages[t][0].getId());
Utilities.sleep(500);// **pause in the loop for 500 milliseconds
if (labelMessages[t] == undefined){} // If it's empty, skip.
else { // If it's not empty.
Logger.log("part 2 - inside if statement in for-loop > push emailData into arrays");
emailBody.push([" "]);
emailFrom.push([labelMessages[t][0].getFrom()]);
emailTo.push([labelMessages[t][0].getTo()]);
emailDate.push([Utilities.formatDate(labelMessages[t][0].getDate(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")]);
emailSubject.push([labelMessages[t][0].getSubject()]);
emailLabel.push([labels[l].getName()]);
mysheet.getRange(2,2,emailLabel.length,1).setValues(emailLabel);
}
}
Logger.log("part 3 - outside if statement -> start += 2")
var start = start + 3;
}
Logger.log("part 4 - outside while loop");
}
// ** THEN, LOG THE FILLED DATA ARRAYS TO ROWS **
//getSheetValues(startRow, startColumn, numRows, numColumns)
mysheet.getRange(2,4,emailFrom.length,1).setValues(emailFrom);
mysheet.getRange(2,3,emailTo.length,1).setValues(emailTo);
mysheet.getRange(2,1,emailDate.length,1).setValues(emailDate);
mysheet.getRange(2,5,emailSubject.length,1).setValues(emailSubject);
mysheet.getRange(2,6,emailBody.length,1).setValues(emailBody);
}
I think at this point I should be using triggers, but it seems like I also have to track the previous batch of emails-logged and continue to the next batch. I'm also not aware of how to tie this in with triggers.
Thanks for reading. Any help is appreciated.
EDIT
I was setting data to the spreadsheet incorrectly here. For each loop I was trying to set data in the spreadsheet. I'm not sure how I ended up doing that. Simply moving this out of the loop and setting it later fixed my issue. updated code here: http://pastie.org/9793256#96,100,109,117,123-125,131,135-139
for (var t = 0; t <labelThreads.length; t++){ // ** Runs a for-loop over threads in a label.
Logger.log("part 1 - inside for-loop over message number: " + labelMessages[t][0].getId());
Utilities.sleep(500);// **pause in the loop for 500 milliseconds
if (labelMessages[t] == undefined){} // If it's empty, skip.
else { // If it's not empty.
Logger.log("part 2 - inside if statement in for-loop > push emailData into arrays");
emailBody.push([" "]);
emailFrom.push([labelMessages[t][0].getFrom()]);
emailTo.push([labelMessages[t][0].getTo()]);
emailDate.push([Utilities.formatDate(labelMessages[t][0].getDate(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")]);
emailSubject.push([labelMessages[t][0].getSubject()]);
emailLabel.push([labels[l].getName()]);
// ** INCORRECTLY TRYING TO SET DATA PER LOOP **
mysheet.getRange(2,2,emailLabel.length,1).setValues(emailLabel);
}
}
You can use PropertiesService to locally store the index of the last processed email. You can then add the "start" parameter to the GmailApp.search() method to begin searching from the last position and set this whole thing to trigger every 5 or 10 minutes.
function myTrigger() {
var start = PropertiesService.getScriptProperties().getProperty("startIndex");
var threads = GmailApp.search("in:inbox", start, 200);
for (var t in threads) {
// Log the thread using your existing code
start++;
}
PropertiesService.getScriptProperties().setProperty("startIndex", start);
}

Removing label from Gmail email after X days using Google Apps Script

I created a Google Apps Script Code.gs as follows to remove the Gmail label from every thread that is older than X days and labeled Y.
function archiveYThreads() {
// Every thread, older than two days, and labeled "Unread Feeds".
var threads = GmailApp.search('label:"Unread Feeds" older_than:2d');
for (var i = 0; i < threads.length; i++) {
threads[i].removeLabel("Unread Feeds");
}
}
According to the documentation, the function removeLabel exists. Alternatively, I found some sources that use deleteLabel. However, with both I get the error that both functions do not exist, after having set a time-based trigger:
Can anybody please help me detecting why the function does not work?
You have to supply an object of type GmailLabel as the argument to removeLabel() method. Try this snippet.
function archiveYThreads() {
var label = GmailApp.getUserLabelByName("Unread Feeds");
var threads = GmailApp.search('label:"Unread Feeds" older_than:2d');
for (var i = 0; i < threads.length; i++) {
threads[i].removeLabel(label);
}
}

How do I fix "Exceeded maximum execution time" error on this Google Apps Script?

I want to devise a script that will clean up my email. I want to create a few labels called "Auto Archive/# days" where # is a number between 0 and 9. I wrote this script below, but every time it runs, I receive an "Exceeded maximum execution time" error.
I have a time-driven (hour timer) trigger set up to run every 12 hours. I call the autoArchive method in the trigger. I tried adding Utilities.sleep a few times, but it didn't help. [Should I put them somewhere else in the code?]
Any help is greatly appreciated! Thank you in advance!
function cleanUp(delayDays) {
//var delay2Weeks = 14 // Enter # of days before messages are moved to archive
//var delay2Days = 2 // Enter # of days before messages are moved to archive
if (typeof delayDays != 'number') {
return null;
}
var maxDate = new Date();
maxDate.setDate(maxDate.getDate()-delayDays);
var label = GmailApp.getUserLabelByName("Auto Archive/" + delayDays + " days");
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
if (threads[i].getLastMessageDate()<maxDate)
{
var randnumber = Math.random()*5000;
Utilities.sleep(randnumber);
Utilities.sleep(randnumber);
threads[i].moveToArchive();
}
}
}
function autoArchive()
{
for (var i = 1; i < 10; i++) {
cleanUp(i);
}
}
So it seems I was getting all items with "Auto Archive/X days" and not limiting the result set to only items within the Inbox. After correcting that, the maximum execution time error went away. I corrected it by choosing the inbox items first, and next the items with the label.
function cleanUp(delayDays) {
//var delay2Weeks = 14 // Enter # of days before messages are moved to archive
//var delay2Days = 2 // Enter # of days before messages are moved to archive
if (typeof delayDays != 'number') {
return null;
}
var maxDate = new Date();
maxDate.setDate(maxDate.getDate()-delayDays);
var inboxItems = GmailApp.getInboxThreads();
for (var i = 0; i < inboxItems.length; i++) {
if (inboxItems[i].getLastMessageDate()<maxDate) {
var autoItems = inboxItems[i].getLabels();
for(var j=0; j < autoItems.length; j++) {
if (autoItems[j].getName() == "Auto Archive/" + delayDays + " days") {
inboxItems[i].moveToArchive();
break;
}
}
}
}
}
function autoArchive()
{
Session.getActiveUser().getEmail();
for (var i = 1; i < 10; i++) {
cleanUp(i);
}
}
There are many ways to speed it up. For starters dont call sleep as it will make the problem worse (makes the script take even more time from your daily quota and trigger 5min limit).
After that if the problem is that you have too many threads it might help to write a list of threads to archive (in scriptdb for example store the thread ids) but dont archive them yet.
Later from another trigger (say every 10min) you process your list by chunks (see https://developers.google.com/apps-script/reference/gmail/gmail-app#getThreadById(String)) and use more triggers if needed to avoid the 5min limit.