Send Email when value changes in Google Spreadsheet - google-apps-script

I am trying to figure out, how do the following in Google Spreadsheet.
Send email when a value changes in a cell. (Value = Completed).
Compile that rows data into the email. See format in code below.
Prompt user for confirmation of info.
If YES, send email to active user as well as the preset users in the code below.
This is optional: Update sheet in row on column (P) 16 with Email Sent + timestamp.
Hi Serge,
Try to implement the code you provided, but I could not make heads or tails on what to modify to fit what I needed done.
Let me explain it again with below workflow.
Send an email when the value changes for column K.
Partial Sample code to watch column K
var sheetNameToWatch = "Active Discs";
var columnNumberToWatch = 14; // column A = 1, B = 2, etc.
var valueToWatch1 = "Completed";
var valueToWatch2 = "in progress";
try{
var ss = e.source;
var sheet = ss.getActiveSheet();
var range = e.range;
if (sheet.getName() == sheetNameToWatch && range.columnStart ==
columnNumberToWatch && e.value == valueToWatch)
var confirm = Browser.msgBox
('Email will be sent Team X. Do you want to sent this email?', Browser.Buttons.YES_NO);
if(confirm!='yes'){return};
// if user click NO then exit the function, else move data
The email will contain the specified values of that specific row. Ex. Values in columns A, B, C, D, E, F, G, H, I, J.
//Email to be sent if **Inprogess** value is a match:
Var sendEmailTeamA(){
var ProjectName = e.values[0];
var ProjectId = e.values[1];
var ProjectManager = e.values[3];
var Sales = e.values[4];
var Client = e.values[5];
var DiscType = e.values[6];
var DVDFlash = e.values[7];
var Phase = e.values[8];
var Encryption = e.values[9];
var Qty = e.values[11];
var DueDate = e.values[12];
var SpecialInstructions = e.values[13];
var emailAddress = '';
var subject = "DVD Request - " + ProjectName + " " + ProjectId;
var emailBody = "Hi Venue Colombo Team,"
"\n\nThe following data room(s) will need a disc creation. Please begin bulk save data room and create ISO to upload to the FTP site: " +
"\nProject Name: " + ProjectName +
"\nProject ID: " + ProjectId +
"\nProject Manager: " + ProjectManager +
"\nPhase: " + Phase +
"\nDisc Type: " + DiscType +
"\nEncryption: " + Encryption +
"\nQuantity: " + Qty +
"\nClient Due Date: " + DueDate +
"\nSpecialInstructions: " + SpecialInstructions;
var htmlBody = "Thank you for your <b>Club Ambassador Program</b> report submitted on <i>" + timestamp +
"</i><br/> <br/>Person Show Submitted this email: " +
"<br/><font color=\"red\">Your Name:</font> " + activeSessionuser +
"<br/>Your Email: " + toAddress;
var optAdvancedArgs = {name: "Club Ambassador Program", htmlBody: htmlBody};
MailApp.sendEmail(emailAddress, subject, emailBody, optAdvancedArgs);
}
//Email to be sent if **“Completed”** value is a match:
Var sendEmailTeamB() {
var ProjectName = e.values[0];
var ProjectId = e.values[1];
var ProjectManager = e.values[3];
var Sales = e.values[4];
var Client = e.values[5];
var DiscType = e.values[6];
var DVDFlash = e.values[7];
var Phase = e.values[8];
var Encryption = e.values[9];
var Qty = e.values[11];
var DueDate = e.values[12];
var SpecialInstructions = e.values[13];
var emailAddress = '';
var subject = "DVD Request - " + ProjectName + " " + ProjectId;
var emailBody = "Hi Venue Colombo Team,"
"\n\nThe following data room(s) will need a disc creation. Please begin bulk save data room and create ISO to upload to the FTP site: " +
"\nProject Name: " + ProjectName +
"\nProject ID: " + ProjectId +
"\nProject Manager: " + ProjectManager +
"\nPhase: " + Phase +
"\nDisc Type: " + DiscType +
"\nEncryption: " + Encryption +
"\nQuantity: " + Qty +
"\nClient Due Date: " + DueDate +
"\nSpecialInstructions: " + SpecialInstructions;
var htmlBody = "Thank you for your <b>Club Ambassador Program</b> report submitted on <i>" + timestamp +
"</i><br/> <br/>Person Show Submitted this email: " +
"<br/><font color=\"red\">Your Name:</font> " + activeSessionuser +
"<br/>Your Email: " + toAddress;
var optAdvancedArgs = {name: "Club Ambassador Program", htmlBody: htmlBody};
MailApp.sendEmail(emailAddress, subject, emailBody, optAdvancedArgs);
}
This workflow will apply to columns K, L, M, N, O. Email will be sent to the preset email addresses in the code. I hope this explains it a little bit better. I thank you again for your time and help.

I can get you started:
Add a trigger in Resources>Current project's triggers that triggers sendEmail() "on edit".
...

I just wrote a script that does that kind of thing but I wanted it to keep an eye on all the changes in the sheet but send a message only once every hour to avoid spamming my mailBox.
The script has 2 functions, one that collects the changes and stores them in text format and a second that sends email if any change occurred in the last hour.
The first function is called grabData and must be triggered by an onEdit installable trigger and goes like this :
function grabData(e){
Logger.log(JSON.stringify(e));
var cell = e.range.getA1Notation();
var user = e.user.email;
var time = Utilities.formatDate(new Date(),Session.getScriptTimeZone(),'dd-MM-yyyy')+' à '+Utilities.formatDate(new Date(),Session.getScriptTimeZone(),'hh:mm');;
if(user!='email1#email.com'&&cell!='A1'){
var dataUser1 = PropertiesService.getScriptProperties().getProperty('contentUser1');
if(dataUser1==null){dataUser1=''};
dataUser1+='\nCellule '+cell+' modifiée le '+time+' par '+user+' (nouvelle valeur = '+e.range.getValue()+')';
PropertiesService.getScriptProperties().setProperty('contentUser1',dataUser1);
}
if(user!='email2#email.com'&&cell!='A1'){
var dataUser2 = PropertiesService.getScriptProperties().getProperty('contentUser2');
if(dataUser2==null){dataUser2=''};
dataUser2+='\nCellule '+cell+' modifiée le '+time+' par '+user+' (nouvelle valeur = '+e.range.getValue()+')';
PropertiesService.getScriptProperties().setProperty('contentUser2',dataUser2);
}
}
The other function has a timer trigger, I set it to fire every hour but you can change it to your best fit.
function sendReport(){
var dataUser1 = PropertiesService.getScriptProperties().getProperty('contentUser1');
var dataUser2 = PropertiesService.getScriptProperties().getProperty('contentUser2');
if(dataUser1.length>1){
MailApp.sendEmail('email2#email.com', 'Modification dans le planning FFE', dataUser1);
PropertiesService.getScriptProperties().setProperty('contentUser1','');
}
if(dataUser2.length>1){
MailApp.sendEmail('email1#email.com', 'Modification dans le planning FFE', dataUser2);
PropertiesService.getScriptProperties().setProperty('contentUser2','');
}
}
after a mail has been sent, the stored data is deleted. No mail is sent if no change was recorded.
You can also notice that I have 2 different users and 2 different storage places so that each of them can see what the other does without being notified for his own modifications.
Since both function use installable triggers, this will run on your account so beware not to explode your quotas if you set the timer to a very short period.

Related

Running a Google Script from a duplicated speadsheet

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.

Get value from formula cell to trigger email sending

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.

Trigger script on non manual change/edit to cell - google script

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.

Test for missing user input, and stop or delay a Google script from running

I have the below Google script running in a Google sheet.
function sendNotification(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var cellvalue = ss.getActiveCell().getValue().toString();
var emailAdd = "email#yourdomain.com";
if(event.range.getA1Notation().indexOf("G") > -1 && sheet.getRange("G" + row).getDisplayValue() > 999 && emailAdd.length > 1)
{
var rowVals = getActiveRowValues(sheet);
var aliases = GmailApp.getAliases();
Logger.log(aliases);
var bodyHTML,o,sendTO,subject;//Declare variables without assigning a value
o = {};//Create an empty object
bodyHTML = "There has been a new allocation request from " + rowVals.name + " in the " + rowVals.team + " team.<br \> <br \> "
+ "<table border = \"1\" cellpadding=\"10\" cellspacing=\"0\"><tr><th>Issuing Depot</th><th>Delivery Date</th><th>Case Quantity</th></tr><tr><td>"+rowVals.depot+"</td><td>"+rowVals.date+"</td><td>"+rowVals.quantity+"</td></tr></table>"
+ "<br \>To view the full details of the request, use the link below.<br \> <br \>" +
"Allocation Requests"
+"<br \> <br \><i>This is an automated email. Please do not reply to it.<\i>";
o.htmlBody = bodyHTML;//Add the HTML to the object with a property name of htmlBody
o.from = aliases[0]; //Add the from option to the object
sendTO = "email#yourdomain.com";
subject = "Allocation Request - " + rowVals.quantity + " cases on " + rowVals.date,
GmailApp.sendEmail(sendTO,subject,"",o);//Leave the third parameter as an empty string because the htmlBody advanced parameter is set in the object.
};
}
function getActiveRowValues(sheet){
var cellRow = sheet.getActiveRange().getRow();
// get depot value
var depotCell = sheet.getRange("E" + cellRow);
var depot = depotCell.getDisplayValue();
// get date value
var dateCell = sheet.getRange("F" + cellRow);
var date = dateCell.getDisplayValue();
// get quantity value
var quantCell = sheet.getRange("G" + cellRow);
var quant = quantCell.getDisplayValue();
// return an object with your values
var nameCell = sheet.getRange("B" + cellRow);
var name = nameCell.getDisplayValue();
var teamCell = sheet.getRange("C" + cellRow);
var team = teamCell.getDisplayValue();
return {
depot: depot,
date: date,
quantity: quant,
name: name,
team: team
} }
It works fine, but if the person who fills out the spreadsheet doesn't fill out the columns in ascending order, the email that gets sent misses out information.
Is there a way to delay the running of the script until the row (columns B,C,D,E,F & G) has been completed? I've looked at utilities.sleep but not sure where to put it in the script. When I've tried to do so, it doesn't seem to make any difference.
Test the return object for missing values. The way that the following code does this is to convert the object to an array, and then get the length of the array. There should be 4 elements in the array. If the value of any of the variables that go into the object is undefined, then that element will be missing, and therefore the array will have fewer than 4 elements.
function sendNotification() {
var rowVals = getActiveRowValues(sheet);//Returns an object
var testArray = JSON.stringify(o).split(",");//Convert object to an array
Logger.log('length: ' + testArray.length)
if (testArray.length !== 4) {//Object must have 4 elements
Browser.msgBox('There is missing data!');
return; //quit
}
}
function getActiveRowValues() {
var depot = 'something';
var date;//For testing purposes - leave this as undefined
var name = 'the name';
var team = 'team is';
var o = {
depot: depot,
date: date,
name: name,
team: team
}
Logger.log(JSON.stringify(o))
return o;
}
You could improve upon this by highlighting cells with missing data, or determining exactly which piece of data is missing and informing the user.
This is probably not the answer that you want to hear. But I'm not familiar with any trigger that's associated to specific cells. The question has come up many times and the closest we have is the on edit event. I suppose you could check on each on edit to see if the appropriate cells contained appropriate data. Personally, I would prefer a dialog or sidebar for this situation and then I could have all of the power of Javascript in an html environment to help with the form submission process and in the end I'd probably just put a send button there.
I carried on playing around with utilities.sleep and have now got it to work as shown below.
function sendNotification(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var cellvalue = ss.getActiveCell().getValue().toString();
if(event.range.getA1Notation().indexOf("G") > -1 && sheet.getRange("G" + row).getDisplayValue() > 999)
{
Utilities.sleep(1200000)
var rowVals = getActiveRowValues(sheet);
var aliases = GmailApp.getAliases();
Logger.log(aliases);
var bodyHTML,o,sendTO,subject;//Declare variables without assigning a value
o = {};//Create an empty object
bodyHTML = "There has been a new allocation request from " + rowVals.name + " in the " + rowVals.team + " team.<br \> <br \> "
+ "<table border = \"1\" cellpadding=\"10\" cellspacing=\"0\"><tr><th>Issuing Depot</th><th>Delivery Date</th><th>Case Quantity</th></tr><tr><td>"+rowVals.depot+"</td><td>"+rowVals.date+"</td><td>"+rowVals.quantity+"</td></tr></table>"
+ "<br \>To view the full details of the request, use the link below.<br \> <br \>" +
"Allocation Requests"
+"<br \> <br \><i>This is an automated email. Please do not reply to it.<\i>";
o.htmlBody = bodyHTML;//Add the HTML to the object with a property name of htmlBody
o.from = aliases[0]; //Add the from option to the object
sendTO = "email#yourdomain.com";
subject = "Allocation Request - " + rowVals.quantity + " cases on " + rowVals.date,
GmailApp.sendEmail(sendTO,subject,"",o);//Leave the third parameter as an empty string because the htmlBody advanced parameter is set in the object.
};
}
function getActiveRowValues(sheet){
var cellRow = sheet.getActiveRange().getRow();
// get depot value
var depotCell = sheet.getRange("E" + cellRow);
var depot = depotCell.getDisplayValue();
// get date value
var dateCell = sheet.getRange("F" + cellRow);
var date = dateCell.getDisplayValue();
// get quantity value
var quantCell = sheet.getRange("G" + cellRow);
var quant = quantCell.getDisplayValue();
// return an object with your values
var nameCell = sheet.getRange("B" + cellRow);
var name = nameCell.getDisplayValue();
var teamCell = sheet.getRange("C" + cellRow);
var team = teamCell.getDisplayValue();
return {
depot: depot,
date: date,
quantity: quant,
name: name,
team: team
} }
It now delays the script from running for 1 minute, allowing time for the remaining cells to be completed before the script pull data from them.

Script won't stop sending emails

Below is my script. It sends emails, as its suppose to, but it continues to send emails even when the refereed cell already says "Sent". Can anyone show me where I went wrong?
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(2,1,lastRow,13);
var data = range.getValues();
for (i=0;i<data.length;i++){
var employeeName = data [i][6];
var startDate = data [i][2];
var endDate = data [i][3];
var oHours = data [i][4];
var email = data [i][14];
var emailAd = data [i][1];
var response = data [i][9];
if (response == "Approved" && email != "Sent"){
var subject = "Overtime Request";
var message = "Your request for overtime for " + employeeName + " has been " + response + ". This overtime should occur between " + startDate + " to " + endDate + " and should not exceed " + oHours + " hours.";
MailApp.sendEmail(emailAd,subject,message);
sheet.getRange(i+2,14).setValue("Sent");}
else if(response=="Declined" && email != "Sent"){
var subject = "Overtime Request";
var message = "Your request for overtime for " + employeeName + " has been " + response + ".";
MailApp.sendEmail(emailAd,subject,message);
sheet.getRange(i+2,14).setValue("Sent");}
else if(email == "Sent"){return;}
}}
Right now the email is undefined and email != "Sent" will always evaluate to true.
At this line:
var range = sheet.getRange(2,1,lastRow,13);
The getRange() method is getting the the columns A to M, and then at this line:
var email = data [i][14];
You're trying to get the value of the column O.
Remember that while a range index starts at 1, 1, the JavaScript array
will be indexed from [0][0].
You just need to change your sheet.getRange(2,1,lastRow,13); to sheet.getRange(2,1,lastRow,15); so you can get the value where you're setting the value "Sent" and evaluate it in your if() statement.