Script to sent email from Google Sheet is sending multiple times - google-apps-script

I used the tutorial here: https://developers.google.com/apps-script/articles/sending_emails
The problem I am having is that it sends the notifications many times (17, 25, 30 times.) I tried the "Improved" version as well, where it should only send an email if the column "Email_sent" isn't populated. The script populates the "email_sent" but that didn't prevent it from sending a bunch of times, even though with the "if" that shouldn't be possible.
I don't know what I could possibly be doing wrong that causes it to send so many times.
I had to modify the code slightly because when I use var row = data[i] and var emailAddress = row[0] it was not able to retrieve the email addresses.
If I take out the [i] and just use var row = data then it works. I don't know what difference that is causing or if it is related to the problem I am having.
I also changed numrow to sheet.getlastrow() instead of a static number because the number of rows in my data could change.
Here is my code, written as in the tutorial with the i
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var numRows = sheet.getLastRow()
var dataRange = sheet.getRange(3, 2, numRows);
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[0];
var message = 'You have unreleased time in LETS. Please review and release.';
var subject = 'Please review LETS time';
MailApp.sendEmail(emailAddress, subject, message);
}
}
This way fails to send, says it cannot find a recipient, and the debugger shows the emailAddress line returning blank. If I remove the [i] it runs, but every time I run it I get a ton of emails, and it should only send one per email address.

The syntax for getting ranges is getRange(row, column, numRows)
It expects the number of rows, rather than the last row as the third parameter.
Assuming that your last row is 6,
var dataRange = sheet.getRange(3, 2, numRows);
would mean "get 6 rows starting with number 3", or "get rows 3 to 8". But you won't have any data in row 7 and 8 - this throws you the error.
Just change your code to:
var startRow=3;
var dataRange = sheet.getRange(startRow, 2, numRows-startRow+1);

Related

I'm trying to use paypal IPN with Google Sheets and Gmail to set up an automated email response when a buyer purchases a digital download but too hard

I've been successful getting the paypal ipn data to my google sheets using a variation of a script i found online.
function doPost(e) {
var rowData = [];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet()
rowData.push(new Date(e.parameter.payment_date));
rowData.push(e.parameter.payment_type);
rowData.push(e.parameter.payment_status);
rowData.push(e.parameter.payer_email);
rowData.push(e.parameter.item_name1);
rowData.push(e.parameter.item_number1);
rowData.push(e.parameter.quantity);
rowData.push(e.parameter.first_name);
rowData.push(e.parameter.last_name);
rowData.push(e.parameter.address_street);
rowData.push(e.parameter.address_city);
rowData.push(e.parameter.address_state);
rowData.push(e.parameter.address_zip);
sheet.appendRow(rowData);
}
The above seems to work great and what the next step and I'm trying to do is take the data from a few of the cells in my google sheet where this script is working and use that in another script where an email with a link or attachment to the digital download images they purchased. I need to extract the first name, last name, payer email, and a variation of the itemname1 from above function and send the email automatically. I've set up the google sheet with the IPN information above and have added a last column 'P' where the link to the image will be located online using data from itemname1 as mentioned. The next step would be to use something like the function below using my data, but I'm having a super difficult time. I need to tweak the below function to work with my data. Is that possible? I'm not having any luck and it's all so close at this point. Anyone out there now how to do this? Can you point me in the right direction?
Something like this is what I was thinking would work for me, but I'm not sure how to tweak it to do the job I need:
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = 'EMAIL_SENT';
/**
* Sends non-duplicate emails with data from the current spreadsheet.
*/
function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 2; // 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();
}
}
}
My problem is that I haven't been able to put all the data together and make it work yet. Any help?

How do I reduce the "computer time" that my Google Apps Script is using?

I followed Hugo Fierro's tutorial on adding a Google Apps Script to send emails from Google Sheets. The tut is at https://developers.google.com/apps-script/articles/sending_emails.
I customised the script:
1) I replaced the "MailApp.sendEmail" API with "GmailApp.sendEmail" because I was getting authentication errors and the emails were not sending. The Gmail API has worked fine.
2) I added an option to send a PDF attachment with each mail using "DriveApp.getFileById".
3) I added a second condition to the IF statement to check that the PDF document is available before sending (by referencing a column in the sheet).
The problem is that if the script references only 5 rows, then it processes in under 30 seconds. When I try to process 10 rows or more, the processing time goes up significantly.
I replaced "sheet.getRange" with "sheet.getLastRow()" in an attempt to reduce how many rows the script is referencing.
var READY = 'READY';
var SENT = 'SENT';
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Email'); // Get the active spreadsheet, then get the "Email" sheet
var startRow = 2; // Select data row to start at
var endRow = sheet.getLastRow(); // Get the last row in the sheet
var data = sheet.getRange(startRow, 1, endRow, 6).getValues(); // Get the range of cells, then get the values
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var email = row[0]; // Column 1
var subject = row[1]; // Column 2
var message = row[2]; // Column 3
var attachment = DriveApp.getFileById(row[3]); // Returns the attachment file ID
var emailReady = row[4]; // Column 5
var emailSent = row[5]; // Column 6
var name = 'VFISA'; // Set "from" name in email
var bcc = 'myaddress#gmail.com'; // Blind carbon copy this email address
if (emailReady==READY && emailSent!==SENT) { // Prevents sending duplicates, waits for attachment cell to confirm available
GmailApp.sendEmail(email, subject, message,{
name: name,
bcc: bcc,
htmlBody: message,
attachments: attachment
});
sheet.getRange(startRow + i, 6).setValue(SENT); // Set the cell in column F to "SENT"
SpreadsheetApp.flush(); // Make sure the cell is updated right away in case the script is interrupted
}
}
}
I expected the script to run much faster. The error I get is "Service using too much computer time for one day". When I reference 20 rows it takes up to 4 minutes to run.
DriveApp.getFileById was causing each row to take about 5 seconds to execute. I removed this and now the entire script executes in under 1 second.
Instead of adding the PDF as an attachment, I used an inline link based on the file's ID (based on Alan Wells' suggestion).

Sending an email with spreadsheet data

I'm currently trying to write an Apps Script function that sends an email when its Google Sheet is edited. My current problem is that the function only sends one column of data and I need it send both the date and amount of inventory. This is what I currently have, I'm thinking that maybe I need a nested loop in order for the function to print both columns. Any help would be great!
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(2, 1, 4, 2);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
//var data = dataRange.getSheetValues();
for (i in data) {
var row = data[i];
var message = row[1]; // Second column
var subject = "Inventory update";
MailApp.sendEmail("test#test.com", subject, message);
}
}
In the above code values of your cells from range "A2:B5" are stored in an array of array format in variable data i.e
var data = dataRange.getValues(); //This code results in the below
data = [["A2","B2"],
["A3","B3"],
["A4","B4"],
["A5","B5"]]
Where A2 corresponds to the value of cell A2, B2 to cell B2 and so on. To access the first row you would the following:
data[0] => ["A2","B2"]
Note: data[0] returns another array containing all the elements on row 1. Also, note the numbering starts from 0 not form 1
Now to access the first element of the first row, you would do the following:
data[0][0] => "A2"
So similarly in your above, code when you do this
var row = data[i]; where i = 0
row => ["A2","B2"]
Hence to get first column you would do
row[0] => "A2"
to get the second column you would do
row[1] => "B2"
Modify your message variable to look like this
message = message + row[0] +","+row[1]"\n"
Where in your appended the new row to the previous message with a comma delimiter and \n new line feed.
As of now, your mail app sends 1 email per each row. However, I don't believe that is the intended behavior. Below is your modified code
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(2, 1, 4, 2);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
//var data = dataRange.getSheetValues();
var message ="" //empty string
//Loop through each row and form CSV data
for (var i=0;i<data.length;i++) { //edited because:https://stackoverflow.com/questions/500504/why-is-using-for-in-with-array-iteration-a-bad-idea
var row = data[i];
message = message + row[0] +","+row[1]"\n"
}
// Send one email with all the rows
var subject = "Inventory update";
MailApp.sendEmail("test#test.com", subject, message);
}
The initial question did not insist on a coded solution.
Therefore, your exact usecase can be done without code, using this Gsheet addon:
https://reactor.isvery.ninja/
see the tutorial video in the bottom (send email from spreadsheet)

Google Script E-mail confirmation

My goal is to have a form that fills out a spreadsheet. Part of the form will have an e-mail address and another with is task complete. I want it to be if I switch the spreadsheet from no to yes under task complete to e-mail the user with a confirmation saying so. I don't know if it is possible but any help would be appreciated.
Edit :
This code is what I have down now to do the task. I like the way it is setup up but there are 2 major issues that I want to try and solve. First being when the script runs lets say I skip over a row the script will fail and not keep trying more entries down the rows. Second issue is I am not sure how to control who is sending the script e-mail past where it asks you to okay and run the script the first time.
// This constant is written in column O for rows for which an email
// has been sent successfully.
var EMAIL_SENT = "EMAIL_SENT";
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 1000; // Number of rows to process
// Fetch the range of cells M2:N1000
var dataRange = sheet.getRange(startRow, 1, numRows, 1000)
// 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[12]; // First column
var message = row[13]; // Second column
var emailSent = row[14]; // Third column
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = "Help Desk Completion Response";
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 15).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
The e-mail will always be sent as the user that runs the script. You can have the script run as the result of an installable trigger which you can set on any Google account with access to the script and the script will then send the e-mail as that Google account. For example, have the script run once a day using a time-based trigger and check the values for EMAIL_SENT and TASK_COMPLETE, if the task is complete but the e-mail is not sent, then have the script send an e-mail. This should also solve the empty row problem because when TASK_COMPLETE is empty rather than "Yes" it will not try to send an e-mail.
If you want to make the script send an e-mail immediately you will need an edit-based trigger. In this case you may not even need to load the entire spreadsheet but a simple e.range.offset() may suffice.
Something like:
//This constant is written in column O for rows for which an email
//has been sent successfully.
var EMAIL_SENT = "EMAIL_SENT";
function sendEmails(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var columnNumber = e.range.getColumn(); //check if the changed value is TASK_COMPLETE
if(columnNumber = 5 && e.value = "Yes") {
var row = e.range.offset(0, -5, 1, sheet.getLastColumn()).getValues();
var emailAddress = row[12]; // First column
var message = row[13]; // Second column
var emailSent = row[14]; // Third column
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = "Help Desk Completion Response";
MailApp.sendEmail(emailAddress, subject, message);
e.range.offset(0,9).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
Of course the exact offsets depend on where exactly in your spreadsheet things are located.

How to send email from a spreadsheet

I have a spreadsheet on Google Docs with tasks for a job that we are working on at work. What I am wanting it to do is to send the whole row to the intended recipient but I can only get it to send the info from the first column after the email address. Everything has a due date on it and I would like to get it to send a reminder when it gets close to that date but I do not know how to do that.
Any help would be greatly appreciated.
Here is the code I have right now:
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 3; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = row[1]; // Column B, Column C, Column D, Column E, Column F, Column G
var subject = "Email Test Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
}
}
when you define your dataRange like this :
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
you read data from startRow column 1 for numRows but only 2 columns (the last value is 'width') so you could easily modify your script to get all the columns you want just by changing this last value. Then using row[i].toString(), you will get your comma separated fields.
Then you might want to get a better presentation using HTML format to present data in a table for example but this is outside the point of your question ;-)
When you define a data range like this
var dataRange = sheet.getRange(startRow, 1, numRows, 2);
you are taking the data from A3:C5
you can also refer to the documentation
If you can post a sample spreadsheet as per the description of your problem, then it will be helpful for others to help you.
This piece: var message = row[1]; will only ever get the second element (index 1) in the array. You'll need to iterate through the row to get all the array elements.
var message = row[1];
message.concat(row[2]);
message.concat(row[3]);
And so on. Or you could build a loop.
http://www.w3schools.com/jsref/jsref_concat_string.asp
And to get the range, why do it the hard way? sheet.getRange("A2:B3");
That's the range you said you're trying to get in the comment above that line, although it is only 4 cells. sheet.getRange("A2:G3") would get two rows out to column G.
Of course, if you add rows to the sheet, you'd have to modify the code every time.
Instead, try this.
var bottomRow = sheet.getLastRow();
var rangeInA1 = "A2:G"+bottomRow;
var dataRange = sheet.getRange(rangeInA1);