Auto send Email from Sheets Using Google Scrip - google-apps-script

I am trying to automate an email system where whenever someone submits a form, therefore editing the spreadsheet, google-scripts will send me an email containing the message. The code works perfectly fine and does what I want. However, I need to manually run the script every time which defeats the purpose. I have tried to do the function onEdit(e) however it never worked. I have also tried the triggers feature of AppsScript with no luck.
/**
* Sends emails with data from the current spreadsheet.
*/
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = sheet.getLastRow();
var numRows = sheet.getLastRow();
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, sheet.getLastColumn());
// Fetch values for each row in the Range.
var data = dataRange.getValues();
var emailAddress = 'email#email.com'; // First column
var message = data[0][3]; // Fourth column;
var subject = 'A Subject';//this will always be the same
Logger.log(emailAddress, subject, message); //This is to test because of the limit of how many emails you can send a day.
MailApp.sendEmail(emailAddress, subject, message);
}
Here is a link to a google sheet that you can test and edit the script on: https://docs.google.com/spreadsheets/d/1bBNDc33fBx2JPcRByt-8TA2vrLqa51H72TIM-SeARsc/edit?usp=sharing
The sheets is not using google forms, it is getting it's data from the results of an HTML webpage I built that is used as a form.

This is possible by using Installable Triggers.
Follow these steps once you created the form inside your sheets:
For New Editor
Open your Apps Script project.
At the left, click Triggers alarm.
At the bottom right, click Add Trigger.
Select and configure the type of trigger you want to create.
Click Save.
For Legacy Editor
From the script editor, choose Edit > Current project's triggers.
Click the link that says: No triggers set up. Click here to add one now.
Under Run, select the name of function you want to trigger.
Under Events, select either Time-driven or the Google App that the script is bound to (for example, From spreadsheet).
Select and configure the type of trigger you want to create (for example, an > > - Hour timer that runs Every hour or an On open trigger).
Optionally, click Notifications to configure how and when you are contacted by email if your triggered function fails.
Click Save.
For this scenario, Select event source: From spreadsheet and Select event type: On form submit.
Once the setup for Installable trigger is done, you can now add an event object e to the function parameter to access the form values submitted by the user.
Try this in your code:
function onSubmitForm(e){
var message = e.namedValues["Message"]; //get the message value using question name
var emailAddress = 'xxxxxsampleemailxxxxxx';
var subject = 'A Subject';
MailApp.sendEmail(emailAddress, subject, message); //send email
}
Example:
Output:
References:
Installable Triggers
Event Object

Related

Creating a backup of data entered into a sheet via Google App Scripts

I have a spreadsheet where users can enter data and then execute a function when clicking on a button. When the button is clicked it logs the time and entered data in a new row on another sheet in that spreadsheet.
To make sure that sheet is not accidentally edited by the users I want to create a non-shared backup of that data.
I import the range to another spreadsheet, but just importing the range means that if the original sheet is edited/erased that data will also be edited/erased, so I wrote the following script to log the changes as they come in.
function onEdit(event){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var incomingSheet = ss.getSheetByName('Incoming');
var lastRow = incomingSheet.getLastRow();
var incomingData = incomingSheet.getRange(lastRow,1,1,7);
var permanentSheet = ss.getSheetByName('PermanentLog')
var newdataRow = permanentSheet.getLastRow();
incomingData.copyTo(permanentSheet.getRange(newdataRow+1,1));
}
This works when Run from the Apps Script Editor, however, when I enter new data and click the button on the original spreadsheet, it logs the data to the log sheet there, and the range is imported to the 'Incoming' sheet of the new Spreadsheet, but the data is not copied over to the 'Permanent Log' sheet (unless I Run it manually from within the Apps Script Editor). It also works if I remove the ImportRange function from the first sheet and then just manually enter data in on the 'Incoming' sheet.
So does this mean new rows from an Imported Range do not trigger onEdit? What would be the solution? I don't want to run this on a timed trigger, I want to permanently capture each new row of data as it comes in.
Also, am I overlooking a more elegant and simple solution to this whole problem?
Thank you for your time.
This function will copy the data to a new Spreadsheet whenever you edit column 7 which I assume is the last column in your data. It only does it for the sheets that you specify in the names array. Note: you cannot run this from the script editor without getting an error unless you provide the event object which replaces the e. I used an installable onEdit trigger.
The function also appends a timestamp and a row number to the beginning of the archive data row
function onMyEdit(e) {
e.source.toast('entry');//just a toast showing that the function is working for debug purposes
const sh = e.range.getSheet();//active sheet name
const names = ['Sheet1', 'Sheet2'];//sheetname this function operates in
if (~names.indexOf(sh.getName()) && e.range.columnStart == 7) {
const ass = SpreadsheetApp.openById('ssid');//archive spreadsheet
const ash = ass.getSheetByName('PermanentLog');//archive sheet
let row = sh.getRange(e.range.rowStart, 1, 1, 7).getValues()[0];
let ts = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy/MM/dd HH:mm:ss");//timestamp
row.unshift(ts, e.range.rowStart);//add timestamp and row number to beginning
Logger.log(row);//logs the row for debug purposes
ash.appendRow(row);//appends row to bottom of data with ts and row
}
Logger.log(JSON.stringify(e));
}
Restrictions
Script executions and API requests do not cause triggers to run. For example, calling Range.setValue() to edit a cell does not cause the spreadsheet's onEdit trigger to run.
https://developers.google.com/apps-script/guides/triggers
So yeah, as far as I understand you it can't be done that way.

Google app script to construct URL from spreadsheet values and execute it

I am basically creating a CRM upon a spreadsheet, that is being populated by a google form.
Everytime there is a new entry, I would like to notify an external server by calling a postback URL.
So URL parameters must be taken from the spreadsheet cells of the new entry (the last row), and then executed.
Something like this:
http://www.myserver.com/script.php?value1=[lastrow.cell1]&value2=[lastrow.cell2]....
Then I need to call the URL.
How would you code it?
I Guess this script can be triggered on new line creation on the spreadsheet.
You can use Installable Triggers and UrlFetchApp.fetch() to automate the process of submitting a form to your spreadsheet and then sending an HTTP request to your server containing the form's values.
This is an example of how you would do it (Adapt it to your own form's questions):
// Installable trigger function
function formSubmit(e) {
// Take values from the form submitted
var channelResp = e.namedValues["Channel"];
var question1 = e.namedValues["Question 1"];
// Get values form Parameters sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var parametersSheet = ss.getSheetByName("Parameters");
var parametersArray = parametersSheet.getRange("A2:B4").getValues();
try {
// Verify if the value in channel was well submitted and search for it
var [channel, variable] = parametersArray.find(element => element[0] == channelResp);
// Build the params with your form's answers
var params = `value1=${question1}&value2=${variable}`;
// Add the params to your url
var url = `http://www.myserver.com/script.php?${params}`;
// Make the request
UrlFetchApp.fetch(url);
} catch(e) {
// Throw an exception if the value in channel was not well-submitted
// Or there was an HTTP error
Logger.log(e);
}
}
Notice that e represents the Event Object Form Submit. Now, for setting up the installable trigger, do the following:
1) Go to your Apps Script project
2) Click Edit->Current project's triggers
3) Click "+ Add Trigger"
4) Select :
Choose which function to run -> Function Name
Select event source-> From spreadsheet
Select event type -> On form submit
5) Click Save
Restrictions
Keep in mind the Installable Trigger restrictions
Class UrlFetchApp will not work if you're executing your server as a local server because it will not be able to reach it.

google sheets emails onEdit trigger

The case is I have a google sheet, that has a column that gets edited and 3 columns next to it that include a check, an email body and an email subject.
I made the following code so that when a certain cell is edited in the edit column, an email is sent for notification. I put the email in a column that is referred to in the code.
function onEdit(e){
//Detecting the edited cell and fetching the values from the other columns
var range = e.range;
var check = range.offset(0,2).getValue()
var serial = range.offset(0,-1).getValue()
var email = range.offset(0,-8).getValue()
var message = range.offset(0,3).getValue()
var subject = range.offset(0,4).getValue()
if (check == "SendEmail") { var email2 = email; }
//Checks to see if the code is running
SpreadsheetApp.getActiveSpreadsheet().getRange('R1').setValue(email2)
SpreadsheetApp.getActiveSpreadsheet().getRange('S1').setValue(check)
//Email part
var emailAddress = email2;
MailApp.sendEmail(emailAddress, subject, message)
}
When I try using the function without the on edit feat, the email is sent. when I, however, put the onEdit back on, it works perfectly still but no emails are sent.
Please confirm whether onEdit(e) is installed as a trigger. When you use MailApp.sendEmail(), it is required authorization. So onEdit(e) without Installable Triggers cannot run MailApp.sendEmail(). How to install onEdit(e) as a trigger is as follows.
On script editor
Edit -> Current project's triggers -> Click here to add one now.
For "Run", set "onEdit"
For "Events", set "From spreadsheet" and "On edit"
Click Save button
After this, please try again.
The detail information of Installable Triggers is here.
If this was not useful, I'm sorry.
Edit :
This is a sample for confirming running MailApp.sendEmail(). When you use this, please install onEdit() as a trigger.
function onEdit() {
MailApp.sendEmail("### your e-mail address ###", "Sample subject", "Sample body");
}
You are experiencing issues because your function name overlaps with the expected name of a simple trigger function - merely naming a function onOpen or onEdit is sufficient to make that function run under "simple trigger" environment.
If you rename your function to something more pertinent - such as sendNotification(e), then the only way it will run after cells are edited is if it is called via an installed trigger - one created manually by a user, or programmatically.

create and bind onEdit script to spreadsheet from inside form-bound script

I'm having a bit of trouble with this code, this is a script bound within a Google Form and triggered on Form Submit:
function onFormSubmit(e) {
//these lines are to get the email address that was entered into the form
var familyResponses = e.response.getItemResponses();
var familyEmailAddress = familyResponses[0].getResponse();
//this loads the template spreadsheet
templateSpreadsheetFile = DriveApp.getFileById("xxxxxxxx");
//this generates an 8-digit random number
var eightDigitCode = Math.floor(Math.random() * (99999999 - 10000000 + 1)) + 10000000;
//this makes a copy of the template spreadsheet, with the 8-digit code as the name of the copy, in the specified folder.
var FamilyWorksheetsFolder = DriveApp.getFolderById("yyyyyyyy");
var newSpreadsheetFile = templateSpreadsheetFile.makeCopy(eightDigitCode, FamilyWorksheetsFolder);
newSpreadsheetFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.EDIT); //this allows anyone with the link to view+edit the new file
//now we need to add a script to the new spreadsheet, that makes a copy every time it is edited
ScriptApp.newTrigger('familyWorksheetScript')
.forSpreadsheet(newSpreadsheetFile)
.onEdit()
.create();
//these lines build the subject and body of the email
var emailSubject = "link to your worksheet";
var emailBody = "Here is the link to your worksheet: \n" + newSpreadsheetFile.getUrl() + "\n\n\
Your spreadsheet is named with your unique 8-digit code " + eightDigitCode
//send the email
GmailApp.sendEmail(familyEmailAddress, emailSubject, emailBody);
}
function familyWorksheetScript() {
}
==============================================
The idea here is pretty simple:
family fills out form (one question: what's your email address?) and submits
onFormSubmit script runs (installed trigger), gets email address, generates random 8-digit code, makes a copy of a template spreadsheet, the copy is named with the 8-digit code. puts it in the right folder, and sets the permissions.
then the family is emailed with a link to the spreadsheet
All the above works. But I would now like to add the feature, from within this form-bound script, to create an on-edit triggered script bound to the new spreadsheet (copy of template, named with 8-digit code). And this is the part that I can't get to work. It's the ScriptApp.newTrigger code block, when I comment it out, the whole script runs fine I get the email at the end. But when I leave in the ScriptApp.newTrigger code uncommented, the script dies right at that spot. I can tell it's dying there because the new spreadsheet still gets created, but the email doesn't get sent. I don't know why it isn't working and I don't know how to troubleshoot it.
Any help would be much appreciated. Thanks!
You can't creat a trigger on a sheet that would act as the new sheet user, everything you create belongs to you ! the triggered function would run as "you", not the new user.
What I would suggest id to create an "onOpen" function with a UI that would ask for the new user to click on a "button" to run a function that would create the onEdit trigger, asking them for explicit authorization.
Edit
below is a sample code with an onEdit trigger working on the copy of the active spreadsheet, just for test purpose.
function createNewCopy(){
var ss = SpreadsheetApp.getActive();
var newSs = DriveApp.getFileById(ss.getId()).makeCopy('copyOf-'+ss.getName());
var nSs = SpreadsheetApp.openById(newSs.getId());
var trigger = ScriptApp.newTrigger('onEdit').forSpreadsheet(nSs.getId()).onEdit().create();
}
the onEdit in the original SS is as simple as that, just to check it works :
function onEdit(){
Browser.msgBox('hello');
}
note that no trigger will be viewable in the spreadsheet's script editor ressource tab, it will only appear in your own triggered function list in your Google account.

Ensure form submission trigger runs only one sheet

I have two sheets in a particular spreadsheet both of which have form submissions tied to them. The first sheet/form is supposed to send an email containing the form submission data (the original was created by Amit Agarwal, here's an historic link). The second form/sheet doesn't do anything special as it just collects data from the form. The script in question is set to a On Form Submit trigger.
The issue I am having is that the script sometimes runs from form/sheet2. I would like to specify which sheet/form the script needs to be triggered from to run on. The modified code that I have created was based on lots of looking around. Here is the snippet:
function Initialize() {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendConfirmationMail")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendConfirmationMail(e) {
try {
var ss, bcc, sendername, subject, columns;
var message, value, textbody, sender;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Help Request Tickets');
var rowNumber = s.getActiveRange().getRowIndex();
var row = e.range.getRow();
// This is your email address and you will be in the BCC
bcc = "email", "email";
// This will show up as the sender's name
sendername = "sendername";
// Optional but change the following variable
// to have a custom subject for Google Docs emails
subject = "subject"
// This is the body of the auto-reply
message = "message"
ss = SpreadsheetApp.getActiveSheet();
columns = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
These two lines I though were supposed to accomplish this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName('Sheet1');
I suppose the script thinks Sheet2 is active (which if I have the spreadsheet open could be the case). Surely there is way to work around/accomplish this, what I am missing?
A Sheets form submission trigger will be invoked for all forms submitted to the spreadsheet. Once upon a time, only one form could be associated with a spreadsheet, but now, with multiple form associations possible, you need to allow for that possibility. You can't specify which form a trigger function is for, but you can check the source of the event and respond appropriately.
One effective way to do this is to use a director function which will receive all form submission events, and direct them to unique trigger functions depending on which sheet received the response.
Here, we are associating "Form Responses 1" with SendConfirmationMail(), and assuming that "Form Responses 2" has its own form submission handler, handleForm2(). (If there is no handler for that form, then the specific case can be deleted, and submissions will end up in the default case.)
/**
* This director function should be used as the "top level" form submission trigger
* function for spreadsheets accepting responses from multiple forms. Events are
* directed to the appropriate trigger sub-functions according to the name of the
* sheet which received the current response.
*
* From: https://stackoverflow.com/a/37839189/1677912
*/
function formSubmitted(e) {
var sheetName = e.range.getSheet().getName();
switch (sheetName) {
case "Form Responses 1":
SendConfirmationMail(e);
break;
case "Form Responses 2":
handleForm2(e);
break;
default:
// do nothing
break;
}
}
If you use a Forms form submission trigger instead, you can avoid this altogether, since the destination spreadsheet would not be a direct consideration.
I suppose the script thinks Sheet2 is active (which if I have the spreadsheet open could be the case)
Not quite. The trigger function is invoked outside of the context of any spreadsheet UI, so what any user is doing in the spreadsheet has no effect on it. Rather, the "active" sheet is related to the submission event being handled. Regardless, it is a much better idea to reference the event object itself, rather than rely on "normal" operations. It does become trickier to test and debug, but not terribly so. For more about testing trigger functions, see How can I test a trigger function in GAS?