Sending out emails based on date in Google Sheets Scripts - google-apps-script

I'm trying to create a Script that will send an email for every cell that contains today's date. Here's what I have so far:
function email() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var rowCounter = 1;
var limit = sheet.getLastRow();
// Fetch the estimated dates & Today's date
var estimatedReturnDateRange = sheet.getRange("F:F");
var estimatedReturnDate = estimatedReturnDateRange.getCell(rowCounter, 1);
var todayDateRange = sheet.getRange("S1");
var todayDate = todayDateRange.getValue();
// Check totals sales
for(i=1; i<=limit; i++){
if (estimatedReturnDate = todayDate){
// Fetch the email address
var emailAddress = "maxbkimmel#gmail.com";
// Send Alert Email.
var message = estimatedReturnDate; // Second column
var subject = 'Your Google Spreadsheet Alert';
MailApp.sendEmail(emailAddress, subject, message);
}
rowCounter++;
estimatedReturnDate = estimatedReturnDateRange.getCell(rowCounter, 1);
}
rowCounter =1;
}
This is how I envision the logic of the script working:
estimatedReturnDate initially grabs the first cell in column F, which is a list of dates.
todayDate grabs cell S1, which contains today's date.
a for loop then loops through all rows of the sheet, and checks if estimatedReturnDate = todayDate.
If it does, an email is sent that contains the Row Number that matched today's date.
Then, rowCounter is incremented, estimatedReturnDate is set to the next cell in the row, and the loop runs again.
The problem I'm having is that when I run this script, an email is sent out for each row in the sheet, regardless of whether estimatedReturnDate matches todayDate or not.
Does anyone know what would be causing this?

function email() {
var ss=SpreadsheetApp.getActive();
var sheet=ss.getSheets()[0];//This is always the left most sheet but not necessarily the same sheet depending how users move the sheets around.
var vA=sheet.getRange(1,5,sheet.getLastRow(),1).getValues()
var dt=new Date();
var toda=new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()).valueOf();//midnight yesterday
var tmro=new Date(dt.getFullYear(),dt.getMonth(),dt.getDate()+1).valueOf();//midnight today
for(var i=0;i<vA.length;i++){
var dt=new Date(vA[i][0]).valueOf();//date from column5 of spreadsheet
//dt is between midnight yesterday and midnight today
if(dt>=toda && dt<=tmro){
var emailAddress = "maxbkimmel#gmail.com";
var message = Utilities.formatDate(dt, Session.getScriptTimeZone(), "E MMM dd, yyyy");
var subject = 'Your Google Spreadsheet Alert';
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
Utilities.formatDate
Date Class

Related

Google app script trigger not sending email

Please can someone show me the error here (I think it will be very obvious for someone who has any knowledge of coding)? I have cobbled this script together from various sources but it does not work - the logic is correct as it works with the standard Google triggers (which i do not think I can use as I want to send email only in office hours Mon - Friday). Thanks in advance;
`enter code here`function startCustomTrigger()
{
ScriptApp.newTrigger('StartProcess').timeBased().everyHours(1).create();
}
function StartProcess() {
var date = new Date();
var day = date.getDay();
var hrs = date.getHours();
if ((day >= 2) && (day <= 6) && (hrs >= 8) && (hrs <= 18)) {
// Get the sheet where the data is, in sheet 'Mail'
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Mail")
var startRow = 2; // First row of data to process since there is a header row
var numRows = sheet.getRange(1,5).getValue(); // Number of rows to process is set by a formula which
counts rows
// Fetch the range of cells A2:B6 where the emails and messages are
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
// Fetch values for each row in the Range to input into the mailing system
var data = dataRange.getValues();
// This processes the emails you want to send
for (i in data) {
var row = data[i];
var emailAddress = row[0]; // First column is the email address
var message = row[1]; // Second column is the message
var subject = row[1]; // This is the subject of the email
// This parses the data for the email to send
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
You have these two declarations:
var message = row[1]; // Second column is the message
var subject = row[1]; // This is the subject of the email
one must be incorrect
Try it this way:
function StartProcess(e) {
var day = e['day-of-week'];//available from event object but a little different than that which comes from Date().getDay()
var hrs = e.hour;//available from event object
if ((day >= 1) && (day <= 5) && (hrs >= 8) && (hrs <= 18)) {
const ss=SpreadsheetApp.getActive();
// Get the sheet where the data is, in sheet 'Mail'
var sheet=ss.getSheetByName("Mail")
var startRow=2; // First row of data to process since there is a header row
var numRows=sheet.getRange(1,5).getValue(); // Number of rows to process is set by a formula which counts rows
// Fetch the range of cells A2:B6 where the emails and messages are
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
// Fetch values for each row in the Range to input into the mailing system
var data = dataRange.getValues();
// This processes the emails you want to send
for (var i=0;i<data.length;i++) {
var row = data[i];
var emailAddress = row[0]; // First column is the email address
var message = row[1]; // Second column is the message
var subject = row[2]; // This is the subject of the email
// This parses the data for the email to send
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
JavaScript getDay()
timebased event objec
If you continue to have problems please provide an image of Mail Sheet.

Taking user input to Google Sheets using Apps Script and process with queries

I created a spreadsheet for reporting students attendance that contains 8 sheets (each sheet named as a subject code). After completing a particular class, I go to the specific sheet (subject) and select all the rows of that particular date and press the button AfterClass-->Process Data (sorting, removing duplicates and protecting) using Google Apps Script/Macros. All working fine.
Now I created a DASHBOARD and I want that a teacher can do everything from the dashboard rather than going to individual sheet (subject). S/he can give two inputs - subject (sheetname) and Date from the Dashboard and then automatically process these specific dataset of that Sheet (Not all data of the sheet). Please note that date is in Column A and subject-code in Column F. The code I wrote as follows:
function AfterClass() {
var spreadsheet = SpreadsheetApp.getActive();
//Sorting and removing duplicates
var height = spreadsheet.getActiveSheet().getActiveRange().getHeight();
spreadsheet.getCurrentCell().offset(0, 0, height, 6).activate()
.sort({column: spreadsheet.getActiveRange().getColumn() + 2, ascending: true});
spreadsheet.getActiveRange().removeDuplicates([spreadsheet.getActiveRange().getColumn() + 2]).activate();
//Protecting data finally
//var lastRow = spreadsheet.getLastRow();
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var me = Session.getEffectiveUser();
var description = 'Protected on ' + stringDate + ' by ' + me;
var protection = SpreadsheetApp.getActiveSheet().getActiveRange().protect().setDescription(description);
//protection.setDomainEdit(false);
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
//Removing blank spacess in between data
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[1] == '') {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
//For Double periods in a class
//var ss = SpreadsheetApp.getActiveSpreadsheet()
//var database = SpreadsheetApp.openById("xxx");
//var source = ss.getSheetByName('yyy');
var dataToCopyRng = SpreadsheetApp.getActiveSheet().getActiveRange(); //Gets range object of all data on source sheet
var dataToCopy = dataToCopyRng.getValues(); //Gets the values of the source range in a 2 dimensional array
var copyToSheet = SpreadsheetApp.getActiveSheet();
var copyData = copyToSheet.getRange(copyToSheet.getLastRow()+1,1,dataToCopy.length,dataToCopy[0].length).setValues(dataToCopy);
//Calculate class attendance and signed
var height2 = spreadsheet.getActiveSheet().getActiveRange().getHeight();
SpreadsheetApp.getActiveSheet().getCurrentCell().offset(2*height2,1).activate();
SpreadsheetApp.getActiveSheet().getCurrentCell().setRichTextValue(SpreadsheetApp.newRichTextValue()
.setText(height2 + ' Students, SIGNED')
.setTextStyle(0, 12, SpreadsheetApp.newTextStyle()
.setBold(true)
.build())
.build());
spreadsheet.getCurrentCell().offset(0, -1, 1, 6).activate();
spreadsheet.getActiveRangeList().setBackground('#e6b8af');
//.setBackground('#d9d9d9')
}
[dashboard][1]
[1]: https://i.stack.imgur.com/cMtHC.png
How to run your script from Dashboard after selecting the specified sheet and time
Modify your function in a way that it takes input from the cells A2 and C2 and D2 from Dashboard
Replace all instances of getActiveSheet() through getSheetByName(name), whereby name is your input from A1
Replace all instances of getActiveRange() through sheet.getRange(), whereby you define the range as as subrange as defined by the dates you retrieved
In order to find the first occurrence of the start date and the last occurrence of the end date - use the methods indexOf() and lastIndexOf()
Make sure that your date notation in Dashboard is the same as in the sheets for correct functionality
Sample:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('After Class')
.addItem('Process Data', 'AfterClass')
.addToUi();
}
function AfterClass() {
var spreadsheet = SpreadsheetApp.getActive();
var dashboard = spreadsheet.getSheetByName("Dashboard");
var sheetName = dashboard.getRange("A2").getValue();
//retrieve the start date to use as desired
var startDate = dashboard.getRange("C2").getDisplayValue();
var endDate = dashboard.getRange("D2").getDisplayValue();
var sheet = spreadsheet.getSheetByName(sheetName);
//chose the range within the specified dates, for this first locate the date column
var startRow = 2;
var dateColumn = sheet.getRange(startRow,1,sheet.getLastRow(), 1);
var dates = dateColumn.getDisplayValues().flat();
var firstRow = dates.indexOf(startDate)+startRow;
var lastRow = dates.lastIndexOf(endDate)+startRow;
//now get the range between (and including) those rows
var range = sheet.getRange(firstRow, 1, lastRow-firstRow+1, sheet.getLastColumn());
//Sorting and removing duplicates
// You need to specify by which column you want to sort your data, in this sample it it column 3 - that it column C
var column = 3;
range.sort({column: column, ascending:true});
range.removeDuplicates([column]);
//now delete empty rows if any
for (var i = range.getHeight(); i >= 1; i--){
if(range.getCell(i, 1).isBlank()){
sheet.deleteRow(range.getCell(i, 1).getRow());
}
}
//Protecting data
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var me = Session.getEffectiveUser();
var description = 'Protected on ' + stringDate + ' by ' + me;
var protection = range.protect().setDescription(description)
//protection.setDomainEdit(false);
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
Important
The sample above will work if your sheet is sorted by dates (ascending) - as you specified in the comments. However, once the data is sorted by column C and not dates anymore it might not work as intended.
Sidenote:
From your code I understand that you recorded it as a macro rather than writing it from scratch (visible by the (unnecessary) calls of activate()).
I very much recommend you to take some time to study Apps Script in roder to understand your code in depth and be able to perform modificaitons and adjustments according to your needs.
There is the basic tutorial for Apps Script in general, samples and explanations for Google Sheets in specific and the references for all available Apps Script methods, whereby most methods feature a code sample.

How do you set up automatic email alerts triggered by date

I am struggling to write an apps script to trigger automatic emails based on a date. I have reviewed other questions/answers but can't get it right. My Google Sheet is below:
Google Sheet
My alert data is in the sheet called H&S Reviews.
My email message is in cell A1 of the Email Alerts Sheet.
The trigger date to send an email is column J of the H&S Reviews sheet and I thought that the code could be written against this and cell J1 which is today's date.
Column J = date email to be sent
Column I = first name of email recipient
Column H = email address
Column B = task per message
Column C = description per message
My code is called functionsendEmails`. You will see that it needs your expertise.
Also, I am hoping that the alert could be written to the email recipients Google Calendar but not sure if this is possible.
Here is the code from your script modified to send the email when date on J column match the date in J1. I recommend you change your code to use getValues() [1] and loop the resulting array instead of using getValue() every time, is much more optimized and will prevent you from hitting the Google quotas[2].
function sendEmails() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("H&S Reviews").activate();
var lastRow = sheet.getLastRow();
var message = spreadsheet.getSheetByName("Email Alerts").getRange(1,1).getValue();
for (var i = 3;i<=lastRow;i++){
var emailAddress = sheet.getRange(i, 8).getValue();
var firstName = sheet.getRange(i, 9).getValue();
var todaysDate = sheet.getRange(1, 10).getValue();
var date = sheet.getRange(i, 6).getValue();
date = Utilities.formatDate(date,'GMT+0200','dd MMMM yyyy');
var task = sheet.getRange(i, 2).getValue();
var description = sheet.getRange(i, 3).getValue();
var messageBody = message.replace("{name}",firstName).replace("{Task}",task).replace("{Description}",description).replace("{Date}",date);
var subject = "Health & Safety Review Task";
var sendDate = sheet.getRange(i, 10).getValue();
var sheetDate = new Date(sendDate);
Sdate=Utilities.formatDate(todaysDate,'GMT+0200','yyyy:MM:dd')
SsheetDate=Utilities.formatDate(sheetDate,'GMT+0200', 'yyyy:MM:dd')
Logger.log(Sdate+' =? '+SsheetDate)
if (Sdate == SsheetDate){
var subject = "Health & Safety Review Task";
MailApp.sendEmail(emailAddress, subject, messageBody);
Logger.log('SENT :'+emailAddress+' '+subject+' '+messageBody)
}
}
}
[1] https://developers.google.com/apps-script/reference/spreadsheet/range#getValues()
[2] https://developers.google.com/apps-script/guides/services/quotas

Google Sheets - Script to move rows to different tab depending on a key word

I have been working on a google sheet that that receives a form submission.
Once the submission comes in I need a script to move the rows of data to different tabs depending on the name shown in column C.
I have extensively searched for solutions on stack overflow and I am very close to a solution right now through the code I have found and edited.
I have a script that will move historic dates to a different tab and leave all future dates in the original tab based in the logic of the date being older than today.
All I need to do now is modify this to move the rows with the name "John" in column C to the John tab and ignore the date.
Once I can get one name to work I am confident I can make this work for multiple names and multiple tabs.
Please feel free to create a copy of the following test sheet I have been working on.
link:
https://docs.google.com/spreadsheets/d/1zJpylrD_5hzScW3lIjIQQSKiY0Aan6Wkm_h_IbVrVXM/edit#gid=0
function MovePastDates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var entrySheet = ss.getSheetByName("Entry Sheet");
var franksSheet = ss.getSheetByName("Franks Sheet");
var lastColumn = entrySheet.getLastColumn();
for(var i = entrySheet.getLastRow(); i > 0; i--){
var dateCell = entrySheet.getRange(i, 1).getValue();
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = entrySheet.getRange(i, 1, 1, entrySheet.getLastColumn()).getValues();
franksSheet.getRange(franksSheet.getLastRow() + 1, 1, 1, entrySheet.getLastColumn()).setValues(rangeToMove);
entrySheet.deleteRow(i);
}
}
}
The final result should be a google sheet that receives form entries.
Each entry will be allocated to a specific person who will only have edit permissions to there own tab only where they can approve/decline requests submitted through the form.
All other users of the sheet will have view only access.
I wanted two scripts:
1) Script to move form submission rows to a specific tab dependent on person’s name
(I was going to set up a trigger every minute for this)
2) Script to move past dates into an historic sheet
(I was going to set up a trigger every night for this)
I have been able to modify your code to achieve the desired function, it may not be the most efficient but it appears to work well.
Script One is:
function moveRowsToNamesSheets() { //Name of function
var sObj={John:'Johns Sheet',Frank:'Franks Sheet',David:'Davids Sheet'}; // Put key work and sheet name here in format eg.( keyWord1: 'sheet name to move keyWord1 to')
var ss=SpreadsheetApp.getActive(); // ??
var esh=ss.getSheetByName('Entry Sheet'); //Sheet data is being pulled form
var fsh=ss.getSheetByName('Franks Sheet'); //unsure why one of the sheets is named here
var erg=esh.getDataRange(); // Not sure of function now that I am not using dates
var evA=erg.getValues(); // ??
var d=0; //??
//var today=new Date(new Date().getFullYear(),new Date().getMonth(),new Date().getDate()).valueOf(); // Didnt need this line
for(var i=1;i<evA.length;i++) { //??
if(/*new Date(evA[i][0]).valueOf() < today*/ evA[i][2]=='John' ||evA[i][2]=='Frank' ||evA[i][2]=='David') { //Keywords used go here, what does the [2] mean?
ss.getSheetByName(sObj[evA[i][2]]).appendRow(evA[i]); //??
esh.deleteRow(i+1-d);
d++; //increments d by one
}
}
}
Script Two is:
function HistoricDates() {
// Initialising
var ss = SpreadsheetApp.getActiveSpreadsheet();
//--------------- Franks Sheets --------------------
var franksSheet = ss.getSheetByName("Franks Sheet");
var PastSheet = ss.getSheetByName("Historic Requests");
var lastColumn = franksSheet.getLastColumn();
// Check all values from your "Franks Sheet" sheet
for(var i = franksSheet.getLastRow(); i > 0; i--){
// Check if the value is a valid date
var dateCell = franksSheet.getRange(i, 4).getValue(); //Dates in column 4
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = franksSheet.getRange(i, 1, 1, franksSheet.getLastColumn()).getValues();
PastSheet.getRange(PastSheet.getLastRow() + 1, 1, 1, franksSheet.getLastColumn()).setValues(rangeToMove);
franksSheet.deleteRow(i);
}
}
}
//---------------------- Johns Sheets -------------------------
var johnsSheet = ss.getSheetByName("Johns Sheet");
var pastSheet = ss.getSheetByName("Historic Requests");
var lastColumn = johnsSheet.getLastColumn();
// Check all values from your "Johns Sheet" sheet
for(var i = johnsSheet.getLastRow(); i > 0; i--){
// Check if the value is a valid date
var dateCell = johnsSheet.getRange(i, 4).getValue(); //Dates in column 4
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = johnsSheet.getRange(i, 1, 1, johnsSheet.getLastColumn()).getValues();
pastSheet.getRange(pastSheet.getLastRow() + 1, 1, 1, johnsSheet.getLastColumn()).setValues(rangeToMove);
johnsSheet.deleteRow(i);
}
}
}
//--------------- Davids Sheets --------------------
var davidsSheet = ss.getSheetByName("Davids Sheet");
var pastSheet = ss.getSheetByName("Historic Requests");
var lastColumn = davidsSheet.getLastColumn();
// Check all values from your "Davids Sheet" sheet
for(var i = davidsSheet.getLastRow(); i > 0; i--){
// Check if the value is a valid date
var dateCell = davidsSheet.getRange(i, 4).getValue();//Dates in column 4
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = davidsSheet.getRange(i, 1, 1, davidsSheet.getLastColumn()).getValues();
pastSheet.getRange(pastSheet.getLastRow() + 1, 1, 1, davidsSheet.getLastColumn()).setValues(rangeToMove);
davidsSheet.deleteRow(i);
}
}
}
}
// Check is a valid date
function isValidDate(value) {
var dateWrapper = new Date(value);
return !isNaN(dateWrapper.getDate());
}
The working spreadsheet is located here:
https://docs.google.com/spreadsheets/d/1VCONRkBpkva-KrFDO2bFV8ZTp1U168QWAGavcKCa_uQ/edit?usp=sharing
I think this is what you want:
function movePastDatesOrJohn() {
var sObj={John:'Johns Sheet',Frank:'Franks Sheet',David:'Davids Sheet'};
var ss=SpreadsheetApp.getActive();
var esh=ss.getSheetByName('Entry Sheet');
var fsh=ss.getSheetByName('Franks Sheet');
var erg=esh.getDataRange();
var evA=erg.getValues();
var d=0;
var today=new Date(new Date().getFullYear(),new Date().getMonth(),new Date().getDate()).valueOf();
for(var i=1;i<evA.length;i++) {
if(new Date(evA[i][0]).valueOf() < today || evA[i][2]=='John') {
ss.getSheetByName(sObj[evA[i][2]]).appendRow(evA[i]);
esh.deleteRow(i+1-d);
d++;
}
}
}
So Franks Sheet and Davids Sheet only get the rows that are older than today. But Johns Sheet gets all of the row that are Johns and disregards the date. I think that's what you wanted.
By the way, did you know that if you more that one form attached to your spreadsheet you can tell which response sheet the formSubmit trigger is writing too, with the event object range? Using sheet name = e.range.getSheet().getName();

Log the user that modified a row in a Google Spreadsheet?

I have the following code which writes the last edit date and time in the last cell of the row:
function onEdit(event)
{
var sheet = event.source.getActiveSheet();
// Note: actRng = return the last cell of the row modified
var actRng = event.source.getActiveRange();
var index = actRng.getRowIndex();
var cindex = actRng.getColumnIndex();
// Note: date = return date + time
// Note: user = return the user email
var dateCol = sheet.getLastColumn();
var lastCell = sheet.getRange(index,dateCol);
var date = Utilities.formatDate(new Date(), "GMT-0300", "dd-MM-yy HH:mm");
//var user = event.user; // Note: event.user will not give you collaborator's Id
var user = Session.getEffectiveUser();
// Note: setcomment = Inset a comment with the user email
// Note: setValue = Insert in the cell the date + time when this row was modified
lastCell.setValue("'" + date).setComment(user);
}
This also comments the modifier email on the last cell. However, this is only commenting the owner's email ID and not the email ID of the person with whom the shared is shared and who has made the change.
How can I log the email id of the person who last edited a row?