Gmail script run on new mail recieved - google-apps-script

I am trying to write a script that will forward mail from my work gmail to a personal email. The script needs to run only from Mon - Fri, between 4pm and 7am. The spot where I am getting stuck is being able to run the script every 5 mins during that time and forwarded only new mail.
Is their a google script api to do an action on received mail.(onRecieve)? Or I am I looking at adding a custom label and some if statements to ensure I don't forward duplicates.
Heres the generics of what I have going right now.
function startCustomTrigger()
{
ScriptApp.newTrigger('nonWorkHours').timeBased().everyMinutes(5).create();
}
function nonWorkHours() {
var date = new Date();
var day = date.getDay();
var hrs = date.getHours();
if ((day >= 1) && (day <= 5) && (hrs >= 16) && (hrs <= 7)) {
// forward email here
var thread = GmailApp.getInboxThreads(0,1)[0]; // get first thread in inbox
var message = thread.getMessages()[0]; // get first message
message.forward("example#example.com");
}
}
Updated script: FYI, works decent but needs some updating and cleaning.
/**
Forward unread inbox messages to personal email at desired hours. M - F from 4pm to 7am and Sat all day.
Also mark the messages that are forwarded with custom label "Forwarded(Non_Hours)" and marked as read.
Grab timestamp of last message that was forwarded and save as global script property.
tjones © 2015
TimeMailed
**/
// Custom trigger to run script every 5 mins
function startCustomTrigger()
{
ScriptApp.newTrigger('timeBound').timeBased().everyMinutes(5).create()
}
// Global Variables ====================================================================================================
var scriptProperties = PropertiesService.getScriptProperties();
//Grab current time of run
var startTime = new Date().getTime(); //Log the start of script--> combine with below Logger line
Logger.log("START OF RUN: " + Utilities.formatDate(new Date(startTime),Session.getScriptTimeZone(),'d MMM yy hh:mm:ss' )); //Log time into readable format
// Grab gmail inbox
var thread = GmailApp.search('is:unread'); //Grab all unread messages
var gmailMessages = GmailApp.getMessagesForThreads(thread); //Grab all the messages of given threads, set above
//Setup script timestamp properties
var lastForwardTime = scriptProperties.getProperties(); //Var for the timestamp of last message from last time scirpt ran
var keys = scriptProperties.getProperty('lastForward'); //The key from the lastForward timestamp
Logger.log("LAST TIMESTAMP OF MESSAGE FORWARDED: " + keys) //Log the key to the logger
Logger.log("label: " + GmailApp.createLabel("Forwarded(Non_Hours)")); //Create label, if exists will just overwrite
//Variable to set label "Forwarded(Non_Hours)" to threads being forwarded
var label = GmailApp.getUserLabelByName("Forwarded(Non_Hours)");
//Set some time formats to check if between M-F and 4pm to 7am
var date = new Date();
var day = date.getDay();
Logger.log(day);
var hrs = date.getHours();
Logger.log(hrs);
//=========================================================================================================================
if (hrs >= 16 && hrs <= 24) {
var inBound = true;
} else if (hrs >= 1 && hrs <= 6) {
inBound = true;
} else {
inBound = false;
}
function timeBound() {
if ((day >= 1) && (day <= 5) && (inBound == true)) {
timeMailed();
}
else if ((day >=6) && (day <=7)) {
timeMailed();
}
else {
Logger.log("Time is out of bounds, Within work hours: Sleeping for 5 mins");
}
}
// Meat and potatoes of forwarding
function timeMailed() {
for(var i=0;i<thread.length;i++){ //for loop for all the threads from above
var messagesForThread = gmailMessages[i]; //var for messages in threads
label.addToThread(thread[i]) // Set label to thread
GmailApp.markThreadRead(thread[i]); //Mark messages as read before forwarding them
for(var j=0;j<messagesForThread.length;j++){ //for loop to go through messages found above
// Get timestamps of messages for duplicate check
var messageDateTime = messagesForThread[j].getDate();
Logger.log(messagesForThread[j].getDate());
var messageEpocTime = messageDateTime.getTime();
Logger.log(messageDateTime.getTime());
// Compare message timestamp to lastForward key and make sure its newer than last check
if (messageEpocTime > scriptProperties.getProperty('lastForward')) {
scriptProperties.setProperty('lastForward', messageEpocTime); //Get date of messages and set as script property "lastForward"
messagesForThread[j].forward("tjones#livefake.com"); //forward the messages from above to forward address
Logger.log("Message with subject " + messagesForThread[j].getSubject() + " was forwarded")
}
else {
Logger.log("Message with subject " + messagesForThread[j].getSubject() + " was already forwarded within last 5 min check")
}
Logger.log("FINAL TIMESTAMP AT: " + scriptProperties.getProperty('lastForward') ); //Leave in final run to log last timestamp
}
}
}

what you have is a good start. to not forward duplicates you could use labels but its overkill in this case.
instead remember (in script properties service) the timestamp of the last email forwarded. then use search to only find emails "after" that time.
if you save the property after sending the email, you will never miss an email but may rarely send a duplicate if the sendmail call crashes right after sending.
If you instead save the property right before the mail you guarantee no duplicates but may miss some if it crashes right before sending the email.
look at docs and other s.o. questions for how to do a search "after:date". i have done this for dates but not datetime. if only date is supported you might need to skip some results or find a more efficient way to search.
using a label is good in the general case where there is no pattern to the sequence of emails. in your case new inbox mails are always consecutive.

Related

SendEmail duration / Count per week in Googlsheet AppScript

I'm sorry if this question was already asked, I tried to look for it but couldn't find it.
I'm trying to send out alert emails with app script based on a specific cell value in google sheets. To point out the value is generated by a formula not by a user.
I created the below with a trigger that runs weekly.
My challenge is, that I want to be able to set up the trigger for the script to run daily to check for the condition but only send the email one time every 7 days until the condition is no longer met.
I greatly appreciate any advice if this is possible.
function CheckSessions() {
// Fetch the sessions available
var sessionsRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Billing").getRange("K1");
var sessions = sessionsRange.getValue();
var ui = SpreadsheetApp.getUi();
// Check totals sessions
if (sessions == 1){
// Fetch the email address
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Billing").getRange("H2");
var emailAddress = emailRange.getValue();
// Send Alert Email.
var message = 'This is your Alert email!'; // Second column
var subject = 'Your Google Spreadsheet Alert';
MailApp.sendEmail(emailAddress, subject, message);
}
}
I believe you're saying you want the script to run daily, but if you've already sent the email within the past 7 days, don't send it again even if the condition is true.
One approach is to store the date for the last time you sent the reminder, and then do some date math to see if it has been 7 days since the last reminder.
You could store that date in a designated cell of the spreadsheet, but there are some good reasons not to (e.g. so a spreadsheet editor doesn't accidentally overwrite it). This is a typical use case for PropertiesService.
Properties are stored as a string, so you'll have to convert dates to/from strings.
Properties store values based on a key. If your script is only managing email alerts to one address, you can use a static key like lastSent. If you're managing alerts to multiple addresses then you could key by the email address itself. That's what I've done here:
function CheckSessions()
{
// Fetch the sessions available
var sessionsRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Billing").getRange("K1");
var sessions = sessionsRange.getValue();
// Check totals sessions
if (sessions == 1)
{
// Fetch the email address
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Billing").getRange("H2");
var emailAddress = emailRange.getValue();
// Check if we've sent to this email within 7 days
var propertyStore = PropertiesService.getScriptProperties()
var lastSent = propertyStore.getProperty(emailAddress)
if (!lastSent || getDaysDifference(new Date(lastSent), new Date()) >= 7)
{
// Send Alert Email.
var message = 'This is your Alert email!'; // Second column
var subject = 'Your Google Spreadsheet Alert';
MailApp.sendEmail(emailAddress, subject, message);
// Store the date in properties as an ISO string, to be fetched on next execution
propertyStore.setProperty(emailAddress, (new Date()).toISOString())
} else
{
console.log("Last email was sent on %s so we won't send one today", lastSent)
}
}
}
/**
* Get days elapsed between two dates
* #param {Date} startDate
* #param {Date} endDate
*/
function getDaysDifference(startDate, endDate)
{
var start = new Date(startDate);
var end = new Date(endDate);
start.setHours(0, 0, 0, 0);
end.setHours(0, 0, 0, 0);
var days = Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
return days;
}
Send Email once a day
function CheckSessions() {
const ss = SpreadsheetApp.getActive()
var sessions = ss.getSheetByName("Billing").getRange("K1").getValue();
if (sessions == 1) {
var emailAddress = ss.getSheetByName("Billing").getRange("H2").getValue();
var message = 'This is your Alert email!';
var subject = 'Your Google Spreadsheet Alert';
MailApp.sendEmail(emailAddress, subject, message);
}
}
function createTrigger() {
if(ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() == "CheckSessions").length == 0) {
ScriptApp.newTrigger("CheckSessions").timeBased().everyDays(1).atHour(2).create();//keeps you from creating more than one
}
}

Send email if cells' value is below certain value

I am absolutely new to this and pulling my hair out trying to make a script for work.
I need to check employee's certifications on a daily basis and have them re-certified if expired.
Here is a "testing" spreadsheet with random data: https://docs.google.com/spreadsheets/d/1vJ8ms5ZLqmnv4N1upNHD4SRfgIgIbEAAndvUNy-s9S4/edit?usp=sharing
It lists personnel working for my department along with their badge numbers and number of days their certifications are valid for. The original sheet takes the days value from another spreadsheet, but it shouldn't affect this (I think?).
What I'm trying to achieve is write a script that checks all numbers in C3:G24.
If any cell in this range has a value lower than 15, it should pull their badge number and name from the same row, along with the "days" their certificates are valid for and send an email containing all this data.
For example
Subject: Certifications about to expire
E-mail content: Your employee's (Name from col B) certification with Badge# (# from Col A) will expire in X days (where X = the number from range C3:G24).
So far my best attempt was to at least make it send ANY e-mail on edit, but failing miserably trying to adapt any script found online.
Here is what worked to at least send an e-mail but then I did something to break it:
function checkValue()
{
var ss = SpreadsheetApp.getActive();//not sure if needed, the spreadsheet eventually doesn't have to be open/active
var sheet = ss.getSheetByName("Certifications");
//not sure if this is ok
var valueToCheck = sheet.getRange("C3:G24").getValue();
//Here I'd like to change the "days" based on needs
if(valueToCheck < 15)
{
MailApp.sendEmail("email#company.com","Certifications","Your employee certification will expire soon" + valueToCheck+ ".");
}
}
Can someone please help guide me in the right direction?
here is what I would do:
function checkValue()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Certifications");
var valueToCheck = sheet.getDataRange().getValues();
var resultValues = [];
valueToCheck = valueToCheck.filter(function(element){
var val = 0
if (parseInt(element[2]) < 15)
{
resultValues.push({col: "Cert1", value: element[2]})
return (true);
}
else if (parseInt(element[3]) < 15)
{
resultValues.push({col: "Cert2", value: element[3]})
return (true);
}
else if (parseInt(element[4]) < 15)
{
resultValues.push({col: "Cert3", value: element[4]})
return (true);
}
else if (parseInt(element[5]) < 15)
{
resultValues.push({col: "Cert4", value: element[5]})
return (true);
}
else if (parseInt(element[6]) < 15)
{
resultValues.push({col: "Cert5", value: element[6]})
return (true);
}
})
for(var i = 0; i < valueToCheck.length; i++)
{
MailApp.sendEmail("mail#company.com","Certifications","your employee's " + valueToCheck[i][1] + "with badge " + valueToCheck[i][0] + " certification will expire in " + resultValues[i].value + " days (column " + resultValues[i].col + ").");
}
}
use the getValues() function to retrieve datas.
then filter the values based on condtion of value being less than 15
at the same time grab the column name and the less than 15 data.
parse through both arrays to send datas to your mail

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);

Google Apps Script - Calendar Reservation - Fails to Create Event

I have pasted the code below. To explain what the code is intended to do is block out a conference room so that it can not be reserved for use, it is a small "huddle room" that will be blocked out and only available to reserve a week in advance.
Anyway here is the problem I am encountering with the code below. If I run the code starting from Jan 1. The code will run and then part way through March stops creating events, if this happens exactly at the beginning of a month it wouldn't be an issue as I could either start easily from that point again, or assume the month is spelled wrong. But it creates reservations though march 18th. Also when I restarted this and set it to create blocked reservations starting at the beginning of April it got though December 8th.
My first guess is that I need to deal with reformatting the code to handle months without 31 days, but I assumed that those none existent days would just throw an error and the lop would continue, and it did get through February which is a short month.
Just thinking maybe someone who has more experience with Google Scripting may have an idea or see a flaw in something I am doing. Thanks for any help
function blockReservations(){
var roomcalendar = CalendarApp.getCalendarById('company.com_12458546525839392d898932#resource.calendar.google.com');
//for(var z=2014;z<=2020;z++){
//var year = z;
var year = '2014'; //This Line May be used in place of the above for loop to specify a specific year
for(var x=4;x<=12;x++)
{
if(x==1) var month = 'January';
else if(x==2) var month = 'February';
else if(x==3) var month = 'March';
else if(x==4) var month = 'April';
else if(x==5) var month = 'May';
else if(x==6) var month = 'June';
else if(x==7) var month = 'July';
else if(x==8) var month = 'August';
else if(x==9) var month = 'September';
else if(x==10) var month = 'October';
else if(x==11) var month = 'November';
else if(x==12) var month = 'December';
else month = 'null';
//var month = 'July'; //This Line May be used in place of the above for loop to specify a specific year
for(var y=1;y<=31;y++)
{
var date = y;
var startDateString = month + ' ' + date + ', ' + year +' 00:00:00';
var endDateString = month + ' ' + date + ', ' + year +' 24:00:00';
var event = roomcalendar.createEvent('Time Blocked', new Date(startDateString), new Date(endDateString));
}
}
// }
}
You don't mention any error messages, but I would expect that you're receiving a notification email reporting that the script was killed for running too long. Creating events in a loop can take lots of processing time.
I propose a different approach. Instead of creating daily events to reserve the room, why not create a recurring all-day event, starting a number of days in the future. Then each day, this reservation can be updated (by a timed trigger function) to revise the recurrence rule to start one day later.
/**
* Create or update a block reservation for a conference room,
* starting 'blockFrom' days from today.
*/
function updateBlockReservation() {
// Get Calendar
var calName = 'Huddle Room';
var cal = CalendarApp.getCalendarsByName(calName)[0];
var title = 'Reserved'; // Reserved events will have this title
var blockFrom = 7; // Days from now until room is blocked
var today = new Date(); // Today's date, ...
today.setHours(0,0,0,0); // at midnight.
var startDate // Daily block reservation starts here
= new Date(today.getTime() + (blockFrom * 24 * 60 * 60 * 1000));
var endTime = new Date(startDate.getTime() + (24 * 60 * 60 * 1000) - 1);
var recurrence = CalendarApp.newRecurrence().addDailyRule();
// Look for existing block reservation
var series = cal.getEvents(startDate, endTime, {search:title});
if (series.length == 0) {
// No block reservation found - create one.
var reserved = cal.createAllDayEventSeries(title, startDate, recurrence);
}
else {
// Block reservation exists - update the recurrence to start later.
reserved = series[0].getEventSeries();
reserved.setRecurrence(recurrence, startDate);
}
debugger; // Pause if running in debugger
}

Calendar eTags Mismatch error - Precautions taken, still getting error

I am creating a Google Apps Spreadsheet roster for my workplace. the actual roster is fairly simple, with a few calculations and an onEdit script that changes the cell colour, depending on duty for that hour.
One major part of this new roster is the ability to create Google calendar events for each person to notify them of the shifts. The roster is created and events should be created monthly. At the beginning of the month, the staff member doing the roster, will run a function, and create all events for that month, for each person. (I hope that made sense...)
My code is listed below. The sheet called "Splash" (Sheet index 0) contains the Name and Email of each Staff member on the team. The rosters will be created on sheet index 1 through infinity.
On the Roster sheet itself, column A contains the name of each Staff. Column B contains the Start time and C contains the End time.
function sendInvites() {
// Gather Prelim Information
var splash = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Splash");
var nameRange = splash.getRange("A6:B9"); // Need to change this to be the full staff list in Splash
var inviteSheet = Browser.inputBox("Sheet to send invites from (Number)", Browser.Buttons.OK_CANCEL)
var days = SpreadsheetApp.getActiveSpreadsheet().getSheets()[inviteSheet];
var dayRange;
//Initiate iteration through Names, according to the Splash sheet
for(nameRow=nameRange.getRow(); nameRow<=9; nameRow++){ //Change to Corresponding iterate
// Gather Name and Email information for the each person
var col = nameRange.getColumn();
var row = nameRow;
var name = splash.getRange(row, col).getValue();
var email = splash.getRange(row, col+1).getValue();
var cal = CalendarApp.getCalendarById(email);
//Initiate iteration through Days, according to the Roster
for(i=0; i<=6; i++){
//Specify Day Ranges
switch(i){
case 0: //Saturday
dayRange = days.getRange("A1:O6"); break;
case 1: //Sunday
dayRange = days.getRange("A10:O15"); break;
case 2: //Monday
dayRange = days.getRange("A19:O41"); break;
case 3: //Tuesday
dayRange = days.getRange("A45:O67"); break;
case 4: //Wednesday
dayRange = days.getRange("A71:O93"); break;
case 5: //Thursday
dayRange = days.getRange("A97:O119"); break;
case 6: //Friday
dayRange = days.getRange("A123:O145"); break;
}
//Find Name in dayRange
for(dayRow=dayRange.getRow(); dayRow<=dayRange.getLastRow(); dayRow++){
var searchCol = dayRange.getColumn();
var searchRow = dayRow;
var searchName = days.getRange(searchRow, searchCol).getValue();
if (name==searchName){
// Gather and format Date and Time information for invitation
var eventName = "Phones";
var date = Utilities.formatDate(dayRange.getValue(), "GMT+1000", "EEE MMM dd yyyy");
var startCell = days.getRange(searchRow, searchCol+1).getValue();
var endCell = days.getRange(searchRow, searchCol+2).getValue();
if (startCell != ""){
var startTime = date + " " + Utilities.formatDate(startCell, "GMT+1000", "HH:mm:ss");
var endTime = date + " " + Utilities.formatDate(endCell, "GMT+1000", "HH:mm:ss");
//Create a calendar event with the details above
Utilities.sleep(500); //Pause event creation for half second, to allow the last event to be fully created (Avoids "Calendar: Mismatch: etags error")
cal.createEvent(eventName, new Date(startTime), new Date(endTime)).removeAllReminders();
}
continue;
}
}
}
}
}
The issue I'm having right now is that the logic works, but not all the events are being created. I am testing with four staff, including myself. it will only complete one and a half iterations of Names before throwing a Calendar: Mismatch: etags error.
I am aware that this error occurs when a Calendar is being changed twice, at one time. I added a sleep time of half a second, before event creation, to allow for this. It is much rarer now, but still occurring. I have tried making it a 2 second sleep, I have tried moving it to different points. All of this will still eventuate with the eTags error.
Not too sure where to go from here. I feel like the roster is almost ready to be used, but it's just not stable enough yet.
Let me know if you have any ideas, or if you need some clarification.
You will need a try/catch to avoid "etags" errors. The operation generating the error is removeAllReminders() as it takes some time to create a new contact object in Google's distributed storage.
I use a helper function to avoid these dreaded "etags" errors:
function retryMethod(object, method) {
for (i=0; i<10; i++) {
try {
return object[method]();
}
catch (e) {
if (e.message.indexOf('Mismatch: etags') == -1) throw e;
Utilities.sleep(500);
}
}
throw new Error('retryMethod failed after retrys method ', method, ' object ', object)
}
You could then rewrite your "cal.createEvent(" line into:
retryMethod(cal.createEvent(eventName, new Date(startTime), new Date(endTime)), 'removeAllReminders');
Remember to check if the createEvent is null.
Actually my current helper function is slightly different as most of the function calls raising "etags" errors are in fact successful.