Email notification when changes are made to specific data in Google sheets - email

I have a general script that will send email notification of changes made to a live roster in Google sheets and have set up a trigger for the event to occur 'On change'. All data in the sheet is retrieved via IMPORTRANGE. This script is performing as expected, however I wish to make a couple of changes that am not sure how to go about. Here is a sample sheet: link to sheet
Wish to only send email notification when changes concern the name 'Craig'. For example my name is either added or taken off a rostered day. The script currently sends emails for all changes made across the sheet.
ID the row that this change occurs so as to reference the date and venue in the email body.
Thanks for any help and suggestions.
Full script:
function onEdit() {
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data );
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your live roster', 'Previous value: ' + lenProp + ' New value: ' + data);
}
}
New revised script:
function installedOnEdit(e) {
var range = e.range;
var value = range.getValue();
if (value == "Craig" || (e.oldValue == "Craig" && value == "")) {
var rowNumber = range.getRow(); // You can retrieve the row number of the editor cell.
console.log(rowNumber)
var [colA, colB] = e.source.getActiveSheet().getRange(rowNumber, 1, 1, 2).getValues()[0];
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster_LIVE');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data);
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your roster!', 'Your live roster has been edited. These changes concern ' + colB + ' on ' + colA);
}
}
}

I believe your goal as follows.
You want to send an email using OnEdit trigger when the value of Craig is put to a cell.
You want to retrieve the edited row number.
You want to run the script when the value of Craig is removed.
Modification points:
From I have a general script that will send email notification of changes made to a live roster in Google sheets and have set up a trigger for the event to occur 'On change'., I understood that you are using the installable OnChange trigger. If it's so, in your situation, I would like to recommend to use the installable OnEdit trigger.
And, when I saw your script, the function name is onEdit. If the installable OnEdit trigger installs to onEdit, when a cell is edited, onEdit is run 2 times by the simple trigger and the installable trigger with the asynchronous process. Ref So, please rename the function name from onEdit to other and reinstall the installable OnEdit trigger to the renamed function name.
In order to retrieve the row number of the edited cell, you can use the event object.
When above points are reflected to your script, it becomes as follows.
Modified script:
After the script was modified, please install the OnEdit trigger to the function of installedOnEdit. In this script, when you put the value of Craig to a cell, the script below the if statement is run.
From:
function onEdit() {
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data );
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your live roster', 'Previous value: ' + lenProp + ' New value: ' + data);
}
}
To:
function installedOnEdit(e) {
var range = e.range;
var value = range.getValue();
if (value == "Craig" || (e.oldValue == "Craig" && value == "")) {
var rowNumber = range.getRow(); // You can retrieve the row number of the editor cell.
console.log(rowNumber)
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data);
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your live roster', 'Previous value: ' + lenProp + ' New value: ' + data);
}
}
}
In this modified script, when the value of Craig is put to a cell, the script below the if statement is run. If you want to also check the column and row, please tell me.
References:
Installable Triggers
Event Objects

This sheet does not get edited, but rather changes occur through IMPORTRANGE.
No events get triggered when a formula result changes, so I do not think that you can get an automatic notification when values get updated in the imported data.
To make it work, you will have to use a trigger in the source spreadsheet rather than the target spreadsheet.

Related

google apps script to send recurring reminders doesn't work

I have a piece of code meant to send recurring emails every 8 weeks to a list of addresses in a google sheet.
It does not seem to be sending out the mails.
I have tried various examples that I found online, with no success
ScriptApp.newTrigger("sendEmails")
.timeBased()
.onWeekDay(ScriptApp.WeekDay.THURSDAY)
.atHour(11)
.nearMinute(00)
.everyWeeks(8)
.create();
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("EMAILS")
var startRow = 2;
var numRows = sheet.getRange(1,4).getValue();
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[0];
var message = row[1];
var subject = "mail subject here";
MailApp.sendEmail(emailAddress, subject, message);
}
}
when I run the script manually it seems to be working fine, so I guess the problem is in the new trigger part?
Problem:
Currently you have the code in place but it won't be used at all because it is not part of a function.
Requirement:
Trigger to run code every 8 weeks.
Solution:
Separate your trigger builder into a separate function.
Run the following script, it'll delete any triggers you may have accidentally set up and create a new one that should run as you're expecting.
function newTrigger() {
//clear all triggers
var tg = ScriptApp.getProjectTriggers();
if(tg.length>0){
for(i=0;i<tg.length;i++){
ScriptApp.deleteTrigger(tg[i]);
}
}
//build new trigger
ScriptApp.newTrigger("sendEmails")
.timeBased()
.onWeekDay(ScriptApp.WeekDay.THURSDAY)
.atHour(11)
.nearMinute(00)
.everyWeeks(8)
.create();
}
Notes:
You'll only need to run this function once to set up the trigger.
In your project's triggers, it'll show as "every week" but should actually only run every 8 weeks like we specified in the code using .everyWeeks(8).
References:
Installable Triggers
Class ClockTriggerBuilder

Copying cells from one sheet to another with Google Apps Script

I am trying to copy specific cells from one sheet to another using a button as a trigger. The same script should also send an email notification to notify someone that the document has been updated.
This is the code I am using but it does not work and it seems incomplete:
function myFunction() { MailApp.sendEmail({ to: "xx#xx.com.au", subject: "xx", htmlBody: " xxx", }
function Copy() {
var sss = SpreadsheetApp.openById('xxxxxxxxx');
var ss = sss.getSheetByName('xxx');
var range = ss.getRange('xx');
var data = range.getValues();
var tss = SpreadsheetApp.openById('xxxx'); //replace with destination ID
var ts = tss.getSheetByName('xxx'); //replace with destination Sheet tab name
}
I need the script to copy the cells and add them to an existing sheet and not overwrite any rows too.

Stock Inventory - Send email when cell value < 2 (Google Spreadsheet)

I currently trying to create for stock inventory of some products that are frequently used in my workplace using google spreadsheet. Moreover, I'm trying to come up with a script that would send me an email when a certain product reaches a value below 2 so that I would know that a certain product needs to be restock. I'm do not know the basics of coding, but here's what I got so far:
function readCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var ProductA = sheet.getRange("B2").getValue();
var Product B = sheet.getRange("B3").getValue();
var min = 2
if (ProductA<min) MailApp.sendEmail('n********#googlegroups.com', 'LOW REAGENT STOCK', 'Attention! Your stock of ProductA is running low. Please proceed to restock.');
if (ProductB<min) MailApp.sendEmail('n********#googlegroups.com', 'LOW REAGENT STOCK', 'Attention! Your stock of ProductB is running low. Please proceed to restock.');
}
I put the trigger on onEdit to run the script and I intent to expand the list with more products. The thing is that if one product as already reached a value below 2 and if a change another one, the script will send email for both of them. With more products, this becomes a nuisance, because I would received a bunch of emails if other values remain below 2. Can someone help me out with this? I couldn't find any solution to this so far and I would truly appreciate some help.
Thank you!
When the "onEdit" trigger fires, it receives the event object as parameter containing some useful information about the context, in which the edit action occurred.
For example,
function onEdit(e) {
// range that was edited
var range = e.range;
//value prior to the edit action
var oldValue = e.oldValue;
//new value
var value = e.value;
//sheet the action came from
var sheet = range.getSheet();
//cell coordinates (if edited range is a single cell)
//or the upper left boundary of the edited range
var row = range.getRow();
var col = range.getColumn();
}
You can inspect the event object to get the cell that was edited and see if it's in column B.
var productsColIndex = 1; //column A index;
var inventoryColIndex = 2; //column B index
var range = e.range;
var value = e.value;
var sheet = range.getSheet();
var editedRow = range.getRow();
var editedCol = range.getColumn();
var productName = sheet.getRange(editedRow, productsColIndex).getValue();
//checking if
//1) column B was edited
//2) the product exists in column A
//3) new value is less than 2
if ((editedCol == inventoryColIndex) && productName && value < 2) {
//code for sending notification email.
}
Finally, because simple triggers like onEdit() can't call services that require authorization, it's better to create a function with a different name and then set up the installable trigger manually. In your Script Editor, go to "Edit" -> "Current project's triggers" -> "Add a new trigger" , select your function name from the dropdown list, and pick the following options: "From spreadsheet", "On edit".

Google Script Email loop

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.

Google Script - Move new submissions to another sheet based on the responses

I'm trying to create a script that will take a new form response and move it to another sheet based on the information submitted. For example, let's say the form has two answer choices A, B. The spreadsheet has three sheets; Form Responses, Sheet A, Sheet B. If someone submits the form and selects A, I need that new row to be moved from "Form Responses" to "Sheet A." I found someone else's script that does exactly this but using the OnEdit function. I cannot figure out how to modify this script to work when a new form response is submitted.
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Form Responses" && r.getColumn() == 2 && r.getValue() == "A") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Sheet A");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
I used the installable triggers and replaced the OnEdit function with onFormSubmit but that doesn't work. I'd really appreciate it if anyone could help me with this.
Thanks,
To achieve what you want, you will need to:
Create a function write_to_new_sheet that we'll use in a trigger function whenever a new response hits the form. This function will take the form response as an event object e:
function write_to_new_sheet(e){
let responses = e.response.getItemResponses()
let new_row = get_new_response_data_as_row(responses)
let sheet_to_write = SpreadsheetApp.openById('your spreadsheet id').getSheetByName('sheet A') // or 'sheet B', you can set this dynamically by checking the new_row, corresponding to the response as a gsheet row
write_values_in_first_row(sheet_to_write, new_row)
}
this are the auxiliary functions to write_to_new_sheet:
function get_new_response_data_as_row(responses){
let new_row = []
responses.forEach(response => {
new_row.push(response.getResponse())
})
return new_row
}
function write_values_in_first_row(sheet, new_row_values){
let row_to_write_from = 2 // assuming you have a header
let sheet_with_new_row = sheet.insertRowBefore(row_to_write_from)
let number_of_rows = 1
let number_of_columns = new_row_values.length
let range = sheet_with_new_row.getRange(row_to_write_from, 1, number_of_rows, number_of_columns)
let results =range.setValues([new_row_values])
return new_row_values
}
Set up an installable trigger that works whenever you submit a new response to the form:
function setup_write_to_new_sheet_on_form_submit(){
ScriptApp.newTrigger('write_to_new_sheet')
.forForm('your form id goes here')
.onFormSubmit()
.create();
}
Run the above function once, to set up the trigger.
try submitting a new response on the form, and check the changes in the sheets you want it to be written.
Try something a little less broad in your comparing of variables,, For instance the sheet that submissions are sent to is a constant and already address.
function formSubmission() {
var s = SpreadsheetApp.getActiveSheet();
var data = range.getValues(); // range is a constant that always contains the submitted answers
var numCol = range.getLastColumn();
var row = s.getActiveRow;
var targetinfo = s.getRange(row,(Yourcolumn#here).getValue);
if(targetinfo() == "Desired Sheet Name") {
var targetSheet = ss.getSheetByName("Sheet A");
var targetrow = targetSheet.getLastrow()+1);
var Targetcol = numCol();
targetSheet.getRange(targetrow,1,1,Targetcol).setValues(data);
}
}
I didn't test that but hopefully it helps look up Event Objects in the developer guide i just recently found it and it clarifies a lot
the triggers can be set by going to:
then set it:
I have a spreadsheet that collects the form submissions and then has extra sheets that have filtered out these submission based on the answers. All this is just with formulas. Could that do the trick also?