Google Script Email loop - email

I am relatively new to programming, and have recently been working on a script to send emails from a google spreadsheet when a cell in a certain column is changed. The recipient is assigned based off of an email address in another column in the same row as the change. I am having difficulty getting my code to stop running after the first email. As it is, the script runs indefinitely (at least until I run out of emails for the day).
Here is the code:
function sendNotification() {
var sheet = SpreadsheetApp.getActiveSheet();
//Get Active cell
var mycell = sheet.getActiveSelection();
var cellcol = mycell.getColumn();
var cellrow = mycell.getRow();
var address = sheet.getRange("C" + cellrow).getValue();
var streetAddress = sheet.getRange("F" + cellrow).getValue();
var startRow = 2;
var numRows = 2000;
// Fetch the range of cells A2:O2000
var dataRange = sheet.getRange(startRow, 1, numRows, 15)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = address; // First column
var message = streetAddress +" Has been Submitted for permitting!"; // Second column
var subject = "The above Address has been Submitted For Permitting! We will Follow up with you when it has been approved.";
//Check to see if column is H to trigger
if (cellcol == 8 && sheet.getName() == "Sheet1" && mycell !== "")
{
//Send the Email
MailApp.sendEmail(emailAddress, message, subject);
}
//End sendNotification
}
}
What can I do to resolve this? Would a loop be the best option? How would I implement this?
How about this approach?
var EMAIL_SENT = "EMAIL_SENT";
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var mycell = e.range;
var cellcol = mycell.getColumn();
var cellrow = mycell.getRow();
var emailAddress = sheet.getRange("C" + cellrow).getValue();
var streetAddress = sheet.getRange("F" + cellrow).getValue();
var subject = "The above Address has been Submitted For Permitting! We will follow up with you when it has been approved."
// Fetch values for each row in the Range
var message = streetAddress +" Has been Submitted for permitting!";
var emailSent = sheet.getRange("O" + cellrow).getValue();
if ( cellcol == 8 && sheet.getName() == "Sheet1" && emailSent != EMAIL_SENT) { // Prevents sending duplicates
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(cellrow, 15).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}

If you use the onEdit() the function will be triggered every time you edit the cell. Is this what you want?
What I assume you're looking for is a script that:
Reads every row of the active sheet
If the 8th column is not empty, sends an e-mail to the address in the first column
The whole script is trigger upon the user requests (not when the user edits a cell)
In this case the first approach is better, the sendNotification(). Also a loop is necessary to read all the rows. And the IF statement should be something like if (row[8] != "") then send the e-mail.
In this case row which was defined as row = data[i] in your first script will have the values of all the cells in the row being read in the loop. So row[8] will have the value of the 8th (column H), which you want to check for emptiness, thus if(row[8] != "").
Also, if I understand correctly the e-mail adress should be emailAddress = row[1] inside the loop, because the email address is different every row.

Your second approach var EMAIL_SENT = "EMAIL_SENT"; is almost similar to the simple extension of the code that sets the cells in a column to 'EMAIL_SENT' for each row after sendEmail is called given in Section 2: Improvements. Within tutorial, each cell was marked in each row every time an email is sent. With that, you should mark edited cells as unsent then you will be able to re-run the script later on, avoid sending email duplicates and will only send the edited cells.
To make your code more efficient and help you improve the performance of your scripts, there are also list of best practices given.

Related

E-mail notification based on cell value. Unable to apply script function for all rows

I am using Google sheets and the Google script editor to create a script to automatically send myself an e-mail every time a product quantity goes below the minimum inventory level. Since I have multiple products with different minimum inventory levels I expect to get a series of e-mails, one for each row.
I use one sheet for the actual Inventory data and another sheet that contains information for the script to refer to, such as my email and what message to include in the e-mail.
I succeeded having an e-mail sent collecting data from the first row of the Inventory sheet but I am not being able to apply that for all the following rows.
I tried changing the .getRange("F2") to .getRange("F2:F"), then whenever one of the products goes under the minimum inventory level I get one single e-mail containing the information about all products, regardless of whether their quantity is under the minimum level or not.
The ideal solution would be ONE single e-mail containing all the information about all products that are under the minimum quantity .
Here is a link to my spreadsheet: https://docs.google.com/spreadsheets/d/1ZHmBvi8ZeaDRYq6Qigaw08NUiOwZumPrLnvnka_mgmA/edit?usp=sharing
Current script:
function CheckInventory() {
// Fetch inventory quantity
var InventoryRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Inventory").getRange("F2");
var Inventory = InventoryRange.getValue();
// Fetch minimum quantity
var MinimumQuantityRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Inventory").getRange("D2");
var MinimumQuantity = MinimumQuantityRange.getValue();
// Check Inventory
if (Inventory < MinimumQuantity){
// Fetch email address
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Notification Rules").getRange("E2");
var emailAddress = emailRange.getValues();
// Fetch email message details.
var detailsRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Notification Rules").getRange("G2");
var details = detailsRange.getValues();
var subjectdetailsRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Notification Rules").getRange("H2");
var subjectdetails = subjectdetailsRange.getValues();
// Send Alert Email.
var message = details;
var subject = subjectdetails;
MailApp.sendEmail(emailAddress, subject, message);
}
}
Update:
As you said in a comment, you want the email to be sent when you edit an inventory cell, and only if this edited inventory quantity is below the corresponding minimum. So here I update my answer accordingly.
First, I guess you have done this already, but because the function uses sendEmail, you will have to grant authorization for it to work. I created a trigger for that. Run this once:
function createEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("CheckInventory")
.forSpreadsheet(ss)
.onEdit()
.create();
}
Then, this is the function that will run every time the spreadsheet is edited. To avoid the email to be sent every time the file is edited, we need a condition that checks whether the edited cell is an inventory and that this inventory is below the minimum:
function CheckInventory(e) {
var ss = e.source;
var inventorySheet = ss.getSheetByName("Inventory");
var rowIndex = e.range.getRow();
var columnIndex = e.range.getColumn();
var numCols = 6;
var row = inventorySheet.getRange(rowIndex, 1, 1, numCols).getValues()[0];
var editedInventory = row[5];
var editedMinimum = row[3];
var sheetName = ss.getActiveSheet().getName();
// Checking that: (1) edited cell is an inventory quantity, and (2) Inventory is below minimum
if(editedInventory <= editedMinimum && sheetName == "Inventory" && columnIndex == 6 && rowIndex > 1) {
var inventoryValues = inventorySheet.getDataRange().getValues();
var emailBody = "";
for(var i = 1; i < inventoryValues.length; i++) {
var inventory = inventoryValues[i][5];
var minimum = inventoryValues[i][3];
if(inventory <= minimum) {
var productName = inventoryValues[i][0] + " " + inventoryValues[i][1];
var productUnits = minimum + " " + inventoryValues[i][4];
var messagePart1 = "Inventory for " + productName + " has gone under " + productUnits + ". ";
var messagePart2 = "Organise purchase order. Inventory as of today is: " + inventory + " " + inventoryValues[i][4];
var message = messagePart1.concat(messagePart2);
var newItem = "<p>".concat(message, "</p>");
emailBody += newItem;
}
}
var emailSubject = "Low inventory alert";
var emailAddress = "your-email#your-domain.com";
// Send Alert Email
if(emailBody != "") {
MailApp.sendEmail({
to: emailAddress,
subject: emailSubject,
htmlBody: emailBody
});
}
}
}
As I said in a comment, if you just want to send an email, it doesn't make sense to have many email subjects, so I hardcoded the subject.
Regarding the emails, I assumed you just want an email address to receive the email, so I hardcoded it too. If you want all the different email addresses found in Notifications tab to receive emails regarding all products, a small fix would be needed to this code. Tell me if that's needed for you.
Also, I didn't use your notifications tab at all, I created the message directly using the script. I'm not sure it is that useful to have the sheet "Notification Rules". Much of its info is the same as the one in inventory, and the rest of data (email address, basically) could be easily included there. But whatever suits you.
I hope this is of any help.

Script to send email based on cell value equaling today's date

I have been trying to retrofit an old code I have used to send emails before to send an email when the formula solution in a cell equals today's date in another cell.
Here is my code:
function sendtest(e) {
var ss = e.source;
var s = ss.getSheetByName("Test");
var r = e.range;
// Below is the line that selects what column to use for the send date
var actionCol = 2;
var colIndex = r.getColumnIndex();
// Testing if statement
if (e.value == s.getRange(2, 3).getValue () && colIndex == actionCol) {
// All rows below are used to send the email
var row = r.getRowIndex();
// This takes the email addresses in column D
var userEmail = s.getRange(row, 4).getValue ();
//The subject line of the email
var subject = "Test test " + s.getRange(row, 1).getValue ();
//The structure of the body of the email
var body = "It has been two weeks since test test, " + s.getRange(row, 1).getValue ();
body +="\n\ntest test?";
body +="\n\ntest test,";
body +="\n\ntest test";
// This line sends the email
MailApp.sendEmail(userEmail,subject,body);
}
}
EDIT: Sorry I forgot to state what is going wrong. Every time I attempt to trigger this script (time based), I get an error telling me that line 3 (get sheet by name) is undefined.

I want to use the content of a cell in a google sheet to control whether to send an e-mail out or not

I have a spreadsheet for recording customer samples. A column [I] is used to calculate when the original entry date is over a certain period which then displays an overdue message using an if statement. I want to trigger a script to check this column daily and if 'overdue' is found to send an email to an address contained in another column [C]. I am also using another column [J] to return a message when an email has been sent. I am using the script below but am returning errors
The script marks all rows in column J with the sent email message
I appear to be collecting a bigger array of data have needed [adding two rows the the bottom]
Anyone any ideas?
//Setup the function
function autoMail () {
var ss = SpreadsheetApp.getActiveSpreadsheet(); //Get the active spreadsheet
var sheet = ss.getSheetByName( 'TS_Requests' ); //Get the active spread
var startRow = 3; // Start of row that going to be process considering row 2 is header
var lastRow = sheet.getLastRow(); // Get the last row of data to be process
var lastColumn = sheet.getLastColumn(); // Get the last column of data to be process
var range = sheet.getRange(startRow,1,lastRow,lastColumn); //Getting the specific cell to be process
var data = range.getValues(); // Get the values inside the cell
for(var i=0;i<data.length;++i){
var row = data[i];
var email = row[2]; // Grab the email column. C in this case
var customer = row[3]; // Grab data for Email body
var item = row[5]; // Grab data for Email body
var description = row[6]; // Grab data for Email body
var status = row[8]; // Grab the column containing the value needed to be checked.
var EmailSent = row[9]
if (EmailSent != 'Email_Sent') { //Prevents e-mail being sent in duplicate
if(valstatus ='Overdue'){ // Check the values, if it's what's needed, do something
MailApp.sendEmail(email, 'Your sample request is overdue', customer + ' - ' + item + ' - ' + description); // if condition is met, send email
sheet.getRange(startRow + i, 10).setValue('Email_Sent');
}
}
}
}

How to send e-mails automatically from a sheet as it updated?

I posted this question on Google Sheets forum and was directed here for help.
*Please note that I am not a coder, I've just started working with things like this.
I've created a workbook with 3 pages. It's connected to a form, so as people submit their reviews the answers are collected on the first sheet, calculated on the second sheet, and any submission with an overall score higher than 3.9 has the email address moved to the third sheet where it will be sent a follow-up email.
Here is a link to a copy of the sheet:
https://docs.google.com/spreadsheets/d/15p7_M4guRWCVHG-acRAheAROmh9t4wk3GWD_x0_rl90/edit?usp=sharing
It all works except the last page: I cannot figure out how to get the emails to send automatically as the sheet is updated. I've set the trigger to have it run when the sheet is updated, but to no avail - I have to manually go in and click the "Run" button to have the emails sent. (When I do, it pops up 'Failed to sent email: no recipient (line 21, file "Code")' but the emails are successfully delivered anyway.)
The formula I am using is this: (I have also tried swapping "sendEmails2" with "autoResponder" but that didn't change anything either.) Can anyone tell me what is wrong with it? Or is there a better way to automatically send the e-mails?
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = "EMAIL_SENT";
function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 20000; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 3)
// 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 emailAddress = row[0]; // First column
var message = row[1]; // Second column
var emailSent = row[2]; // Third column
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = "Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is
interrupted
SpreadsheetApp.flush();
}
}
}
This should do it:
function sendEmails2(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Automatic Emails');
var rg=sh.getRange(2,1,sh.getLastRow(), 3);
var vA=rg.getValues();
for (var i=0;i<vA.length;i++){
var emailAddress = vA[i][0];
var message = vA[i][1];
var emailSent = vA[i][2];
var subject = "Sending emails from a Spreadsheet";
if(emailSent != 'EMAIL_SENT'){
MailApp.sendEmail(emailAddress, subject, message);
sh.getRange(2 + i,3).setValue('EMAIL_SENT');
}
}
}
Don't forget the onFormSubmit:

Google script: email with answers given by a single user to a form compiled in google drive

I've created a form using google drive.
In order to provide to answerers a summary of what they wrote I would like to send an email to each of them, using the email they insert in the form.
I think it is possible to do this installing a script in the spreadsheet of the form result that send an email every time a new row is recorded.
I've found (on this forum) the following script that is, unfortunately, an email alert with a modify in a certain spreadsheet.
function sendNotification() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = ss.getActiveCell().getA1Notation();
var row = sheet.getActiveRange().getRow();
var cellvalue = ss.getActiveCell().getValue().toString();
var recipients = "me#gmail.com";
var message = '';
if(cell.indexOf('G')!=-1){
message = sheet.getRange('D'+ sheet.getActiveCell().getRowIndex()).getValue()
}
var subject = 'Update to '+sheet.getName();
var body = sheet.getName() + ' has been updated. Visit ' + ss.getUrl() + ' to view the changes on row: «' + row + '». New comment: «' + cellvalue + '». For message: «' + message + '»';
MailApp.sendEmail(recipients, subject, body);
};
And here is the help I need. The script sends an email to a single predefined address. Is it possible to let the script take the recipient email from a certain cell in the relevant row (for example the email is recorded in the col C of the spreadsheet) and send him a message that contains some text taken by other cells of the same row ( for example col D and E)?
Using the variables you defined in your code (sheet, row):
//returns email address in column C
var recipient = sheet.getRange(row, 3).getValue();
//returns values in column D and E
var info1 = sheet.getRange(row, 4).getValue();
var info2 = sheet.getRange(row, 5).getValue();
Please be aware that this is an 'expensive' way to get the values and you would better served getting all values as an array in one call and then referencing them by index, as below.
//get all required values in one call
var values = sheet.getRange(row, 3, 1, 3).getValues();
//define variables using array index
var recipients = values[0];
var info1 = values[1];
var info2 = values[2];