Time-Driven trigger for mail forwarding - google-apps-script

I'm attempting to test a script that I'm working on. The script is fine, it executes successfully when I trigger it manually. When I add a time driven script of every minute interval the scrips starts throwing an exception after couple of hrs .
Exception: Service invoked too many times for one day: gmail
I checked the daily quota of email and found that i still have mail quota left
var quota = MailApp.getRemainingDailyQuota();
Logger.log(quota);
Also I am able to receive the try catch email but the mails are not forwarded .
Is this because of the execution time quota associated with the trigger? Below is the code
function MailForward() {
try{
var glabel = createLabel_("Mail-Forwarded");
var rtm_email = 'abc#abc.com';
var from_email = Session.getActiveUser().getEmail();
var threads = GmailApp.search('in:inbox is:unread newer_than:1d');
var mForward = 0;
for (var i=0;i<threads.length;i++) {
var messages=threads[i].getMessages();
for (var m = 0; m < messages.length; m++){
if (messages[m].isUnread()){
mForward = 0;
var mlabels = threads[i].getLabels();
for (var j = 0; j < mlabels.length; j++) {
Logger.log(mlabels[j].getName());
if (mlabels[j].getName() === "Mail-Forwarded") {
mForward = 1;
}
}
if (mForward===0) {
// Logger.log(messages.length)
// Logger.log(messages[m].getFrom());
var from = messages[m].getFrom();
//Logger.log(messages[m].getDate());
var date = messages[m].getDate();
// Logger.log(messages[m].getSubject());
var subject = messages[m].getSubject();
// Logger.log(messages[m].getTo());
var to = messages[m].getTo();
var body = messages[m].getBody();
var attachment = messages[m].getAttachments();
var emailoptions = ("---------- Forwarded message ----------" +'<br>'+'From: '+from+ "<'" + from.replace(/^.+<([^>]+)>$/, "$1") +"'>"+'<br>'+ 'Date: '+date+'<br>'+ 'Subject: ' +subject+'<br>'+
'To: ' +to+ "<'" + to.replace(/^.+<([^>]+)>$/, "$1") +"'>"+'<br>'+'<br>'+'<br>');
messages[m].forward(rtm_email,{htmlBody: emailoptions + body , Attachment: attachment});
glabel.addToThread(threads[i]);
Logger.log(glabel.getName());
messages[m].markRead();
mForward = 1;
}
}
}
}
} catch(e) {
MailApp.sendEmail("abc#abc.com", "Exception found in Sript", e );
Logger.log(e);
}
}

You checked quota using MailApp.getRemainingDailyQuota(); not GmailApp. These are two different services.
The quota method returns only "the number of remaining emails a user can send for the rest of the day." But the limit you are hitting is the number of times you invoked the service, for whatever purpose.
You are using GmailApp a lot to access existing messages, not so much to send new ones. In particular, you are checking every message in every thread from today, and do it every minute. That's a lot of API calls: getMessages, isUnread, etc.
One way to reduce the number of API calls is to have more targeted search. The after: search parameter accepts Unix timestamp, which makes it possible to do the following:
function doSomethingWithNewEmail() {
var interval = 5; // if the script runs every 5 minutes; change to 1 if it runs every minute
var date = new Date();
var timeFrom = Math.floor(date.valueOf()/1000) - 60 * interval;
var threads = GmailApp.search('is:inbox after:' + timeFrom);
for (var i = 0; i < threads.length; i++) {
// do something
}
}
I successfully used the above approach with 5 minute interval. It may work with 1 minute too, since most of the time search will be the only API call made by the script.

Related

How to find a time for a meeting with several participants using Google Calendar API and Apps Script?

I am working on a chatbot project using Dialogflow API, Apps Script, Calendar API. The chatbot should be able to organize a meeting between two or more participants directly from the chat. For example, the user says something like "organize a meeting with john.smith#mail.com for tomorrow at 5 pm" and the chatbot should go to both my and John's calendars, check availability for this time frame and book the meeting. So far so good. I already have the solution up to this stage (check the code snippet below). My question is if the users are busy for this time frame, how can I get the suggested time where all the participants are free. I am using the Free/Busy call but it only returns when the users are busy.
//the function search in spreadshet containing all timezones and selects the corresponding offset
function setCorrectTimeZone(rawTime,rawDate){
var spreadSheet = SpreadsheetApp.openById("10tJCi5bRHs3Cl8Gvw8NGMeHhfRBDnvPD338peH2MWyg"); //the timezones sheed ID stored in CEE Google Assistant shared drive
var sheet = spreadSheet.getSheetByName("timezones");
var timezoneRange = sheet.getRange(2, 1, 513, 2).getValues();
//getting the user's timezone
var userTimeZone = CalendarApp.getDefaultCalendar().getTimeZone();
Logger.log("User time zone: " + userTimeZone);
var userTimeZoneOffset = "";
var correctDateTimeAndZone = "";
//iterating over the timezones from the sheet and comparing with user's to find the correct offset
for(var i = 0; i<timezoneRange.length; i++){
if(timezoneRange[i][1] == userTimeZone){
userTimeZoneOffset = timezoneRange[i][0];
}
}
//taking the date only
var date = rawDate.split('T')[0];
//taking the time only
var timeNoTimeZone = rawTime.split('+')[0].split('T')[1];
//concatenating the date, time and the correct timezone together
correctDateTimeAndZone = date + 'T' + timeNoTimeZone + userTimeZoneOffset;
return correctDateTimeAndZone;
}
function organizeMeeting(dialogflowRawResponse, email) {
var guestEmail = dialogflowRawResponse.queryResult.parameters.email; //the list of all guests
var rawDate = dialogflowRawResponse.queryResult.parameters.date;
var rawTime = dialogflowRawResponse.queryResult.parameters.time;
var eventTitle = dialogflowRawResponse.queryResult.parameters.meetingName;
var hasAllParams = dialogflowRawResponse.queryResult.hasOwnProperty('allRequiredParamsPresent'); //checker for all parameters
var correctedTimezone = setCorrectTimeZone(rawTime,rawDate);
Logger.log("Has all required parameters? " + hasAllParams);
//check if all parameters are passed
while(hasAllParams == false){
Logger.log("Parameters are missing");
Logger.log(dialogflowRawResponse.queryResult.fulfillmentText);
return { text: dialogflowRawResponse.queryResult.fulfillmentText };
}
Logger.log("Guests email list detected: " + JSON.stringify(guestEmail) + "\nDate-time detected: " + rawTime + "\nCorrect date-time timezone: " + correctedTimezone +"\nTitle detected: " + eventTitle);
//setting the date-time for the start and the end of the event
var dateTimeStart = new Date(correctedTimezone);
var dateTimeEnd = new Date(correctedTimezone);
dateTimeEnd.setHours(dateTimeEnd.getHours() + 1);
dateTimeStart = dateTimeStart.toISOString();
dateTimeEnd = dateTimeEnd.toISOString();
Logger.log("ISO dateTimeStart: " + dateTimeStart);
Logger.log("ISO dateTimeEnd: " + dateTimeEnd);
var participants = [{"id": email}]; //array of objects. Each object is a particpant for the event
for(var i = 0; i < guestEmail.length; i++){
participants.push({"id": guestEmail[i]}); //filling the participant array
}
//preparing the body for the Calendar API free-busy request
var requestBody = {
"timeMin": dateTimeStart,
"timeMax": dateTimeEnd,
"items": participants
}
//Calendar freebusy request to check if the slot is available for all particiaptns
var response = Calendar.Freebusy.query(requestBody);
for(var i = 0; i < participants.length; i++){
var calendarId = participants[i].id;
if(response.calendars[calendarId]['busy'].length != 0){
Logger.log(calendarId + " is busy at this time");
return { text: calendarId + " is busy at this time" };
break;
}
}
//guest array of objects for each participant
var guestsArr = [{"email":email}];
for(var i = 0; i < guestEmail.length; i++){
guestsArr.push({"email": guestEmail[i]});
}
//preparing the event details for the Calendar API call
var event = {
"summary": eventTitle,
"end": {
"dateTime": dateTimeEnd
},
"start": {
"dateTime": dateTimeStart
},
"attendees": guestsArr
}
//preapring the event options for the Calendar API call
var eventOptions = {
"sendNotifications": true,
"sendUpdates": "all"
}
//Calendar API call
var calendarEventRequest = Calendar.Events.insert(event, "primary",eventOptions);
//logs the Calendar API response to the logs
Logger.log(JSON.stringify(calendarEventRequest));
return { text: "Done! Check you calendar." };
}
The code above takes the parameters from Dialogflow API - date, time, meeting title, and participants and uses this information to make free/busy call and then Calendar API call eventually. It is also using spreadsheet db to find the correct user timezone based on the user location.
Any help will be highly appreciated if someone has already done such feature to get available time slots.
You can check each one-hour timeslot if everyone is free, if they are all free, then send the invitations in Calendar.
Sample Code:
var dateTimeStart = new Date(correctedTimezone);
var dateTimeEnd = new Date(correctedTimezone);
do {
dateTimeEnd.setHours(dateTimeStart.getHours() + 1);
dateTimeStart = dateTimeStart.toISOString();
dateTimeEnd = dateTimeEnd.toISOString();
Logger.log("ISO dateTimeStart: " + dateTimeStart);
Logger.log("ISO dateTimeEnd: " + dateTimeEnd);
var participants = [{"id": email}]; //array of objects. Each object is a particpant for the event
for(var i = 0; i < guestEmail.length; i++){
participants.push({"id": guestEmail[i]}); //filling the participant array
}
//preparing the body for the Calendar API free-busy request
var requestBody = {
"timeMin": dateTimeStart,
"timeMax": dateTimeEnd,
"items": participants
}
//Calendar freebusy request to check if the slot is available for all particiaptns
var response = Calendar.Freebusy.query(requestBody);
for(var i = 0; i < participants.length; i++){
var calendarId = participants[i].id;
if(response.calendars[calendarId]['busy'].length != 0){
dateTimeStart.setHours(dateTimeStart.getHours() + 1);
Logger.log(calendarId + " is busy at this time");
//return { text: calendarId + " is busy at this time" };
break;
}
}
}
while (response.calendars[calendarId]['busy'].length != 0);

How to filter out all emails that came from a mailing list in Gmail

Is there a way to filter out all emails that came from a mailing list within Gmail or Google Apps Script using a search query. I know you can filter out a specific email address using list:info#example.com. But I want a catch-all type of query or even a query to catch-all from a specific domain such as list:#example.com. However, this does not work. Any ideas? Any help is greatly appreciated, thank you!
This function will trash all messages from all inbox thread that are not in the list.
function emailFilter() {
var list=['a#company.com','b#company.com','c#company.com','d#company.com','e#company.com'];
var threads=GmailApp.getInboxThreads();
var token=null;
for(var i=0;i<threads.length;i++) {
if(threads[i].getMessageCount()) {
var messages=threads[i].getMessages();
for(var j=0;j<messages.length;j++) {
if(list.indexOf(messages[j].getFrom()==-1)) {
messages[j].moveToTrash();
}
}
}
}
}
I haven't tested it because I keep my inbox empty all of the time. You might want to replace 'moveToTrash()' to 'star()' for testing
What I could understand from your question and your comments, you need to filter the emails in a user's inbox that he has received, which don't only contain a certain label, but also a certain domain. If I understood well this code can help you:
function checkLabels() {
// Get the threads from the label you want
var label = GmailApp.getUserLabelByName("Label Test List");
var threadArr = label.getThreads();
// Init variable for later use
var emailDomain = '';
// Iterate over all the threads
for (var i = 0; i < threadArr.length; i++) {
// for each message in a thread, do something
threadArr[i].getMessages().forEach(function(message){
// Let's get the domains from the the users the messages were from
// example: list:#example.com -> Result: example.com
emailDomain = message.getFrom().split('<').pop().split('>')[0].split('#')[1];
// if emailDomain is equal to example.com, then do something
if(emailDomain === 'example.com'){
Logger.log(message.getFrom());
}
});
}
}
Using the Class GmailApp I got a certain label with the .getUserLabels() method and iterate through the threads thanks to the .getInboxThreads method. With a second loop and the .getMessages() you can get all the messages in a thread and for knowing the one who sent them, just use the .getFrom() method.
Docs
For more info check:
Gmail Service.
Class GmailMessage.
Class GmailThread.
So I was able to avoid replying to emails that come from a mailing list address by using the getRawContent() method and then searching that string for "Mailing-list:". So far the script is working like a charm.
function autoReply() {
var interval = 5; // if the script runs every 5 minutes; change otherwise
var date = new Date();
var day = date.getDay();
var hour = date.getHours();
var noReply = ["email1#example.com",
"email2#example.com"];
var replyMessage = "Hello!\n\nYou have reached me during non-business hours. I will respond by 9 AM next business day.\n\nIf you have any Compass.com related questions, check out Compass Academy! Learn about Compass' tools and get your questions answered at academy.compass.com.\n\nBest,\n\nShamir Wehbe";
var noReplyId = [];
if ([6,0].indexOf(day) > -1 || (hour < 9) || (hour >= 17)) {
var timeFrom = Math.floor(date.valueOf()/1000) - 60 * interval;
var threads = GmailApp.search('from:#example.com is:inbox after:' + timeFrom);
var label = GmailApp.getUserLabelByName("autoReplied");
var repliedThreads = GmailApp.search('label:autoReplied newer_than:4d');
// loop through emails from the last 4 days that have already been replied to
for (var i = 0; i < repliedThreads.length; i++) {
var repliedThreadsId = repliedThreads[i].getMessages()[0].getId();
noReplyId.push(repliedThreadsId);
}
for (var i = 0; i < threads.length; i++) {
var message = threads[i].getMessages()[0];
var messagesFrom = message.getFrom();
var email = messagesFrom.substring(messagesFrom.lastIndexOf("<") + 1, messagesFrom.lastIndexOf(">"));
var threadsId = message.getId();
var rawMessage = message.getRawContent();
var searchForList = rawMessage.search("Mailing-list:");
var searchForUnsubscribe = rawMessage.search("Unsubscribe now");
// if the message is unread, not on the no reply list, hasn't already been replied to, doesn't come from a mailing list, and not a marketing email then auto reply
if (message.isUnread() && noReply.indexOf(email) == -1 && noReplyId.indexOf(threadsId) == -1 && searchForList === -1 && searchForUnsubscribe === -1){
message.reply(replyMessage);
threads[i].addLabel(label);
}
}
}
}

GmailApp.search() seems to fail correct data

I am trying to get no of emails for yesterday according to one particular subject.
Usually, what I do at midnight 1 o'clock I count the no of emails for a particular subject and send the mail through Google script. At 3 o'clock I trigger one delete trigger which starts deleting mail of previous day.
so by this, I ensure I don't have any mail for the previous day.
var yesterday = "2017/7/10";
var today = "2017/7/11";
var query = "after:"+yesterday+" before:"+today+" subject: abcd";
To count no of emails I have written below function
function getEmailCount(query) {
var threads = GmailApp.search(query, 0, 500);
Logger.log(threads);
var total = 0;
for (var i = 0; i < threads.length; i++) {
var messagesInThread = threads[i].getMessageCount();
var msg = threads[i].getMessages()[0].getSubject()
total = total + messagesInThread;
}
Logger.log(msg)
Logger.log("Query %s", query);
Logger.log("No. Of Threads %s", threads.length);
Logger.log("No. Of Emails %s", total);
return total;
}
When I check for emails in Gmail with above subject I get only 8 but my script is returning 25 mails. Any help will be highly appreacated .
It is interesting, GMailApp does return some emails within threads outside of the search parameters that the Gmail web app was not. I have a suspicion this was a time-zone related thing due to the nature of the specific emails I was looking at.
Some modifications to your code can add for a couple of checks that differentiate the raw results and some of the UX that the web app throws in for free. Additionally, this filters threads and messages that strictly adhere to the search criteria.
function getEmailCount(query) {
var yesterday = "2017/06/07",
today = "2017/06/08",
yesterdayDate = new Date(yesterday),
todayDate = new Date(today);
query = query || "after:" + yesterday + " before:" + today + "";
var threads = GmailApp.search(query).reduce(function(validThreads, thread, idx) {
var messages = thread.getMessages().reduce(function(messages, message, idx) {
var isChat = message.isInChats(),
isDeleted = message.isInTrash(),
sent = message.getDate();
if (!isDeleted && !isChat && sent < todayDate && sent > yesterdayDate) {
messages.push(message.getSubject());
}
return messages;
}, []);
if (messages.length > 0) { validThreads[idx] = messages; }
return validThreads;
}, {});
var totalMessages = Object.keys(threads).reduce(function(count, thread) {
return count += threads[thread].length;
}, 0);
Logger.log("Query: %s", query);
Logger.log("No. Of Threads: %s", Object.keys(threads).length);
Logger.log("No. Of Emails: %s", totalMessages);
return totalMessages;
}

Excluding a label from a Google Apps Script

This is my first post :)
I am using a Google Apps Script that tracks e-mail from the last 7 days that have not had a response (basically tracks emails where my message is the last one).
This is the code here:
// This script searches Gmail for conversations where I never received a response
// and puts them in a NoResponse label
var DAYS_TO_SEARCH = 7; // look only in sent messages from last 7 days, otherwise script takes a while
var SINGLE_MESSAGE_ONLY = false; // exclude multi-message conversations where I sent the last message?
function label_messages_without_response() {
var emailAddress = Session.getEffectiveUser().getEmail();
Logger.log(emailAddress);
var EMAIL_REGEX = /[a-zA-Z0-9\._\-]+#[a-zA-Z0-9\.\-]+\.[a-z\.A-Z]+/g;
var label = GmailApp.createLabel("AwaitingResponse");
var d = new Date();
d.setDate(d.getDate() - DAYS_TO_SEARCH);
var dateString = d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
threads = GmailApp.search("in:Sent after:" + dateString);
for (var i = 0; i < threads.length; i++)
{
var thread = threads[i];
if (!SINGLE_MESSAGE_ONLY || thread.getMessageCount() == 1)
{
var lastMessage = thread.getMessages()[thread.getMessageCount()-1];
lastMessageSender = lastMessage.getFrom().match(EMAIL_REGEX)[0];
if (lastMessageSender == emailAddress)
{
thread.addLabel(label);
Logger.log(lastMessageSender);
}
}
}
}
The problem is at the moment, when the script runs the un-replied messages go into the "NoResponse" label which is great. However, when I delete the label from the emails that I don't need to follow up on, they come back up again when the script runs again.
My question is:
Would there would be a way to apply a label to messages that don't need to be followed up on, and then work that into the script, so that the script knows to exclude that label?
Any help would be fantastic :)
Thanks
Aidan
May be the script can apply two labels - NoResponse and Processed. You can remove the NoResponse label manually and yet the Processed label would stay.
The filter can be modified like:
threads = GmailApp.search("in:Sent -in:Processed after:" + dateString);

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.