I need my google sheet to send emails every time a condition becomes true.
In this case, every time value of C2 is lower than value of J2.
On the cell L2 there is the email address.
The code (found online and just edited)
function CheckPrice() {
var LastPriceRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Alerts").getRange("C2");
var LastPrice = LastPriceRange.getValue();
var EntryLimitRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Alerts").getRange("J2");
var EntryLimit = LastPriceRange.getValue();
var StockNameRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Alerts").getRange("B2");
var StockName = LastPriceRange.getValue();
// Check totals sales
if (LastPrice < EntryLimit){
// Fetch the email address
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Alerts").getRange("L2");
var emailAddress = emailRange.getValues();
// Send Alert Email.
var message = 'Ticker ' + StockName + ' has triggered the alert';
var subject = 'Stock Alert';
MailApp.sendEmail(emailAddress, subject, message);
}
}
With this code I don't receive any error, but I don't even receive the email.
I granted permissions as requested when I run the script for the first time.
On L2 I put the same email address I granted permission (I send email to myself).
I did a try even putting a secondary email address I have.
Can you please show me what's wrong ?
Issues:
See the first lines of your code where you define LastPrice,
EntryLimit and StockName. All of them are coming from the same
range: LastPriceRange.
I also removed all the unnecessary calls in your script. There is no
need to call
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Alerts") so
many times when you can just put it in a variable and use that
variable instead.
Also, you don't need to define unnecessary
variables. For example, you can get the value of a cell with one line:
sh.getRange("B2").getValue().
Solution:
function CheckPrice() {
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Alerts");
const LastPrice = sh.getRange("C2").getValue();
const EntryLimit = sh.getRange("J2").getValue();
const StockName = sh.getRange("B2").getValue();
// Check totals sales
if (LastPrice < EntryLimit){
// Fetch the email address
const emailAddress = sh.getRange("L2").getValue();
// Send Alert Email.
const message = 'Ticker ' + StockName + ' has triggered the alert';
const subject = 'Stock Alert';
MailApp.sendEmail(emailAddress, subject, message);
}
}
If you want an email to be sent when you edit a particular cell, then you need to transform the aforementioned solution to an onEdit(e) trigger. If you want a time basis trigger then you can just use the solution above directly.
Related
If you are reading my question, Thank you for taking the time out of your day to help.
Background:
I have a form that my field techs use in order to request parts, I would like to keep them updated on the status of a part order with an automated sms text to their phone.
Specifics:
Link to test Sheet
https://docs.google.com/spreadsheets/d/1hEDEk-3-z3Wh6PNLoY6PgmbSJZ7OcdMR0kFCl0BRrYU/edit?usp=sharing
Parts Request (Picture)
Status Change
When a Status is changed in (column G), this will trigger an SMS to be sent.
Recipient
The SMS will be sent to the Team Lead ( Column B) in that row.
Example: Status is changed (G2), SMS is sent to Team Lead (B2).
Employee Info (Picture)
Employee Information:
The Script pulls the Employee Telephone number(Employee Info! B2) from the Employee Info Sheet
Text Body:
The Message that is sent would be the entire row in the text message
-Team Lead
-Type of Request
-Job Name
-Part Description
-QTY Missing
-Status
The Script i have tried so far has been a simple one, based on a trigger of anytime a change is made to the sheet. Here is what i have used so far, this has worked and has been sending generic texts. Any Help would be greatly appreciated.
function sendText() {
var EmailTo = "'Mobile Number'"#txt.att.net";
var subject = "Whatever";
var body = "Text";
MailApp.sendEmail(EmailTo, subject, body);
}
The processes needed to achieve your goal involves fetching the desired details on the row wherein the status of the request changes, incorporating the given phone number to the appropriate carrier domain, and adding an installable trigger so that the script will automatically work when there are changes to the Status Column.
Here is the script:
function sendUpdates(e) {
var ss = e.source;
var shEmployeeInfo = ss.getSheetByName("Parts Request");
var shPartsRequest = ss.getSheetByName("Employee Info");
var row = e.range.getRow();
var column = e.range.getColumn();
if(column == 7) { //To limit checking of changes to the Status column
var info = shEmployeeInfo.getRange(row, 2, 1, 6).getValues();
var header = shEmployeeInfo.getRange(1, 2, 1, 6).getValues();
var carrier = shPartsRequest.getRange(row,4).getValues();
const subject = "Insert Subject Here."; //Edit this to change the subject
const carrierMap = { "ATT": 'txt.att.net',
"T-Mobile": 'tmomail.net',
"Sprint": 'messaging.sprintpcs.com',
"Verizon": 'vtext.com',
"Cricket": 'mms.mycricket.com' }
var phoneNumber = shPartsRequest.getRange(row,2).getValues();
var emailTo = `${phoneNumber}#${carrierMap[carrier]}`;
var body = "";
for(let i = 0; i <= 5; i++) {
body += header[0][i] + ": " + info[0][i] + "\n";
}
MailApp.sendEmail(emailTo, subject, body);
}
}
The installable trigger should be set as:
Please refer to the Installable Triggers Guide for more information.
As for the result, I made a test case and got this:
I am creating a Google Script that fills a Google Doc template based on the fields of a Google Forms and sends the generated PDF via email to the user.
All of the steps that I followed are explained in detail here: Hacking it: Generate PDFs from Google Forms
The script (obtained from the article) is:
function onSubmit(e) {
const rg = e.range;
const sh = rg.getSheet();
//Get all the form submitted data
//Note: This data is dependent on the headers. If headers, are changed update these as well.
const cName = e.namedValues['Client Name'][0];
const cEmail = e.namedValues['Client Email'][0];
const cAddress = e.namedValues['Client Address'][0];
const cMobile = e.namedValues['Client Mobile'][0];
const sendCopy = e.namedValues['Send client a copy?'][0];
const paymentType = e.namedValues['What is your agreed upon payment schedule?'][0];
const fixedCost = e.namedValues['What was your agreed upon cost for the project?'][0];
const hourlyRate = e.namedValues['Hourly Rate'][0];
const manHours = e.namedValues['Total man hours'][0];
const services = e.namedValues['Select the services'][0];
//Consequential Data
const tax = 18.5
var subtotal = 0;
var taxAmt = 0;
var payableAmt = 0;
//if the user has selected hourly payment model
//Note: Be careful that the responses match the elements on the actual form
switch (paymentType ){
case 'Hourly Rate':
subtotal = hourlyRate*manHours;
taxAmt = subtotal * (tax/100);
payableAmt = +subtotal + +taxAmt;
break;
case 'Fixed Cost':
subtotal = fixedCost;
taxAmt = fixedCost * (tax/100)
payableAmt = +fixedCost + +taxAmt;
break;
}
const invoiceID = 'IN' + Math.random().toString().substr(2, 9);
var formattedDate = Utilities.formatDate(new Date(), "IST", "dd-MMM-yyyy");
//Set the consequential data in the columns of the spreadsheet for record keeping
//Note: These variable are dependent on the sheet's columns so if that changes, please update.
const row = rg.getRow();
const payableAmtCol = 2; //B
const invoiceIDCol = 3; //C
sh.getRange(row,payableAmtCol).setValue(payableAmt);
sh.getRange(row,invoiceIDCol).setValue(invoiceID);
//Build a new invoice from the file
//Folder and file IDs
const invoiceFolderID = '<invoice-folder-id>';
const invoiceFolder = DriveApp.getFolderById(invoiceFolderID);
const templateFileID = '<template-id>';
const newFilename = 'Invoice_' + invoiceID;
//Make a copy of the template file
const newInvoiceFileID = DriveApp.getFileById(templateFileID).makeCopy(newFilename, invoiceFolder).getId();;
//Get the invoice body into a variable
var document = DocumentApp.openById(newInvoiceFileID);
var body = document.getBody();
//Replace all the {{ }} text in the invoice body
body.replaceText('{{Invoice num}}', invoiceID);
body.replaceText('{{Date}}', formattedDate);
body.replaceText('{{Client Name}}', cName);
body.replaceText('{{Client Address}}', cAddress);
body.replaceText('{{Client Mobile}}', cMobile);
body.replaceText('{{Client Email}}', cEmail);
body.replaceText('{{Services}}', services.split(', ').join('\n'));
body.replaceText('{{Subtotal}}', subtotal);
body.replaceText('{{Tax Value}}', taxAmt);
body.replaceText('{{Total}}', payableAmt);
//In the case of hourly rate payment type, let's add an additional message giving the rate and the man hours.
if(paymentType.includes('Hourly Rate')){
//It should look something like this on the invoice
//Hourly Rate
//Rate of Rs.1200/hour
//Completed 50 man hours
const message = paymentType + '\nRate of Rs.' + hourlyRate + '/hour\nCompleted ' + manHours + ' man hours';
body.replaceText('{{Payment Type}}', message);
} else {
body.replaceText('{{Payment Type}}', paymentType);
}
document.saveAndClose();
//send email with the file
var attachment = DriveApp.getFileById(newInvoiceFileID);
GmailApp.sendEmail(cEmail, '<subject>,
'<body>',
{attachments: [attachment.getAs(MimeType.PDF)]});
}
The code works fine. Now I need that the user can edit its response after he press "Send Form" on Google Forms. So I decided to check "Respondents can edit after submit". Then I need to send the document again via GmailApp with the edited fields. So I created a new trigger: Edit (from a Spreadsheet). The other trigger is Form submit.
However I have a problem. When the user edits a field and press, again, "Send Form", the trigger "Edit" is activated with the following error: Failed to send email: no recipient.
If I go to the Spreadsheet responses I can see the edited row (because the cell has a comment "The person who responded has updated this value"), and the column mail is not edited but it stills throwing the exception.
How can we solve this problem if cEmail was never edited?
Searchs
I could find some interesting answers:
Failed to send email: no recipient
Google scripts “Failed to send email: no recipient” but email arrives
Failed to send email: no recipient, what is wrong with my code?
They seem to describe that a blank row can be generated when the trigger "Edit" is activated. However I don't see why this could happen, and how I can solve it since the Spreadsheet Responses is automatically edited after a new user submit an answer.
When a form response is edited the on form submit event object properties values and namedValues only include values for those questions that were edited.
To fix the error Failed to send email: no recipient, replace
GmailApp.sendEmail(cEmail, '<subject>,
'<body>',
{attachments: [attachment.getAs(MimeType.PDF)]});
by
const recipientIdx = 1; // This is the 0 based index of the column having the recipient email address
const recipient = cEmail ? cEmail : e.range.getValues().flat()[recipientIdx]
GmailApp.sendEmail(recipient , '<subject>',
'<body>',
{attachments: [attachment.getAs(MimeType.PDF)]});
P.S. Instead of hardcoding the value assigned to recipientIdx you might use some code to get it based on the column headers.
NOTE: The above only will prevent the error mentioned in the question. In order to make the script work you will have to apply the same idea for all the fields: Read the missing values from the spreadsheet by using the e.range.getValues().flat().
Related
How to check if current submission is editing response or a new response
How to check if current form submission is editing response
I've built a script that will get the issue with a form submission (D2) then output the text with the information in an email. Right now the Email address location (B2) and issue (D2) are hard coded. How can I work the code to get the email and issue from only the last row submitted?
function SendNot() {
// Fetch the Issue
var reasonRange =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TriggerSettings").getRange("D2");
var reason = reasonRange.getValue();
// Check for Issue
if (reason==="nogps"){
// Fetch the email address
var emailRange =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PlayerInfo").getRange("B2");
var emailAddress = emailRange.getValues();
// Send Alert Email.
var message = 'No GPS...';
var subject = 'Latest VDGL Entry...';
GmailApp.sendEmail(emailAddress, subject, message);
}
else if (reason==="nobarcode"){
// Fetch the email address
var emailRange =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PlayerInfo").getRange("B2");
var emailAddress = emailRange.getValues();
// Send Alert Email.
var message = 'No Barcode...';
var subject = 'Latest VDGL Entry...';
GmailApp.sendEmail(emailAddress, subject, message);
}
}
You can calculate the last row with content using the following method:
sheet.getLastRow()
In your example you can calculate this as follows:
var d_size = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TriggerSettings").getLastRow();
var b_size = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PlayerInfo").getLastRow();
and then you can use d_size to grab the last row reason elements:
var reasonRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TriggerSettings").getRange("D"+d_size);
and b_size to grab the last row email elements:
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PlayerInfo").getRange("B"+b_size);
You can also use a different syntax to get the desired range:
Instead of getRange("D"+d_size) -> getRange(d_size,4)
Instead of getRange("B"+b_size) -> getRange(b_size,2)
References:
Sheet.getLastRow()
I am using this code to send emails (composing email content getting text from the sheet named ranges:
//compose issue emails to student and admin
function composeIssueEmail() {
//student's name, last name and email
var email = ss.getRangeByName("CourseProgressEmail").getValue()
var name = ss.getRangeByName("CourseProgressName").getValue()
var lastName = ss.getRangeByName("CourseProgressStudentLastName").getValue()
var subj = ss.getRangeByName("SetUpIssueTitle").getValue()
var subject = subj.replace("*imya*", name)
var bodyText = ss.getRangeByName("SetUpIssueBody").getValue()
var body = bodyText.replace("*imya*", name)
var link = getChecksheetURL()
var text = body.replace("*link*", link)
//send email to student
var studentEmail = sendEmail(email, subject, text)
var adminEmail = "AGcourseSup#gmail.com"
var adminSubj = ss.getRangeByName("SetUpAdminIssueTitle").getValue()
var adminSubject = adminSubj.replace("*imya*", name)
var adminSubjectFinal = adminSubject.replace("*familia*", lastName)
var adminText = ss.getRangeByName("SetUpAdminIssueBody").getValue()
var adminTextReplace = adminText.replace("*imia*", name)
var adminBody = adminTextReplace.replace("*familia*", lastName)
var adminText = adminBody.replace("*link*", link)
//send email to admin
sendEmail(adminEmail, adminSubjectFinal, adminText)
}
//gets current checksheet URL
function getChecksheetURL() {
var Url = ss.getUrl()
var linkMiddle = "#gid="
var sheetID = sheet.getSheetId()
var shecksheetURL = Url + linkMiddle + sheetID
return shecksheetURL
}
//sends emails
function sendEmail(email, subject, body) {
GmailApp.sendEmail(email, subject, body)
}
Execution transcript:
[19-06-12 16:39:43:396 EEST] Execution succeeded [2.399 seconds total runtime]
It sends stably to the gmail account that is the same as spreadsheet's one.
But to another gmail account it sends about every other time.
Details:
This code is executed (I log the line after this code)
The emails are visible in my outbound box but not arriving to any of the boxes of the recepient gmail.
Not in spam etc.
I don't get any messages, error or bounce notifications.
I tried MailApp instead - it's even worse and sometimes doesn't send even to my own email.
I tried to change things in settings config, but didn't find anything to work.
I set up a filter "never send to spam" and "always star it" - didn't work.
I deleted a link from it so it has no link - didn't work.
What can be a solution?
I handled this issue. The issue is about anti-spam filters not about the code.
I gained more trust to the email account by adding "Reply To" option within GmailApp.sendEmail method. It magically solved the problem so each email reaches target now.
I would like to receive notifications when domain will expire. So I created a spreadsheet with a list of website and date of expiration. It has also a condition in Column A that when a domain is about to expire in 10 days it will appear Send notification as cell value. You can view my spreadsheet here.
With the values in column A, I would like to receive emails telling that the www.sample.com will expire on --some date here--.
For example, when Column A have new values equal to Send notification, then send email.
What I have tried and encountered:
var cell = sheet.getActiveCell().getA1Notation();
var row = sheet.getActiveRange().getRow();
var cellvalue = sheet.getActiveCell().getValue().toString();
var recipients = "youremail#gmail.com";
var domain = '';
var expirydate= '';
if(cell.indexOf('A')!=-1 && cell.indexOf('A') == 'Send notification'){
domain = sheet.getRange('B'+ sheet.getActiveCell().getRowIndex()).getValue();
expirydate = sheet.getRange('D'+ sheet.getActiveCell().getRowIndex()).getValue()
}
var subject = 'Expiry Notification : '+sheet.getName();
var body = 'Website will expire! ' + domain + ' is about to expire on ' + expirydate;
MailApp.sendEmail(recipients, subject, body);
Logger.log(body);
Trying this script only sends me notification without the value of the cell.
I would like this works even when the spreadsheet is not open / I am offline. So I guess I will be using the time driven event (every week).
Any help is much appreciated!
Prepare yourself, my answers tend to be long and explanatory.
Take this block:
if(cell.indexOf('A')!=-1 && cell.indexOf('A') == 'Send notification'){
domain = sheet.getRange('B'+ sheet.getActiveCell().getRowIndex()).getValue();
expirydate = sheet.getRange('D'+ sheet.getActiveCell().getRowIndex()).getValue()
}
In this block we can see that the if will always return FALSE. That is because cell.indexOf('A') cannot be a string, it will be an integer of the index. You want to be checking the value. I assume that is why you have the cellvalue variable.
Furthermore, your use of getRange() is also off. Why bother with A1 notation if you are getting indexes anyway. Instead I will go over the code and offer a different way of coding this.
Ok, let's start from the top of the code. You mentioned that you want this to run offline. We immediately get a problem here:
var cell = sheet.getActiveCell().getA1Notation();
this will be meaningless once you fire script based on a timer. I would recommend batching your data collection. First to be sure that you are not using getActiveSheet():
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Send notification')
OR
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]
to get the first sheet or the sheet by it's name. Then we get the entire list into a 2D array.
var vals = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues()
So we wish to check all domains. So now that we have all the values we wish to loop through them (we assume we left var domain and var expirydate as is)
var i
for (i = 0; i < vals.length; i++) {
if (vals[i][0] == 'Send notification!') {
domain = vals[i][1] //we get the domain name.
expirydate = vals[i][3] //we get the expire date
sendNotification(domain, expirydate); //use a seperate fu
}
}
where in the above block code I would seperate the function
function sendNotification(domain, expirydate) {
var subject = 'Expiry Notification : '+ domain;
var body = 'Website will expire! ' + domain + ' is about to expire on ' + expirydate;
MailApp.sendEmail(recipients, subject, body);
Logger.log(body);
}
of course you can leave the code inside of the for loop, but this will look cleaner. Also, I am not sure you really wanted var subject = 'Expiry Notification : '+sheet.getName(); because that will send all emails with the title Expiry Notification : Send notification because that is the sheet name (the tab at the bottom of the spreadsheet)