Google Apps Scripts Send automated email with trigger (not repetitively) - google-apps-script

I'm attempting to set up automated emails through google sheets using scripts and a trigger.
How do I define only new additions to the spreadsheet should trigger an email? The spreadsheet is constantly added to.
function sendloggeremails() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow()
for (var i = 72; i <= lr; i++) {
var currentEmail = ss.getRange(i, 7).getValue();
var currentClassTitle = ss.getRange(i, 3).getValue();
MailApp.sendEmail(currentEmail, "complaint: customer number " + currentClassTitle , "Please check through the log as you have a new assigned to you");
}
}
var i = 72 plainly because this is the last row, I don't want to have to manually change this constantly. Added triggers but at the moment I still need to go into the code to change var i.
Any chance anyone can help with this?

Sending Emails only Once with a Loop
You could use something like this. To get started you would want to put something in the sent column so that old lines would not resend their emails and you'd still maintain a record of past emails. I suggested putting the string "SENT". But the test just ask the question is that column empty so anything will work.
Obviously, I haven't seen you spreadsheet so I don't know where to put the sentColumn so you can change the var sentColumn = 75 to anything you wish and all of the other places it's used will change accordingly.
function sendloggeremails() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getActiveSheet();//You should probably change this to getSheetByName() and then put in the correct name of the sheet
var sentColumn=75;//If this column is not empty then don't send emails. If you send any email then also put "SENT" into this column so you wont resend it next time you run the loop.
sh.getRange(1,sentColumn).setValue("Sent");//I'm assuming a header row and I'm putting a Header Title there you can pick any open column you want
var rg=sh.getDataRange();//This gets all of the sheets data into one one range
var vA=rg.getValues();//This gets all of the values in above range in a two dimension object we often refer to as a two dimensional array.
//If you have header row you can start at i=1
for(var i=1;i<vA.length; i++) {//This will loop over all of the rows on the sheet.
if(!vA[i][sentColumn-1]){//Heres the test to see if theres something in sentColumn. If sentColumn is empty meaning truthy is false then it send the email
var currentEmail=vA[i][6];//this replaces the getValue from column7 *1
var currentClassTitle=vA[i][2];//this replaces the getValue from column3 *1
MailApp.sendEmail(currentEmail, "complaint: customer number " + currentClassTitle , "Please check through the log as you have a new assigned to you");
sh.getRange(i+1,sentColumn).setValue("SENT");//After sending email we put something. In this case "SENT" into the sentColumn so that next through the loop we won send another email because its truthy will be true.
}
}
}
//*1 Array indices are 1 less that column numbers because arrays start counting from zero.
If you wish we could also delete the rows as we send them. All you have to do is keep track of how many rows you have deleted each time the loop is run with say a variable like var n=0 to start with and then the row number that gets deleted will be i-n+1. And right after the deletion you increment n by one.
You can setup the trigger using this function.
function setupEmailTrigger() {
if(ScriptApp.getProjectTriggers().indexOf('sendloggeremails')==-1){
ScriptApp.newTrigger('sendloggeremails').timeBased().everyDays(1).atHour(17).create();
}
}
It checks to see if the trigger already exists and if the trigger already exists it does nothing.

Related

Google sheet script to search and retrieve Gmail data

I've started a script that matches an email address in Col D with Gmail and writes the last message date in Col E. I'd like that script to repeat row by row - now it only writes to Row 2 (newbie question - sorry).
I'd also like to search for data in addition to "from". I'd like to also search "to" and other variables.
Lastly, I'd like this to have a trigger that is time-based (daily or weekly) and I'm still getting my head wrapped around triggers, so any help is really appreciated.
function myFunction() {
// Get the value in the cell D2 (which will have the email to check the latest interaction)
var ss =
SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('D2').getValue();
// Search the date of the last message of the search from the email in cell D1 and log it
var latestEmail = GmailApp.search('from:"'+ss+'"')[0].getLastMessageDate();
Logger.log(latestEmail);
// Print the date on the cell to the right
SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('E2').setValue(latestEmail);
}
I'd like that script to repeat row by row - now it only writes to Row 2
You will need to iterate ("loop") through your sheet's rows.
Try this:
function myFunction() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1')
const addresses = sheet.getRange(2, 4, sheet.getLastRow()-1).getValues().flat().filter(Boolean)
for (let [index, address] of addresses.entries()) {
const latestEmail = GmailApp.search(`from: ${address}`)[0].getLastMessageDate()
sheet.getRange(index+2, 5).setValue(latestEmail);
}
}
Commented:
function myFunction() {
// Your sheet.
const sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1')
// All cells in Column D (Column 4).
const addresses = sheet.getRange(2, 4, sheet.getLastRow()-1).getValues().flat().filter(Boolean)
// For each address in Column D... (While keeping track of the index)
for (let [index, address] of addresses.entries()) {
// Get the last message date from this address...
const latestEmail = GmailApp.search(`from: ${address}`)[0].getLastMessageDate()
// And set the adjacent cell to that date.
sheet.getRange(index+2, 5).setValue(latestEmail);
// "Index+2" because index starts at 0 and we started our range at row 2.
}
}
I'd also like to search for data in addition to "from". I'd like to also search "to" and other variables.
That can all be done using GmailApp.search()
Lastly, I'd like this to have a trigger that is time-based (daily or weekly) and I'm still getting my head wrapped around triggers, so any help is really appreciated.
Couldn't be easier, see here:
Managing triggers manually
In your Script Editor, click the Triggers icon and then click the Add Trigger button.
You will be looking to choose this function, from event source: "Time-driven", and select when you would like it to run.

How to find value from Google sheet using multiple cell values and send email

I want to find the particular values in a google sheet, once its found then fetch total row values and send email from google sheet with the total row values.
I'm aware of send email from google-sheet but finding the particular value based on that send email is very challenging for me.
Wherever E60 is present the should fetch total row values and need to send email.
To send a email via google sheet you will need to write some code with script editor. I think you could find more details here
After studying your question, I assume the following:
You want to check a column matching the string E60.
When a cell with E60 is found, your want to send a email with the full row content.
If my assumptions are correct, you can use the following example to fulfill your requests:
CODE
function E60Alert() {
var sheet = SpreadsheetApp.openById(
'{SPREADSHEET ID}').getSheetByName('Sheet1');
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
var subject = "E60 Alert";
var email = "{YOUR EMAIL}";
var rowContent = sheet.getRange(1, 1, lastRow, lastColumn).getValues();
for (var i = 1; i <= lastRow; i++) {
if (rowContent[i - 1][4] == 'E60') {
MailApp.sendEmail(email, subject, rowContent[i - 1]);
}
}
}
BEHAVIOUR
That code will first read the full table. After that, it will iterate it searching for the desired string (E60 in this case). If it finds it, it will send an email with the full row content.
OBSERVATIONS
This code will run on demand by clicking the Run button.
You will have to edit the if (rowContent[i - 1][4] == 'E60') { line to match the desired column of the data. I chose the fifth column (number 4) for testing purposes.
ALLUSIONS
getLastRow() & getLastColumn()
getRange() & getValues()
sendEmail()
Please take this as one of the possible solutions to your issue, and don't hesitate to write me back with any additional doubts or requests to further clarifications.

Overwriting Google sheets (for form response) rows if duplicate entered

So, I've been trying to figure out how to stop the duplicate rows appearing in my google sheets response output from a google form. If found this link which sounds like it does exactly what I want (Form Google Script Prevent Duplicates), but cannot for the life of me work out how to edit the given answer to work on my sheet.
I have included a screenshot of my workbook to give an example of the structure of the data I'd like the edited code to run on, and also below is my attempt at making the code run correctly on my data structure.
My sheet structure that I'd like to run the code on. I want to use the email address as the 'unique' identifier, so any duplicate rows can be identified using that.
My attempt at adapting the code to work on the above data structure (I have absolutely no background with this scripting language, so please go easy on me if I've made a glaringly obvious error):
function updateExisting() {
var s = SpreadsheetApp.getActiveSheet(),
// s = ss.getSheetByName(''),
lastRow = s.getLastRow(),
lastValues = s.getRange('A'+lastRow+':C'+lastRow).getValues(),
name = lastValues[0][0],
allNames = s.getRange('B2:B').getValues(),
row, len;
// TRY AND FIND EXISTING NAME
for (row = 0, len = allNames.length; row < len - 1; row++)
if (allNames[row][0] == name) {
// OVERWRITE OLD DATA
s.getRange('A2').offset(0, 0, row,
lastValues.length).setValues([lastValues]);
// DELETE THE LAST ROW
s.deleteRow(lastRow);
break;}
}
Key words: duplicates, Google, spreadsheet, Sheets, Form, submission, edit, row, unique.
This code prevents duplicates in a Google Sheet when submitting a Google Form, by overwriting an existing row with the existing unique value, if one exists.
The code searches one column in a spreadsheet and looks for a match. I tried to make it generic so that the code doesn't need to be changed depending upon what column the unique identifier is in. You need to make a couple of settings in the "User Settings" section to make it work. But that is better than needing to rewrite the code.
function updateExisting(columnWithUniqueIdentifier,sheetTabName) {
var dataFromColumnToMatch,lastColumn,lastRow,rowWithExistingUniqueValue,rowOfDataJustSaved,
sh,ss,valueToSearchFor;
// USER SETTINGS - if the values where not passed in to the function
if (!columnWithUniqueIdentifier) {//If you are not passing in the column number
columnWithUniqueIdentifier = 2;//Hard code column number if you want
}
if (!sheetTabName) {//The sheet tab name was not passed in to the function
sheetTabName = "Put your Sheet tab name here";//Hard code if needed
}
//end of user settings
ss = SpreadsheetApp.getActiveSpreadsheet();//Get the active spreadsheet - this code must be in a project bound to spreadsheet
sh = ss.getSheetByName(sheetTabName);
lastRow = sh.getLastRow();
lastColumn = sh.getLastColumn();
//Logger.log('lastRow: ' + lastRow)
rowOfDataJustSaved = sh.getRange(lastRow, 1, 1, lastColumn).getValues();//Get the values that were just saved
valueToSearchFor = rowOfDataJustSaved[0][columnWithUniqueIdentifier-1];
//Logger.log('valueToSearchFor: ' + valueToSearchFor)
dataFromColumnToMatch = sh.getRange(1, columnWithUniqueIdentifier, lastRow-1, 1).getValues();
dataFromColumnToMatch = dataFromColumnToMatch.toString().split(",");
//Logger.log('dataFromColumnToMatch: ' + dataFromColumnToMatch)
rowWithExistingUniqueValue = dataFromColumnToMatch.indexOf(valueToSearchFor);
//Logger.log('rowWithExistingUniqueValue: ' + rowWithExistingUniqueValue)
if (rowWithExistingUniqueValue === -1) {//There is no existing data with the unique identifier
return;
}
sh.getRange(rowWithExistingUniqueValue + 1, 1, 1, rowOfDataJustSaved[0].length).setValues(rowOfDataJustSaved);
sh.deleteRow(lastRow);//delete the row that was at then end
}

Autofill google forms based on user input

Alright stack friends,
I'm working on my first projects using google scripts and it's been pretty fun so far. My project is to create a form for data entry that can either accept an ID number and fill in the rest of the fields, or let the user fill out the entire form. Basically my method to fill in the other fields is just to have a lookup table on the second sheet. When the user submits a form, the script runs, looks for the ID of the last row, scans the reference table for the ID, and then fills in the details.
I think the problem I'm having is the assumption that the data from the form is already in the sheet when the script runs. The problem I noticed is that the script sometimes fails to fill in the gaps. I tried creating form submissions in a loop with the same ID and they function somewhat erratically but it seems like the last sumbission always works which would make sense if the script executions are not matching up with the form submissions. Here's the script for reference:
function fillGaps() {
// First take in the appropriate spreadsheet objects and get the sheets from it
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheets()[0];
var refSheet = ss.getSheets()[1];
// Here's the last rows' index
var lastRow = sheet.getLastRow();
var lastRowRef = refSheet.getLastRow();
// now this is an array of values for the last row and the student ID entered
var response = sheet.getRange(lastRow, 1, 1, 7).getValues();
var enteredID = response[0][1];
// Next we're going to try to load up the lookup table and scan for the ID
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
var row = 0;
while(enteredID != stuIDs[row] && row <= lastRowRef){
row++;
}
// Okay at this point the row variable is actually -2 from what the sheet index
// is that I'm thinking of. This is because we didn't load the first row (names)
// and the way arrays are indexed starts with 0.
row++;
row++;
// now assuming that it found a match we'll fill in the values
if(row < refSheet.getLastRow()){
// Alright now we need to wrangle that row and format the data
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
}
So I'm wondering:
Does this seem like the right diagnosis?
If so, what would be the best way to remedy? I thought of adding a little delay into the script as well as trying to capture the submissions timestamp (not sure how to do that)
Thank you much!
The following code gives a 2D array:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
Also,refSheet.getLastRow gives the last row, lets say it is 10 in this case. The syntax for getRange is getRange(row, column, numRows) and the last argument is the number of rows, not the last column. So in the above code the selected range would be row 2 - 11 rather than 2- 10. Unless that is what you intended, modify the code like so:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()-1).getValues();
To access the values in stuIDs you should use stuIDs[row][0] (2D array) to check for matching ID. Assuming your ID was to be matched was in column 1.
Secondly, in the loop you are using the following to check for the last index in array row <= lastRowRef which will cause it go out of range(because array starts at 0 and sheet row at 1) instead use this row < stuIDs.length
Finally, in case you don't find a match you will end up with the last row and your code will end you taking the last row as the matched index. This can be prevented by using a boolean variable to check for a match.
var foundId = false
var row = 0;
var i = 0;
for (i in stuIDs){
if(stuIDs[i][0] == enteredID)
foundID = true
break
}
}
row = i + 2
if (foundID){
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
PS: You can also use event objects to get the values of response (eventObj.values). As mentioned here: https://developers.google.com/apps-script/guides/triggers/events
Hope that helps!

Google app script trigger not working

I have a google app script which sends email, and i have set a trigger such that it sends email on every form submit. The problem is the trigger works perfectly fine for initial few minutes, but later even after entering correct data. The script does not send the mail, i have to manually press the execution button of the script. Here is my code
var EMAIL_SENT = "EMAIL_SENT";
function sendEmailsapp() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow(); // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows,8)
// 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[4]; // First column
var message = row[5]; // Second column
var emailSent = row[7]; // Third column
var money=row[6]
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = "You have been registered for follwoing events:-";
var event;
MailApp.sendEmail(emailAddress, subject, "Please bring your college id and copy of this mail either in phone or printed paper"+
"\n\n"+"Name:-"+row[1]+"\n"+"USN:-"+row[2]+"\n"+"Mobile:-"+row[3]+"\n"+"Event:-"+ message+"\nMoney status:-"+money+"You registered on"+row[0]);
sheet.getRange(startRow + i, 8).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
The code works fine. The only problem is triggers.
Here is the image of my trigger. sendEmailsapp is the trigger, and sendEmailsweb is another trigger which also suffers from same problem.
here is the log
My only Problem is the trigger is not getting triggered, it is not with the email being sent.
My trigger wasn't working just like yours, even though the script was correct.
The solution was very stupid. I deleted the trigger and added a new one. Exactly the same trigger.
I had this issue with my script, but I was using Time-Driven as the Event. I set it on every minute but it just wouldn't do anything. Used the logging method written above and I found out that the trigger works fine - every minute as I set. But it just wouldn't actually do the same thing as when I explicitly click on the Run button.
I suspected that it had something to do with the following line:
var sheet = SpreadsheetApp.getActiveSheet();
So I played around with it and changed it to:
var spreadsheet = SpreadsheetApp.openById("INSERT_SPREADSHEET_ID_HERE");
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
And it finally worked.
Replace INSERT_SPREADSHEET_ID_HERE with the ID of your spreadsheet (from your URL https://docs.google.com/spreadsheets/d/INSERT_SPREADSHEET_ID_HERE/edit#gid=0).
And from the getSheets()[0], the 0 would be your first of possibly multiple sheets in that specific spreadsheet.
I hope this helps in any way.
I'd be willing to bet that the problem is not the trigger. Even though your code works, it could be conditions that are preventing the conditional section of your code to work. You can test to see that the function is actually triggering, even if it's not giving you the expected result. Put a Logger.log() statement immediately after the function:
function sendEmailsapp() {
Logger.log('sendEmailsapp ran!');
Then VIEW the LOGS. I can't believe that the function isn't being triggered. The email might not be getting sent, but I'd be willing to bet that you are going to get that msg 'sendEmailsapp ran!' printed to the LOGS. So, that's the first thing you need to do.
The next thing you need to do is put a Logger.log() statement immediately after retrieving the email:
var emailSent = row[7];
Logger.log('value of the email is: ' + emailSent);
Then check the LOGS for what values are actually being returned. If every value being returned is "EMAIL_SENT", then no email will ever get sent.
We need to know what the actual results are. You need to provide what was printed to the LOGS.
What I found useful was to set notifications to immediate in "Resources>Current Projects Triggers". I set it to send me an email on error. That email contains details of the problem which you can then solve :).
It is an old issue but there is a new solution. Please change your Run time environment to V8. V8 does not seem to cause this bug.