I've created a Google spreadsheet that includes some basic script which emails someone when a task to which they are assigned comes due. The script works on the original sheet but when I duplicate the sheet and change nothing but the dates the script no longer works.
I'm not receiving any errors, the emails simply are not being sent.
I'm very new to Google script and wasn't able to find a good answer for this. Does anybody know what the issue might be?
Thanks so much!
function myFunction() {
var date = new Date();
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
var FormattedTodayDate = Utilities.formatDate(date,'GMT-0600','MM/dd/yyyy')
//Here we get all the spreadsheets from the spreadsheet app
var spreadSheets = SpreadsheetApp.getActiveSpreadsheet();
//Here we pick out the first spreadsheet. 0 is the first tab of the spreadsheet "Active Sheet"
var currentSheet = spreadSheets.getSheets()[0];
//Here we set up where on the sheet we want to get the data
var startRow = 3;
var numRows = currentSheet.getLastRow()-1;
var numCols = currentSheet.getLastColumn();
//We use those numbers to find a range throughout the table (A2 - H* etc) where we want to grab the data from
var dataRange = currentSheet.getRange(startRow, 1, numRows, numCols);
//Using the data range, grab all the values
var data = dataRange.getValues();
for(i in data){
var row = data[i];
var reminderDate = new Date(row[1]);
var dueDate = new Date(row[3]);
var FormattedReminderDate = Utilities.formatDate(reminderDate,'GMT-0600', 'MM/dd/yyyy')
var FormattedDueDate = Utilities.formatDate(dueDate,'GMT-0600', 'MM/dd/yyyy')
if(row[0] === ''){
// Logger.log(FormattedTodayDate);
//
// Logger.log(FormattedReminderDate);
//
// Logger.log(FormattedReminderDate == FormattedTodayDate);
if(FormattedReminderDate == FormattedTodayDate){
MailApp.sendEmail({
to: row[7],
subject: row[4] + " task due on " + FormattedDueDate,
htmlBody: "The following " + row[4] + " task is coming due on <b>" + FormattedDueDate + "</b> : <br/> <br>" + row[5] + "<br> </br>Go to Google Spreadsheet for more details: https://docs.google.com/spreadsheets/d/1jIBX5By1jhro1V-gHXPPlhIOXDLut6CMvaq92uW5bFw/edit#gid=0&range=" + row[8] + "<br/><br> Please remember to add the word 'done' to the 'done?' column when you complete this task.",
})
}
if(FormattedDueDate == FormattedTodayDate){
MailApp.sendEmail({
to: row[7],
subject: row[4] + " task due TODAY",
htmlBody: "The following " + row[4] + " task has not been completed and is due TODAY: <br/> <br>" + row[5] + "<br> </br>Go to Google Spreadsheet for more details: https://docs.google.com/spreadsheets/d/1jIBX5By1jhro1V-gHXPPlhIOXDLut6CMvaq92uW5bFw/edit#gid=0&range=" + row[8] + "<br/><br> Please remember to add the word 'done' to the 'done?' column when you complete this task.",
})
}
}
}
}
Thanks everyone. Turns out this issue was caused by our spam filter blocking the notifications.
Related
been using MailApp script to automatically send emails from within a google sheet for several years. developed my script from an example i found online and reverse engineered it until i could make it work (i am not a coder!). worked brilliantly for months/years...
but recently it is no longer functioning. i get a daily email summary of errors which states that EMAIL_SENT is not defined, but when running the script in debug mode it times out after the logger.log(user) - so does appear to hang on the EMAIL_SENT variable.
As i say, this has been running brilliantly for many months, but appears to have stopped around july 22nd.
i have included the script below in case it is something simple:
function autoemailES() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ESautoemail");
//var lastRow = sheet.getLastRow();
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow() -1; // Number of rows to process
var EMAIL_SENT = 'EMAIL_SENT';
// Fetch the range of cells A2:G
var dataRange = sheet.getRange(startRow, 1, numRows, 30);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailSent = row[7]; // 8th column
if (emailSent !== EMAIL_SENT) { // Prevents sending duplicates
var superMail = row[2]; // 3rd column
var responseNo = row[3]; // 4th column
var user = row[0]; // 1st column
var firstName = row[1]; // 2nd column
var pdfLink = row[5]; // 6th column
var pdfID = row[6]; // 7th column
Logger.log(responseNo) //these logger lines just display the values for each of the variables in debugging
Logger.log(superMail)
Logger.log(user)
Logger.log(pdfLink)
Logger.log(pdfID)
var file = DriveApp.getFileById(pdfID);
var subject = 'IBTPHEM MSF results for ' + user;
var message = 'Dear Educational Supervisor,' + "\n" +
'This is an automated notification that your trainee, ' + firstName + ', has now received a quorate number of MSF responses.'+"\n"+
'The anonymised summary of responses is attached in PDF format, or can be downloaded from the link below.'+"\n" + pdfLink +"\n"+
'Further submissions will be added automatically (the trainee is notified for each), but the pdf will need downloading again'+"\n"+
'Please feel free to share this with your trainee however you see fit - we are now releasing the results via the ES in case there is any feedback that may require debriefing or handling more delicately.'+"\n"+
'Please do let me know of any problems or concerns?'+"\n"+
'Kind Regards,' + "\n" +
'IBTPHEM MSF'
MailApp.sendEmail(superMail,subject, message,{
name: 'IBTPHEM MSF',
replyTo: 'dr_nick#doctors.org.uk',
attachments: [file.getAs(MimeType.PDF)]});
sheet.getRange(startRow + i, 8).setValue(EMAIL_SENT);
SpreadsheetApp.flush();
}
}
}
I would be really grateful for any help to identify my problem.
thanks!
dr_nick
(i cannot easily share the sheet in its entirity as it contains a fair bit of senstive/confidential information - though I suppose I could make an anonymised version if this is necessary/helpful?)
This is the same script with a try catch block. It will only log things if you have an error.
function autoemailES() {
const ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("ESautoemail");
var startRow = 2;
var EMAIL_SENT = 'EMAIL_SENT';
var dataRange = sheet.getRange(startRow, 1, sheet.getLastRow() - startRow + 1, 30);
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
try{
var row = data[i];
var emailSent = row[7];
if (emailSent !== EMAIL_SENT) {
var superMail = row[2];
var responseNo = row[3];
var user = row[0];
var firstName = row[1];
var pdfLink = row[5];
var pdfID = row[6];
var file = DriveApp.getFileById(pdfID);
var subject = 'IBTPHEM MSF results for ' + user;
var message = 'Dear Educational Supervisor,' + "\n" +
'This is an automated notification that your trainee, ' + firstName + ', has now received a quorate number of MSF responses.' + "\n" +
'The anonymised summary of responses is attached in PDF format, or can be downloaded from the link below.' + "\n" + pdfLink + "\n" +
'Further submissions will be added automatically (the trainee is notified for each), but the pdf will need downloading again' + "\n" +
'Please feel free to share this with your trainee however you see fit - we are now releasing the results via the ES in case there is any feedback that may require debriefing or handling more delicately.' + "\n" +
'Please do let me know of any problems or concerns?' + "\n" +
'Kind Regards,' + "\n" +
'IBTPHEM MSF'
MailApp.sendEmail(superMail, subject, message, {
name: 'IBTPHEM MSF',
replyTo: 'dr_nick#doctors.org.uk',
attachments: [file.getAs(MimeType.PDF)]
});
sheet.getRange(startRow + i, 8).setValue(EMAIL_SENT);
SpreadsheetApp.flush();
}
}
catch(e){
Logger.log('responseNo: %s\nsuperMail: %s\nuser: %s\npdfLink:%s\npdfID: %s',responseNo,superMail,user,pdfLink,pdfID);
Logger.log(e);
}
}
}
Try setting up another spreadsheet and adjust the data so that it doesn't send data to your clients and see if it will generate any errors. Also if you could share a copy of the new spreadsheet as a markdown table that would enable us to test it a little. But be careful about sharing private information
When I inserted query function into the sheet from which it pulls the data, I began to get blank emails when the trigger ran. When I go into the script editor and debug or run, I get the email the way it is programmed to run
Email I receive on scheduled send:
Email I receive when sent from Script Editor
Here is the code I am running:
function sendEmail() {
//setup function
var ActiveSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var StartRow = 2;
var aVals = ActiveSheet.getRange("A1:A").getValues();
var aLast = aVals.filter(String).length;
var RowRange = aLast - StartRow + 2;
var WholeRange = ActiveSheet.getRange(StartRow, 1, RowRange, 10);
var AllValues = WholeRange.getValues();
var message = "";
//iterate loop
for (i in AllValues) {
//set current row
var CurrentRow = AllValues[i];
//define column to check if sent (starts from "0" not "1")
var hasTracking = CurrentRow[8];
//if row has been sent, then continue to next iteration
if (hasTracking !== "")
continue;
//set HTML template for information
message +=
"<p><b>Order: </b>" + CurrentRow[0] + "</p>" +
"<p><b>Name: </b>" + CurrentRow[4] + "</p>" +
"<p><b>Date Initiated: </b>" + CurrentRow[5] + "</p>" +
// "<p><b>Date Initiated: </b>" + Utilities.formatDate(CurrentRow[5], "GMT", "MM-dd-YYYY") + "</p>" +
"<p><b>Gorgias Link: </b>" + CurrentRow[6] + "</p><br><br>";
//set the row to look at
var setRow = parseInt(i) + StartRow;
//mark row as "sent"
//ActiveSheet.getRange(setRow, 11).setValue("sent");
}
//define who to send grants to
var SendTo = "XXX#XXX.com";
//set subject line
var Subject = "Returns Needing Processed";
//send the actual email
MailApp.sendEmail({
to: SendTo,
cc: "",
subject: Subject,
htmlBody: message,
});
}
Don't use .getActiveSheet() when you run the function via a trigger.
The reason is that .getActiveSheet() gets the active sheet of your file which can be any sheet.
Instead use .getSheetByName().
Replace:
var ActiveSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
with:
var ActiveSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
adjust Sheet1 with your specific case.
I am trying to get an e-mail to be sent when the value in a cell that contains a formula goes below a certain number.
I have achieved this for various other spreadsheets that have the cell value manually entered, but for this specific one sheet where the value on the cell results from a simple SUM formula, the e-mail is not being sent. Now if I enter the value manually into the cell, the e-mail is sent right away.
Below is the code and here is a copy of my spreadsheet:
https://docs.google.com/spreadsheets/d/1gnmJfkmIKHyNqLqTFK-bdOZnh2Ejkia6xo3r_3cjwkk/edit?usp=sharing
function CheckBasketsInventory(e) {
var ss = e.source;
var inventorySheet = ss.getSheetByName("Baskets");
var rowIndex = e.range.getRow();
var columnIndex = e.range.getColumn();
var numCols = 11;
var row = inventorySheet.getRange(rowIndex, 1, 1, numCols).getValues()[0];
var editedInventory = row[10];
var editedMinimum = row[8];
var sheetName = ss.getActiveSheet().getName();
// Checking that: (1) edited cell is an inventory quantity, and (2) Inventory is below minimum
if(editedInventory <= editedMinimum && sheetName == "Baskets" && columnIndex == 11 && rowIndex > 1) {
var inventoryValues = inventorySheet.getDataRange().getValues();
var emailBody = "";
for(var i = 1; i < inventoryValues.length; i++) {
var inventory = inventoryValues[i][10];
var minimum = inventoryValues[i][8];
if(inventory <= minimum) {
var productName = inventoryValues[i][0] + " " + inventoryValues[i][1] + " " + inventoryValues[i][2];
var productUnits = minimum + " " + inventoryValues[i][9];
var messagePart1 = "Inventory for " + productName + " has gone under " + productUnits + ". ";
var messagePart2 = "Organise purchase order. Inventory as of today is: " + inventory + " " + inventoryValues[i][9];
var message = messagePart1.concat(messagePart2);
var newItem = "<p>".concat(message, "</p>");
emailBody += newItem;
}
}
var emailSubject = "Low inventory alert";
var emailAddress = "danielrzg#gmail.com";
// Send Alert Email
if(emailBody != "") {
MailApp.sendEmail({
to: emailAddress,
subject: emailSubject,
htmlBody: emailBody
});
}
}
}
Thank you.
As you can see in the documentation:
Script executions and API requests do not cause triggers to run. For
example, calling FormResponse.submit() to submit a new form response
does not cause the form's submit trigger to run.
This includes values edited by a Sheet Formula not triggering an onEdit trigger.
I'm using the following script to send an email every time a "No" changes to "Yes" in a particular column.
However this only happens if the cell is manually changed to "Yes", typed into a cell. And triggers based on the on edit UI trigger.
But the cell updates based on a formula that references to another sheet where it takes the "No" and "Yes" from, so there is no manual editing/updating on the cell in this sheet.
How can I get it to send the email without any manual change to cells, only on the change from "No" to "Yes"
Any help would be appreciated.
function sendNotification(e){
var s = SpreadsheetApp.getActiveSpreadsheet();
var ss = s.getSheetByName("Order Details")
if(e.range.getColumn()==3 && e.value=='Yes'){
var cell = ss.getActiveCell();
var row = cell.getRow();
var ordernumber = ss.getRange(row,4).getValue(); //Column D
var sku = [{}]; sku = ordernumber.toString().split("-")
var sizewidth = ss.getRange(row,5).getValue(); //Column E
var sizeheight = ss.getRange(row,6).getValue(); //Column F
var qty = ss.getRange(row,8).getValue(); //Column H
var country = ss.getRange(row,10).getValue(); //Column J
var tube = ss.getRange(row,9).getValue(); //Column I
var paintingimage = ss.getRange(row,7).getValue(); //Column G
var orderlink = ('http://testly/Km345TS');
MailApp.sendEmail({
to: "xxx#gmail.com",
subject: country + " New Order No. " + ordernumber, // note the spaces between the quotes...
//attachment: [file,blob],
htmlBody: "Hello XYZ, <br><br>"+
"Please find order details below. <br><br>"+
sku[1] + "<br><br>" +
"Size - Width: " + sizewidth + " x " + "Height: " + sizeheight + "<br><br>"+
"Quantity - " + qty + "<br><br>"+
"- It needs to be tube rolled"+ "<br>" +
"- Shipment to " + country + "<br>" +
"- Order image is " + paintingimage + "<br><br>" +
"Please fill in cost and delivery details at this link " + orderlink + "<br><br>" +
"The order is for a customer from " + country + "<br><br>" +
"Thanking you, <br>" +
"ABC",
})
}
}
Update: Solution - a big thank you to Ron.
function sendNotification2(){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet 1");
var data = sSheet.getRange(2, 1, sSheet.getLastRow(), sSheet.getLastColumn()).getValues(); //get the values of your table, without the header
var EMAIL_SENT = 'EMAIL_SENT';
for (var i = 0; i < data.length; i++) {
var row = data[i];
var send = row[16]; //Column Q
var emailSent = row[17]; //Column R
var ordernumber = row[4]; //Column E
var country = row[10]; //Column K
var orderlink = ('http:/testly/Khghgy');
var shipaddress = row[18]; //Column S
if (send == "Yes"){
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
MailApp.sendEmail({
to: "xx#gmail.com",
subject: country + " Ship Order No. " + ordernumber, // note the spaces between the quotes...
htmlBody: "Hello XYZ, <br><br>"+
"Thanking you, <br>" +
"ABC",
})
sSheet.getRange(i+2, 18).setValue(EMAIL_SENT);
}
}
}
}
If you want to have the email triggered on the edit change, you could create a separate function that watches for the edit on the other sheet, and then call the above function to send the email.
Something like:
function onEdit(){
var s = SpreadsheetApp.getActiveSpreadsheet();
var ss = s.getSheetByName("Other Sheet Name");
if(e.range.getColumn()==3 && e.value=='Yes'){ //change this if the data setup is different on the other sheet
sendNotification();
}
}
The issue is that this setup will then send an email to every 'Yes' on the email sheet. This can be rectified using an 'Email_Sent' indicator (you can look that up - lots of examples available).
**Another option, as I mentioned in the last question, would be to have the sendNotification function triggered every minute, 5 minutes, 10 minutes, or ??? This won't provide immediate emails, but it would be nearly so.
I am still learning the ropes here. Based on code suggested by other contributors, I put together a script to send reminder emails to consultants who record their time entries using a Google Form. The spreadsheet first imports calendar entries with all the Job information for each consultant. After the calendar entries are imported, if the consultant has not yet recorded their time entry, the following script will send them an email with a reminder to do so:
function sendReminder() {
var rmndrFrom = "XYZ, Inc.";
var myemail = "support#xyz.com";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var numRows = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var dataRange = sheet.getRange(2, 1, numRows, lastCol); // row 1 is the header row
var sheetData = dataRange.getValues();
for (var i = 0; i < sheetData.length; ++i) {
var row = sheetData[i];
if (row[0]){
var jobNumb = row[0]; // Job Number
var conName = row[2]; // Consultant Name
var conMail = row[3]; // Consultant Email
var jobDate = row[4]; // Date
// format email string
var subject = "Time Entry Reminder: " + conName + " / Job " + jobNumb;
try {
var conMsgH = 'This is a reminder for you to record your time entry for Job #<strong>' + jobNum + '/' + jobDate + '</strong>';
// strip HTML for plain text message
var conMsgP = conMsgH.replace(/\<br\/\>/gi, '\n').replace(/(<([^>]+)>)/ig, "");
// send reminder to consultants
MailApp.sendEmail(conMail, subject, conMsgP, { htmlBody:conMsgH, name:rmndrFrom });
} catch (e) { // error handler
MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
}
}
So basically, this script parses the Pending sheet, if column A has a Job Number, it will send a reminder email to the consultant with that Job Number. However, a single consultant may have several job numbers to their name. I would like the script to send a single email to each consultant with a list of the Job Numbers for which they have to record their time entries.
Thanks in advance for your kind help. Any suggestions on how to optimize the code will also be very much appreciated.
There are a number of ways that you can approach this. One way is to keep a sheet with the consultants emails, names and a list of their job numbers. Load this data into your script, a list of all job ids and the job info. Then filter the job ids based on the consultants list and build your email, or you could just send that list of numbers for a very short script.
Another way is to do all of that sorting per consultant in the code and send out the emails that way. This is the approach I've taken, and I've also made use of the iterative JS functions map, filter and reduce more details at MDN.
The code is posted below, but if you would like to take a look at it attached to a spreadsheet and commented (as well as the functions to build that extra sheet with just the consultants info on it) take a look here.
Below is my iteration of your function. I hope it is helpful for your situation:
var rmndrFrom = "XYZ, Inc.";
var myemail = "me#emailisawesome.com";
var subject = "Time Entry Reminder";
function sendReminder() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var numRows = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var sheetData = sheet.getRange(2, 1, numRows-1, lastCol).getValues();
var cons = sheet.getRange(2,3,numRows-1,1).getValues().reduce(flatten_).filter(getUniqueConsultants_);
cons.forEach(sendEmail_, sheetData);
}
function sendEmail_(consultant) {
var consultantsJobs = this.filter(getJobsForConsultant_, consultant);
var jobList = consultantsJobs.map(buildJobLine_).join("<br>");
try {
var conMsgH = "Hi " + consultant + ",<br>";
conMsgH += "This is a reminder for you to record your time entry for the following jobs:<br><br>";
conMsgH += jobList;
conMsgH += "<br><br>Thank you for your cooperation.";
var conMsgP = conMsgH.replace(/\<br\/\>/gi, '\n').replace(/(<([^>]+)>)/ig, "");
MailApp.sendEmail(consultantsJobs[0][3], subject, conMsgP, {htmlBody:conMsgH, name:rmndrFrom});
} catch (e) {
MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
function buildJobLine_(job) {
return "Job #" + job[0] + " on " + Utilities.formatDate(job[4], Session.getTimeZone(), "MMM dd yyyy");
}
function getJobsForConsultant_(row) {
return row[2] == this;
}
function getUniqueConsultants_(v,i,a) {
return a.indexOf(v) == i;
}
function flatten_(a,b) {
return a.concat(b);
}
I must say that fooby's answer is far beyond my JS skills, I'm sure it will work nicely but I still feel like proposing something different (and simpler from my pov), just for the fun of it ;-)
The main difference with your original script is the sorting of the array that allowed me to detect duplicate names and threat it accordingly. The html composition could be far better for sure but that was not your main request.
Here is the code
function sendReminder() {
var rmndrFrom = "XYZ, Inc.";
var myemail = "support#xyz.com";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var initial = "Hi conName,<BR>This is a reminder for you to record your time entry for the following jobs : <BR><BR><table border = 1 cellpadding = 3 bgcolor='#FFFFBB'><TR><TD>";
var sheetData = sheet.getDataRange().getValues();// simplified code to get the data array
sheetData.shift();//skips the headers
sheetData.sort(function(x,y){
var xp = x[2];// sort on column 3 but you can change here...
var yp = y[2];// sort on column 3 but you can change here...
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort ascending, eventually change here...
});
// Logger.log(sheetData);// sorted
var last = sheetData.length
var i = 1;//index 0 is handled outside the loop
var row = sheetData[0];
var subject = "Time Entry Reminder: " + row[2] + " / Job "
var conMsgH = initial
var msgComponent = makeline_(row)
subject += msgComponent[0]
conMsgH += msgComponent[1]
while (i<last){
if(sheetData[i][2]!=sheetData[i-1][2]||i==last-1){
sendData_(sheetData[i-1][3],sheetData[i-1][2],subject,conMsgH)
var subject = "Time Entry Reminder: " + sheetData[i][2] + " / Job "
var conMsgH = initial;
}
msgComponent = makeline_(sheetData[i])
subject += msgComponent[0]
conMsgH += msgComponent[1]
++i
}
}
function sendData_(conMail,conName,subject,conMsgH){
conMsgH = conMsgH.substr(0,conMsgH.length-8)+'</TABLE>'
conMsgH = conMsgH.replace('conName',conName)
var conMsgP = conMsgH.replace(/<\/tr>/ig, '\n').replace(/<br>/ig, '\n').replace(/(<([^>]+)>)/ig, "")
subject = subject.substr(0,subject.length-2);// remove the last '+ '
// Logger.log(subject)
// Logger.log(conMsgH)
Logger.log(conMsgP)
// Logger.log(conMail)
try{
// send reminder to consultants
MailApp.sendEmail(conMail, subject, conMsgP, { htmlBody:conMsgH, name:rmndrFrom });
} catch (e) { // error handler
// MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
function makeline_(row){
var jobNumb = row[0]; // Job Number
var conName = row[2]; // Consultant Name
var conMail = row[3]; // Consultant Email
var descr = row[1]; // description
var FUS1=new Date(row[4]).toString().substr(25,6)+':00';// get timezone of this event, taking care of daylight savings
var jobDate = Utilities.formatDate(row[4], FUS1, "MMM dd yyyy # hh:mm aaa"); // Date
var subject = jobNumb+' + ';
var conMsgH = 'Job #'+jobNumb + '</TD><TD>' + jobDate + '</TD><TD>' + descr + '</TD></TR><TR><TD>';
return [subject,conMsgH];
}
EDIT : made some improvement in the mail format, used a table to show jobs & dates + removed some bugs ;-) (to be honest, I made this also for my personal use as I am having almost the same use case )