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.
Related
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
I have a problem with some Google Script stuff. Basically, my goal is to have the script check to see if a client's case was resolved and then send an email to them that the issue has been resolved. I've gotten the logic done on when to send an email, but every time I try and implement it into the spreadsheet, I get the error:
Error
You do not have permission to call MailApp.sendEmail. Required permissions: https://www.googleapis.com/auth/script.send_mail (line 8).
I've got a simple function to test the functionality of it, and when run in the script editor it works fine, but not on the spreadsheet. Here is my sample function:
function myFunction(row) {
var sheet = SpreadsheetApp.getActiveSheet();
var rng = sheet.getRange(row, 1, 1, 2);
var ara = rng.getValues();
var email = ara[0][0];
MailApp.sendEmail(email, "TEST", "This is a test of sendEmail().");
return "Email sent.";}
According to the Apps Script Custom Functions documentation:
If your custom function throws the error message You do not have permission to call X service., the service requires user authorization and thus cannot be used in a custom function.
To use a service other than those listed above, create a custom menu that runs an Apps Script function instead of writing a custom function. A function that is triggered from a menu will ask the user for authorization if necessary and can consequently use all Apps Script services.
Method 1
Basically, you can replicate the wanted behavior of the two functions above with this:
function SendEmail() {
var message = "This is your response";
var subject = "You have feed back in the parking lot";
var ss = SpreadsheetApp.getActiveSheet();
var textrange = ss.getRange("F2");
var emailAddress = ss.getRange("B2").getValue();
if (textrange.isBlank() == false)
MailApp.sendEmail(emailAddress, subject, message);
}
And in order to trigger the execution of this function, you can make use of Apps Script triggers and choose one which is the most convenient for your use-case.
Method 2
You can also create a custom menu and with the option of triggering the above function. You only need to add this:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu("My Menu")
.addItem("Send Email", "SendEmail")
.addToUi();
}
And this is how it will look like on the Spreadsheet:
Reference
Apps Script Custom Functions;
Apps Script Range Class - isBlank();
Apps Script Custom Menus;
Apps Script Triggers.
I encountered the same problem today "You do not have permission to call MailApp.sendEmail".
I solved this by doing the next steps:
open "Tools" -> "Script editor"
in "Script editor" click on "View" -> "Show manifest file"
open the "appscript.json" file that appeared in the left section of your screen and add "https://www.googleapis.com/auth/script.send_mail" to the oauthScopes, like this:
{
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/script.send_mail"],
}
PS: I assigned the script to an image, which basically acts like a button.
I created an audit template in Google sheets that uses several scripts. The script is designed to populate a cell with the active user's email and a time date stamp when they click on a check box. The script works, except for one person, the script does not recognize his active user email. Can someone tell me why the script works for some but not others and how I might fix it?
I've tested the script in a blank test spreadsheet and it works for this person. We had this same issue in another Google Sheet, but we remedied it my creating a new spreadsheet and copying all of the tabs and script. However, attempts to do the same with this spreadsheet have failed.
Here's my script:
function onEdit(e) { //this is the event Google Sheets fires when cells are changed
var email = Session.getActiveUser().getEmail(); // Get the email address of the person running the script.
var date = new Date();
var spreadsheet = SpreadsheetApp.getActive();
if (e.source.getActiveSheet().getName() == "InterimTestsOfControls") { //only do this if the changed cell is on the specific worksheet you care about
switch (e.range.getA1Notation()) { //This gets the cell that was edited
case "E5": //and this switch statement allows you to only respond to the cells you care about
if (e.value == "TRUE") {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Prepared by " + email + " " + date);
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
else {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Click box to sign-off as preparer");
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue("");
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
}
}
}
From the official Session class documentation:
getActiveUser()
Gets information about the current user. If security policies do not allow access to the user's identity, User.getEmail() returns a blank string.
This situation may happen under many, different circumstances:
The user's email address is not available in any context that allows a script to run without that user's authorisation, like a simple onOpen(e) or onEdit(e) trigger, a custom function in Google Sheets, or a web app deployed to "execute as me" (that is, authorized by the developer instead of the user).
These restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
In your situation I see that you are using a simple onEdit(e) trigger - so it looks like this may be the issue. You may want to check out Installable triggers instead.
function onEdit(e) {
var sh=e.range.getSheet();
var email = Session.getActiveUser().getEmail(); //This is a permissions issue. You may have to use an installable trigger and even then it's not guaranteed
var date = new Date();//you might want to use Utilities.formatDate()
//var spreadsheet = SpreadsheetApp.getActive();this is than same as e.source
if (sh.getName()=="InterimTestsOfControls") {
//you can selection your cells with information that's already in the event block rather than having to run another function
if(e.range.columnStart==6 && e.range.rowStart==5) {
if (e.value=="TRUE") {
sh.getRange("F5").setValue("Prepared by " + email + " " + date);
e.source.getSheetByName('Audit Planning').getRange('C7').setValue(email);
} else {
e.source.getRange("F5").setValue("Click box to sign-off as preparer");
e.source.getSheetByName('Audit Planning').getRange('C7').setValue("");
}
}
}
}
Getting and setting the active range is a remnant of the Macro Recorder following your every step and in general is not required in your scripts. And in fact it will slow down your scripts so please try to avoid using that approach in general for most scripting.
How can I add an auto-populate of the user who last edited the row to the following code that auto-populates the date the column was last edited. Here is the current script:
function onEdit(e) {
var s = e.source.getActiveSheet(),
cols = [2],
colStamp = 1,
ind = cols.indexOf(e.range.columnStart)
if (s.getName() !== 'Log' || ind == -1) return;
e.range.offset(0, parseInt(colStamp - cols[ind]))
.setValue(e.value ? new Date() : null);
}
So, ultimately... I would want to add the user name to column G on edit of each row. Any ideas?
You can retrieve user name using Class Session. The detail information is here. https://developers.google.com/apps-script/reference/base/session
Please add following script to last row of your script. The user name is imported to column G. If you want user e-mail, please change from getUsername() to getEmail().
Script :
s.getRange(e.range.getRow(), 7).setValue(Session.getEffectiveUser().getUsername());
About onEdit()
If the onEdit() is not installed as a trigger, other shared users cannot use Session.getEffectiveUser().getUsername() because of authMode=LIMITED. So the user name cannot be retrieved. By installing onEdit() as a trigger, authMode becomes FULL. So you can retrieve user data using Session.getEffectiveUser().getUsername().
The detail information for installing trigger is https://developers.google.com/apps-script/guides/triggers/installable#managing_triggers_manually
If I misunderstand your question, I'm sorry.
Added 1 :
In order to retrieve user information, the user has to install a trigger for onEdit(). I had forgotten about this. I'm sorry.
For example, owner of spreadsheet and user with a permission for editing are OWNER and USER, respectively. When OWNER installs a trigger for onEdit(), the user of spreadsheet becomes OWNER. At this time, when USER edits the spreadsheet, the user name becomes OWNER.
When I have worked a test, I have installed as USER. So I had thought that it works. But it was wrong. So I thought for the solution as follows.
Install a trigger for onEdit() using onOpen().
This didn't work, as you know.
Display a dialog box and a button using onOpen(). Install a trigger for onEdit() by the button.
This didn't work, because of existing several triggers for each user.
Display a dialog box and a button using onOpen(). while temporarily install a trigger for onEdit(), retrieve the user name and put it to cache by the button. The trigger is removed after retrieved user name soon.
This works fine.
I propose the 3rd method. In this script, it is not necessary to install triggers manually. If you want to change the hold time of cache, please modify cache.put().
Script :
function getUser() {
var triggerId = ScriptApp.newTrigger('onEdit')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create().getUniqueId();
var user = Session.getEffectiveUser().getUsername();
var triggers = ScriptApp.getProjectTriggers();
for (i in triggers) {
if (triggers[i].getUniqueId() == triggerId) {
ScriptApp.deleteTrigger(triggers[i]);
}
}
var cache = CacheService.getUserCache();
cache.put("username", user, 3600); // For example, hold user name for 1 h
}
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().show(
HtmlService
.createHtmlOutput('<input type="button" value="OK" onclick="google.script.run.withSuccessHandler(function(){google.script.host.close()}).getUser()">')
.setTitle('Push OK button.')
.setWidth(400)
.setHeight(100)
);
}
function onEdit(e) {
var cache = CacheService.getUserCache();
var user = cache.get("username"); // Please use this as user name.
}
Flow of script :
When spreadsheet is opened, a dialog is opened on the spreadsheet by onOpen().
When user pushes "ok", the user name is retrieved and put to the cache by getUser().
When user edits the spreadsheet, the user name is retrieved from the cache by onEdit().
Please copy and paste this script. You can use user in onEdit().
When I have been confirming this again, I noticed that in order to use this script, each user has to be authorized. The authorization is https://developers.google.com/apps-script/guides/services/authorization
Added 2 :
I report a solution for retrieving shared user information at spreadsheet. It was found as follows.
User information retrieving by Class Session is the owner and users which installed triggers by themselves.
When each user installs a trigger, user information retrieving by Class Session losts the accuracy. So user information has to be retrieved using a temporally installed trigger.
Using onOpen(), it cannot directly install triggers and authorize.
Using menu bar, it can install triggers and authorize Google Services using API.
Here, I thought 2 problems.
The confirmation whether the authorization was done.
At onOpen(), although many methods using Google API can be executed without the authorization, there are also some methods which cannot be executed without the authorization. Furthermore, there are some methods which cannot execute even if the authorization was done. It's trigger. On the other hand, DriveApp requires the authorization for only the first time, but it can use without the authorization after 2nd times.
I thought that users can find easily by displaying information in a dialog box when spreadsheet is launched. So I adopted displaying information using the dialog box. But, there is a big limitation for the dialog box.
Using a click of button on a dialog box, it can install triggers. However it cannot authorize Google Services using API.
Using above information, I thought a flow to retrieve user information.
When user opens the spreadsheet for the first time, it displays 'Please authorize at "Authorization" of menu bar.' using a dialog box, and creates a menu bar "Authorization".
The user clicks "OK" button on the dialog box and run "Authorization" at the menu bar. By running "Authorization", the user information is retrieved by a temporally installed trigger.
When the user opens the spreadsheet after the 2nd time, the authorization is checked by DriveApp. A dialog box with 'Push OK button.' is displayed. By clicking "OK", the user information is retrieved by a temporally installed trigger.
By this flow, the user information which is using the shared spreadsheet can be retrieved. Although I think that there may be also other solutions, I proposal this as one of solutions.
Script :
function getUser(){
var triggerId = ScriptApp.newTrigger('getUser')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create()
.getUniqueId();
var userInf = Session.getEffectiveUser();
var userName = userInf.getUsername();
var userMail = userInf.getEmail();
var triggers = ScriptApp.getProjectTriggers();
[ScriptApp.deleteTrigger(i) for each (i in triggers) if (i.getUniqueId() == triggerId)];
CacheService.getUserCache().putAll({
"username": userName,
"usermail": userMail
}, 3600);
}
function dialogForGetUser(){
SpreadsheetApp.getActiveSpreadsheet().show(
HtmlService
.createHtmlOutput('<input type="button"\
value="OK"\
onclick="google.script.run.withSuccessHandler(function(){google.script.host.close()})\
.getUser()">'
)
.setTitle('Push OK button.')
.setWidth(400)
.setHeight(100)
);
}
function dialogForAuth(){
SpreadsheetApp.getActiveSpreadsheet().show(
HtmlService
.createHtmlOutput('<input type="button"\
value="OK"\
onclick="google.script.host.close()">'
)
.setTitle('Please authorize at "Authorization" of menu bar.')
.setWidth(400)
.setHeight(100)
);
}
function getAuth() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.removeMenu("Authorization");
getUser();
ss.toast("Done.", "Authorization", 3);
}
function onOpen(){
try {
var temp = DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId());
dialogForGetUser();
} catch(e) {
dialogForAuth();
SpreadsheetApp.getActiveSpreadsheet().addMenu(
"Authorization",
[{
functionName:"getAuth",
name:"Run this only when the first time"
}]
);
}
}
function onEdit(e){
var cache = CacheService.getUserCache();
var user = cache.getAll(["username", "usermail"]);
// user.username is user name.
}
When the spreadsheet is opened, at first, onOpen() is executed. It is checked whether the user has already authorized.
If the user has never authorized yet, dialogForAuth() is executed. If the user has already authorized. dialogForGetUser() is executed.
In this case, you can retrieve user name by user.username at onEdit().
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.