I recently adapted a short piece of script to automatically send an e-mail to me when a specific response appears (the word FAIL) in a 'Google docs' spreadsheet, that collects responses from volunteers using a 'Google Form' to record their weekly checks on life-saving equipment.
The email on FAIL is working well, having tested it with a few FAIL responses. However, as the form 'owner' I am receiving notifications from Google, for each normal PASS response submitted, telling me that the script failed to finish successfully.
8/4/15 10:57 AM
SendGoogleForm
ReferenceError: "found" is not defined. (line 35, file "Code")
formSubmit
It finishes successfully if a FAIL response is submitted, so I suspect that, due to my inexperience with scripts, I have not defined what should happen when found is 'not true'.
It's probably glaringly obvious to a more experienced script writer but none of the things I have tried with 'Else' seem to work.
Any suggestions would be greatly appreciated.
function Initialize() {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers)
ScriptApp.deleteTrigger(triggers[i]);
ScriptApp.newTrigger("SendGoogleForm")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit().create();
}
function SendGoogleForm(e) {
if (MailApp.getRemainingDailyQuota() < 1) return;
// Recipients email address
var email = "12345#abcde.co.uk";
// Subject for Google Form email notification
var subject = "Weekly Checks - FAIL Submitted";
var s = SpreadsheetApp.getActiveSheet();
var columns = s.getRange(1, 1, 1, s.getLastColumn()).getValues()[0];
var message = "A check form has been submitted containing a ** FAIL ** response: ";
// Look for a 'FAIL' response
var key = columns[keys];
for ( var keys in columns ) {
if ( e.namedValues[columns[keys]] == 'FAIL') {
found = true;
}
}
// Only include form fields that are not blank
if (found)
for (var keys in columns) {
var key = columns[keys];
if (e.namedValues[key] && (e.namedValues[key] !== "")) {
message += key + ' : ' + e.namedValues[key] + "\n\n";
}
}
MailApp.sendEmail(email, subject, message);
}
Your found variable is null whenever a non-"FAIL" occurs in your script and I think your if(found) is creating the error message.
You could Initialise your variable:
var found = false;
before you start testing it. That way, your if(found) will be false and it will skip down to your send email code.
Perhaps u can use
if ( e.namedValues["Pass or Fail"] == "Fail" ) { ... }
where "Pass or Fail" is the form question title. And also use e.range.getRow() to capture the sheet row that the responses were written.
Related
Gory title but I couldn't find a way of being clearer.
I have no experience with coding and I was wondering if doing something like what I'm about to explain would be possible.
This is my example sheet:
What I'm looking to do is to have automated emails sent out to the person assigned to the task if the task status is set to urgent, while referencing people by names and having an auxiliary sheet with all the names and corresponding emails.
I've browsed around and found some similar questions which I unfortunately had no success in adapting. The one thing I got is that I need to setup an onEdit trigger, which I've done, but I'm completely clueless from here on out.
Can someone point me in the right direction? I don't have a clue where to start.
Looking forward to hearing your advice.
Thanks and stay safe in these crazy times!
It was a funny exercise. I tried to make the script as clean and reusable as possible for others to be able to adapt it to their needs.
Usage
Open spreadsheet you want to add script to.
Open Script Editor: Tools / Script editor.
Add the code. It can be configured by adjusting variables in the top:
var trackerSheetName = 'Tracker 1'
var trackerSheetStatusColumnIndex = 2
var trackerSheetNameColumnIndex = 4
var triggeringStatusValue = 'Urgent'
var peopleSheetName = 'AUX'
var peopleSheetNameColumnIndex = 1
var peopleSheetEmailColumnIndex = 2
var emailSubject = 'We need your attention'
var emailBody = 'It is urgent'
function checkStatusUpdate(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var activeSheet = spreadsheet.getActiveSheet()
// skip if different sheet edited
if (activeSheet.getName() !== trackerSheetName) {
return
}
var editedRange = e.range
// skip if not a single cell edit
if (editedRange.columnStart !== editedRange.columnEnd || editedRange.rowStart !== editedRange.rowEnd) {
return
}
// skip if edited cell is not from Status column
if (editedRange.columnStart !== trackerSheetStatusColumnIndex) {
return
}
// skip if Status changed to something other than we're looking for
if (e.value !== triggeringStatusValue) {
return
}
var assigneeName = activeSheet.getRange(editedRange.rowStart, trackerSheetNameColumnIndex, 1, 1).getValue()
var peopleSheet = spreadsheet.getSheetByName(peopleSheetName)
var people = peopleSheet.getRange(2, 1, peopleSheet.getMaxRows(), peopleSheet.getMaxColumns()).getValues()
// filter out empty rows
people.filter(function (person) {
return person[peopleSheetNameColumnIndex - 1] && person[peopleSheetEmailColumnIndex - 1]
}).forEach(function (person) {
if (person[peopleSheetNameColumnIndex - 1] === assigneeName) {
var email = person[peopleSheetEmailColumnIndex - 1]
MailApp.sendEmail(email, emailSubject, emailBody)
}
})
}
Save the code in editor.
Open Installable Triggers page: Edit / Current project's triggers.
Create a new trigger. Set Event Type to On edit. Keep other options default.
Save the Trigger and confirm granting the script permissions to access spreadsheets and send email on your behalf.
Go back to your spreadsheet and try changing status in Tracker 1 tab for any of the rows. Corresponding recipient should receive an email shortly.
This should get you started:
You will need to create an installable trigger for onMyEdit function. The dialog will help you to design you email by giving you an html format to display it. When you're ready just comment out the dialog and remove the // from in front of the GmailApp.sendEdmail() line.
function onMyEdit(e) {
//e.source.toast('Entry');
const sh=e.range.getSheet();
if(sh.getName()=="Tracker") {
if(e.range.columnStart==2 && e.value=='Urgent') {
//e.source.toast('flag1');
const title=e.range.offset(0,-1).getValue();
const desc=e.range.offset(0,1).getValue();
const comm=e.range.offset(0,3).getValue();
if(title && desc) {
var html=Utilities.formatString('<br />Task Title:%s<br />Desc:%s<br />Comments:%s',title,desc,comm?comm:"No Additional Comments");
//GmailApp.sendEmail(e.range.offset(0,2).getValue(), "Urgent Message from Tracker", '',{htmlBody:html});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html).setWidth(600), 'Tracker Message');
e.source.toast('Email Sent');
}else{
e.source.toast('Missing Inputs');
}
}
}
}
GmailApp.sendEmail()
I'm trying to run a script to send automated emails to a list of people (I needed to edit out a bit of information).
function sendEmail(){
for (var key in emailBank){
var email = getEmail(key);
var bodyText = emailBank[key]['body'];
var commitmentType = emailBank[key]['type'];
GmailApp.sendEmail(key,"Subject Line", bodyText {htmlBody:bodyText});
}
}
I'm using an array (emailBank) to store information from a spreadsheet before sending it out. The function should loop through and send each person an email.
On occasion, I'm getting the following error: "We're sorry, a server error occurred. Please wait a bit and try again." for the line including GmailApp.
Can you think of any to verify if an email is sent or not? My concern is that half of the emails will be sent and half will not, so I wouldn't know who actually received the email and at which point the loop stopped.
Any ideas are appreciated here!
Ok, so I think I found a solution to this after trying out a few methods:
// Variables for error checking later
var emailRecipients = "";
var trueRecipients = "";
//The sendEmail function to send the email as expected
//It will also increment the emailRecipients variable
//only after the email has been sent
function sendEmail(){
for (var key in emailBank){
var email = getEmail(key);
var bodyText = emailBank[key]['body'];
var commitmentType = emailBank[key]['type'];
GmailApp.sendEmail(key,"Subject Line", bodyText {htmlBody:bodyText});
emailRecipients += key + "<br>";
}
}
//This function will only run if there was an error
//It will check increment all of the email addresses
//that should have been sent a message and then send me an
function errorEmail(){
for (var key in emailBank){
trueRecipients += key + "<br>";
}
var errorBodyText = "Emails sent to:<br><br>" + emailRecipients + "<br><br>Number of emails that should have sent:<br><br>" + trueRecipients;
GmailApp.sendEmail("example#gmail.com","Email Errors", errorBodyText,{htmlBody:errorBodyText});
}
function reminderEmail(){
try{
sendEmail();
}
catch(e){
errorEmail();
}
}
As a temporary solution, you could check the last sent email after calling sendEmail() method.
/* the search() method returns an array of GmailThread objects.
Pop() returns the GmailThread */
var threads = GmailApp.search("label: sent", 0, 1).pop(); // returns the most recent thread in 'Sent Mail'
var msg = threads.getMessages().pop(); //returns the most recent message
Logger.log(msg.getId()); //returns unique id for the message
Logger.log(msg.getTo()); //returns comma-separated list of recipients
One option would be to store the message id and check if it changes on each run of the loop. You may also directly compare email properties, e.g. the recipient's email, against the properties of the message you sent.
I'm modifying Amit's code ( found here: http://labnol.org/?p=20884)
to try to send email with the data from a Google Form.
But what I'm trying to grab is from his keys and columns.
I want to specifically take the first 1 and 2 column's data from the row in question and use it as a var in the subject field.
But the output (in email and when sent to asana) is listed as undefined. Where did I go wrong?
/*
Send Google Form Data by Email v4.2
Written by Amit Agarwal amit#labnol.org
Source: http://labnol.org/?p=20884
*/
/**
* #OnlyCurrentDoc
*/
function Initialize() {
try {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers)
ScriptApp.deleteTrigger(triggers[i]);
ScriptApp.newTrigger("EmailGoogleFormData")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit().create();
} catch (error) {
throw new Error("Please add this code in the Google Spreadsheet");
}
}
function EmailGoogleFormData(e) {
if (!e) {
throw new Error("Please go the Run menu and choose Initialize");
}
try {
if (MailApp.getRemainingDailyQuota() > 0) {
// You may replace this with another email address
var email = "x+00000000#mail.asana.com";
// Enter your subject for Google Form email notifications
var key, entry,
message = "",
ss = SpreadsheetApp.getActiveSheet(),
cols = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
// Iterate through the Form Fields
for (var keys in cols) {
key = cols[keys];
entry = e.namedValues[key] ? e.namedValues[key].toString() : "";
// Only include form fields that are not blank
if ((entry !== "") && (entry.replace(/,/g, "") !== ""))
message += key + ' :: ' + entry + "\n\n";
var first = entry[1];
var last = entry[2];
var subject = first+" "+last+": Interested Candidate";
}
MailApp.sendEmail(email, subject, message);
}
} catch (error) {
Logger.log(error.toString());
}
}
/* For support, contact developer at www.ctrlq.org */
entry is a string, defined here:
entry = e.namedValues[key] ? e.namedValues[key].toString() : "";
...which you later treat as an array:
var first = entry[1];
var last = entry[2];
At this point, first and last will both be undefined, because entry isn't an array. Further, this is inside a for loop that's traversing all the columns in the row - you can't see any bad side-effect from that, but these assignments and generation of a subject are happening multiple times.
That last clue suggests a better way to achieve your goal. Define the first and last variables before the loop, with default values. Then when looping over columns, watch for the columns containing the candidates' name, and update the default contents. Finally, after the loop, generate the subject line.
function EmailGoogleFormData(e) {
if (!e) {
throw new Error("Please go the Run menu and choose Initialize");
}
try {
if (MailApp.getRemainingDailyQuota() > 0) {
// You may replace this with another email address
var email = "x+00000000#mail.asana.com";
// Enter your subject for Google Form email notifications
var key, entry,
first = "unknown", last = "unknown",
message = "",
ss = SpreadsheetApp.getActiveSheet(),
cols = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
// Iterate through the Form Fields
for (var keys in cols) {
key = cols[keys];
entry = e.namedValues[key] ? e.namedValues[key].toString() : "";
// Only include form fields that are not blank
if ((entry !== "") && (entry.replace(/,/g, "") !== ""))
message += key + ' :: ' + entry + "\n\n";
if (key == "first") { // Assumes "first" is column header
first = entry;
}
if (key == "last") { // Assumes "last" is column header
last= entry;
}
}
var subject = first+" "+last+": Interested Candidate";
MailApp.sendEmail(email, subject, message);
}
} catch (error) {
Logger.log(error.toString());
}
}
Sandy Good has created a similar app Data Director. I don't know why he did not mention it here? May be it's not what you're looking for.
I haven't used it yet, but thought his works might help someone who needs it.
----------------------------------------
OVERVIEW:
Send form data to different sheet. Integrate with Calendar. Sends emails. Makes an Edit URL and/or a PreFilled URL.
The Data Director for Forms Add-on has multiple features. It can send the form response to an alternate spreadsheet. It can send an email or multiple emails when the Form is submitted. It can add a guest to your calendar event.
When your Google Form is submitted, the Data Director for Forms Add-on can get the last form submission, and save it to a second spreadsheet destination of your choice. The destination spreadsheet can be any Google spreadsheet that your Google account has permission to write to. For example: Your Google Form currently writes data to a spreadsheet, but you want the form response to also go into a second sheet in the same spreadsheet. This Add-on can do that. Or the Add-on can write a copy of the form response to a completely different spreadsheet.
You should install this add-on if you want to save a copy the form response to to a destination other than what is set in the Form's design.
But that's not all Data Director can do! Data Director will also create an Edit URL and/or a PreFilled URL, and save those links to the spreadsheet.
There's even more! It will also send an email to the email address of your choice with a custom message. This is an extra option that you may want or need to use.
Here's a list of What Data Director can do!
Send a copy of the form response to a Google spreadsheet.
The same Google spreadsheet that is already receiving the Form response, or
A different spreadsheet than is currently receiving the Form response.
Exclude the timestamp from the copied response if you choose. The default is to include the timestamp.
Create an Edit URL and save a link to the destination spreadsheet.
Create a PreFilled URL and save the link to the destination spreadsheet.
Send multiple emails to the email addresses of your choice.
Send an email to the email address collected from a Form field.
Include the Edit Url and/or the PreFilled Url in the email.
CC the email to the address of your choice, or not.
Includes the option to specify the subject line.
The Body of the email can be written in the settings for the email. No need to create a template email.
I want to create a process, when person submits a form and I get only filled in information (not every field is required for filling). Found code (http://www.labnol.org/internet/google-docs-email-form/20884/), did everything according to author, but doesn't work me. I'm not a specialist in coding, so I need your help.
Here is the code:
function Initialize() {
var triggers = ScriptApp.getScriptTriggers();
for(var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendGoogleForm")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendGoogleForm(e)
{
try
{
// You may replace this with another email address
var email = "justbeincredible#gmail.com";
// Optional but change the following variable
// to have a custom subject for Google Form email notifications
var subject = "New Driver Application Arrived";
var s = SpreadsheetApp.getActiveSheet();
var columns = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
var message = "";
// Only include form fields that are not blank
for ( var keys in columns ) {
var key = columns[keys];
if ( e.namedValues[key] && (e.namedValues[key] != "") ) {
message += key + ' :: '+ e.namedValues[key] + "\n\n";
}
}
// This is the MailApp service of Google Apps Script
// that sends the email. You can also use GmailApp for HTML Mail.
MailApp.sendEmail(email, subject, message);
} catch (e) {
Logger.log(e.toString());
}
}
Here is what I get when I run Initialize to test:
Cannot call overloaded constructor forSpreadsheet with parameters
(null) because there is more than one matching constructor signature:
interface SpreadsheetTriggerBuilder forSpreadsheet(Spreadsheet)
interface SpreadsheetTriggerBuilder forSpreadsheet(String) (line 12,
file "Code")
Add the code inside the Script Editor of the Google Spreadsheet that is collecting the responses and not inside the Form editor.
[source]
Explanation: The error message indicates that forSpreadsheet() had a parameter of (null). You fed it SpreadsheetApp.getActiveSpreadsheet(), so that evaluated to null. That method is only applicable within Spreadsheet-bound scripts.
The add-on doesn't work on brave browser. Try running on Google Chrome if you are not currently using it.
I've been recently having trouble with what I believe to be a concurrency issue when people are submitting the form near the same times, which is resulting in lost data for a google form. I'm already using the Lock service to prevent this issue, but I still seem to have problems. http://googleappsdeveloper.blogspot.com/2011/10/concurrency-and-google-apps-script.html
The form currently has onFormSubmit triggers: formSubmitReply and logMessage. formSubmitReply sends a confirmation to people that submitted the form and logMessage is supposed to back up the information in a separate spreadsheet in the case that rows in the regular spreadsheet get clobbered. It should be extracting the values from the formSubmit event and then appending it to the "log" sheet.
I've included all the current code for the script and replaced emails with place holders. Can I get some help identify anything buggy in the code that could be preventing the form from recording rows in the form?
function getColIndexbyName(colName){
var sheet=SpreadsheetApp.getActiveSheet();
var rowWidth=sheet.getLastColumn();
var row=sheet.getRange(1,1,1,rowWidth).getValues();//this is the first row
for ( i in row[0]){
var name=row[0][i];
if(name == colName || new RegExp(colName,'i').test(name)){
return parseInt(i)+1;
}
}
return -1
}
function makeReceipt(e){
/*This is for Student Volunteer auto-confirmation*/
var ss,sheet, rowWidth, headers, rowWidth,curRow, values, greeting, robot, msg, space, newline;
curRow=e.range.getRow();
ss=SpreadsheetApp.getActiveSpreadsheet();
sheet=ss.getSheetByName("RAW");
rowWidth=sheet.getLastColumn();
headers=sheet.getRange(1,1,1,rowWidth).getValues();
values=sheet.getRange(curRow,1,1,rowWidth).getValues();
greeting='Hi '+sheet.getRange(curRow,getColIndexbyName('First Name'),1,1).getValue()+"! <br><br>"+ ' ';
robot="<i>Below are the responses you submitted. Please let us know if any changes arise!</i> <br><br>";
msg=greeting+robot;
space=' ';
newline='<br>';
for( i in headers[0]){
//only write non "Reminders" column values
if(headers[0][i]!="Reminders"){
msg+="<b>";
msg+=headers[0][i];
msg+="</b>";
msg+=":";
msg+=space;
msg+=values[0][i];
msg+=newline;
}
}
return msg;
}
/**
* Triggered on form submit
**/
function formSubmitReply(e) {
var ss, row, mailIndex, userEmail, message, appreciation;
var lock = LockService.getPublicLock();
if(lock.tryLock(60000)){
try{
ss=SpreadsheetApp.getActiveSheet();
row=e.range.getRow();
mailIndex=getColIndexbyName('Email Address');
userEmail=e.values[mailIndex-1];
message=makeReceipt(e);
MailApp.sendEmail(userEmail, 'BP Day 2012 Confirmation for'+' '+userEmail,message,{name:"Name", htmlBody:message, replyTo:"example#example.com"});
messageAlert100(e);
} catch(err){
e.values.push("did not send email");
MailApp.sendEmail(""example#example.com","error in formSubmitReply"+err.message, err.message);
}
logToSpreadsheet(e);
} else {
//timeOut
try{
if(e && e.values){
logToSpreadsheet(e);
e.values.push("did not send email");
}
}catch(err){
MailApp.sendEmail("example#example.com", "error in logging script block "+err.message, err.message)
}
}
}
/**
* Triggered on form submit
**/
function messageAlert100(e){
var cheer_list, curRow, cheer_list, cheer_index, cheer, ss=SpreadsheetApp.getActiveSpreadsheet();
if(e && e.range.activate){
curRow=e.range.getRow();
}
cheer_list=["Congratulations!", "Give yourself a pat on the back!", "Yes!", "Cheers!","It's time to Celebrate!"];
cheer_index=Math.floor(Math.random()*cheer_list.length);
cheer=cheer_list[cheer_index];
if(typeof(curRow) != "undefined" && curRow % 100 ==0){
MailApp.sendEmail("example#example.com", ss.getName()+": "+cheer+" We now have "+ curRow + " Volunteers!", cheer+" We now have "+ curRow + " Volunteers!");
}
}
/**
*
**/
function logToSpreadsheet(e){
var ss=SpreadsheetApp.getActiveSpreadsheet(), sh;
if(!ss.getSheetByName("log")){
sh=ss.insertSheet("log");
}
sh=ss.getSheetByName("log");
if(e && e.values !==null){
sh.appendRow(e.values)
} else {
sh.appendRow(e);
}
Logger.log(e);
}
There is a very simple approach that I use to avoid concurrency issues with forms, I had to imagine that before GAS offered the lock method.
Instead of using the on form submit trigger I use a timer trigger (every few minutes or so) on a function that checks a column for a flag (MAIL SENT) ... if the flag is not present I send the email with processed data, copy to the backup sheet and set the flag. I do this on every row starting from the last one and stop when I find the flag. This way I'm sure all datarows are processed and no duplicate mail is sent.
It is actually very simple to implement, your script will need only a few modifications.
Viewed from the users side the result is nearly the same as they receive a mail just a few minutes after their submission.
EDIT : of course in this setup you cannot use e parameter to get the form data but you'll have to read data on the sheet instead... but that's not really a big difference ;-)