Google Apps Script send email through spreadsheet not working - javascript

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.

Related

Container-bound Apps Script does not execute properly from change to Google Sheet under certain circumstances

I have a container-bound script that reads data from a Google Sheet (its container), creates an event in a Google Calendar and then updates the Google Sheet with a confirmation to say that it has created a calendar event. The script is triggered to execute every time there is a change to the spreadsheet.
During testing, the script works perfectly as it should. However, when data is automatically entered into the Google Sheet from a Google Form (followed by some changes made through a Google Forms add-on), the script executes but does not update the Sheet with the confirmation of a new calendar event. This then results in the script creating duplicate calendar events because it does not see the confirmation in the Google Sheet.
Here is an excerpt of my Google Sheet data:
Google Sheet data for employee leave details
And below is my script:
function synctocalendar() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
var calendarId = spreadsheet.getRange("Calendar_sync!H2").getValue();
var eventCal = CalendarApp.getCalendarById(calendarId);
var submissions = spreadsheet.getRange("Calendar_sync!A2:F").getValues();
var last = submissions.length-1
for (x=last; x>0; x--) {
var shift = submissions[x];
var startTime = shift[2];
var endTime = shift[3];
var title = shift[0]+" | "+shift[1];
var ssr = x+2
if (shift[4]!=="" && shift[4]!=="Complete" && shift[4]!=="Declined" && shift[4]!=="In progress") {
break
} else if (shift[4]=="Complete" && shift[5]!=="Y" && shift[5]!=="N") {
eventCal.createEvent(title, startTime, endTime);
var endf = spreadsheet.getRange(ssr,6)
endf.setValue("Y")
break
}
}
}
When I run the script directly from the editor it works perfectly. It reads the data, creates a calendar event, then returns a "Y" in column F (provided it satisfies the IF criteria).
When I have a trigger set to run the script whenever a change is made to the sheet, I can again get the script to run as I expect by manually deleting the "Y" from column F in any one of the rows.
However, when a new submission is written to the Google Sheet from a Google Form (followed by some spreadsheet changes made from a Google Forms add-on for approvals), the script still runs and still creates a calendar event, but does not write a "Y" back into the sheet.
EDIT: Details on the Google Forms add-on
The add-on sends emails on form submission with the form data to be approved. Once the recipient of the email approves the data from the Google Form, the add-on makes changes to the spreadsheet to reflect that.
i.e. when a form is submitted, column E in the spreadsheet will say "In Progress" and once the approval process is complete, the add-on will change column E to say "Complete". At this point, the script recognises it is complete and creates a calendar event.

How to access data from an external spreadsheet with Google Apps Script

I have a spreadsheet with some data tables that I want to use in other spreadsheets script. How can I call this spreadsheet by its ID and access the data?\
From the documentation, I know that custom functions are not allowed to access other spreadsheets, a solution would be to put this function in a menu and run it, since it'd ask for the user's authorization. But this is not an option because I am using the data with the built-in method onEdit().\
Also, I tried accessing the spreadsheet via onOpen() since it's not a custom function but still no success. Any other solution?
My code:
function onOpen(){
ss = SpreadsheetApp.getActiveSpreadsheet();
src = SpreadsheetApp.openById("spreadsheetID");
Error message:
Exception: You do not have permission to call SpreadsheetApp.openById. Required permissions: https://www.googleapis.com/auth/spreadsheets
And appscript.json
{
"timeZone": "Europe/Paris",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets"
]
}
Lastly, I also tried creating an installable trigger with this code:
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}
But the trigger does not seem to be called upon opening the sheet.
As you already realized, custom functions and simple trigger cannot perform requests that require authorization
SpreadsheetApp.openById("spreadsheetID"); is one of those requests
You are on the right track with the installable triggers - they can trigger the execution of requests that require authorization
I think your problem is the correct implementation of installable triggers
function createSpreadsheetOpenTrigger creates an installable onOpen trigger that calls the function myFunction when the sheet is open
This means that you need to create the function myFunction first
Also, you need to run function createSpreadsheetOpenTrigger() once manually - to install the trigger
Sample complete code:
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}
function myFunction(){
ss = SpreadsheetApp.getActiveSpreadsheet();
src = SpreadsheetApp.openById("spreadsheetID");
Logger.log(src.getActiveSheet().getName());
}
Note:
Instead of implementing the function createSpreadsheetOpenTrigger(), you can install the trigger manually by going on Edit->My project's triggers and bind a trigger of the desired type to the desired function, see also Managing triggers manually
First, in the line src = SpreadsheetApp.openById("spreadsheetID"); did you replace the id with the actual spreadsheet ID?
That's the number at the end of the url. I assume you did, but it doesn't show that you did in your example.
Second, just for testing, try to do this action in a regular function (not a trigger) to get things to work. Have you done that? Then you can focus on the spreadsheet access without worrying if the trigger is causing a problem.

Open two urls with one click

I'm new to Google Sheets and Apps Script. I have a sheet with two URLS. In cell F1 is http://testurl1.com and in cell G1 is http://testurl2.com.
I would like to have a button or link or something in cell D1 that when I click it will open both of these links. I can do this manually with Alt-Enter but haven't been able to translate that to code.
I have been able to open both these urls from a menu item, but when I try calling the code from a cell it says
Exception: Cannot call SpreadsheetApp.getUi() from this context.
But the code works from a menu item. Weird. The code I'm currently trying to use is below but I am open to any suggestions!
function callOthers() {
myFunction()
Utilities.sleep(1500);
myFunction2()
}
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var selection = sheet.getRange("F1").getValue();
var html = "<script>window.open('" + selection + "');google.script.host.close();</script>";
var userInterface = HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModalDialog(userInterface, 'Open Tab');
}
function myFunction2() {
var sheet = SpreadsheetApp.getActiveSheet();
var selection2 = sheet.getRange("G1").getValue();
var html2 = "<script>window.open('" + selection2 + "');google.script.host.close();</script>";
var userInterface2 = HtmlService.createHtmlOutput(html2);
SpreadsheetApp.getUi().showModalDialog(userInterface2, 'Open Tab');
}
Problem
Custom function is blocked from being run when used in a cell
Explanation
There are three main ways of making a bound script function available in the Spreadsheet UI:
As a custom function that can be used like a formula
As a menu item that will run the function on click
As a "button" created via image or drawing that will run the function on click
All three have different execution context and limitations on what they can and cannot access, the most restrictive being the first. Custom functions execution context is bound to the cell it is called in, so you cannot do anything that affects the UI as a whole, which getUi() allows to do.
Additionally, since showModalDialog is a method that requires authorization on behalf of the user, even if the getUi() method was available, you could not show the dialog due to the fact that custom functions never ask users to authorize access to personal data.
Solution
If you want to interact with UI, you should either create a menu or a button as mentioned before.
Please note that users will have to give your script their permission for the following scope:
https://www.googleapis.com/auth/script.container.ui
References
Custom functions guide
showModalDialog method reference
getUi() method reference

Google Sheets "You do not have permission to call appendRow"

function myFunction() {
var url = 'https://api.github.com/users/chaimf90/repos'
var response = UrlFetchApp.fetch(url);
var json = response.getContentText();
var data = JSON.parse(json)
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(['Repo Name', data[0].name]);
}
When I execute this function from the script editor it runs as expected, but when I try to call this function in the sheet itself by calling =myFunction(), I get an error saying that I do not have permission to call appendRow.
Why can I call this function from the script editor, but not from the sheet itself?
I had the same problem. The solution seems to be to create a custom menu that runs an Apps Script function instead of writing a custom function and invoking it from a cell in the spreadsheet.
A function that is triggered from a menu will ask the user for authorization if necessary and can consequently use all Apps Script services. I learned this here.
Example:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('My Custom Menu')
.addItem('Run My Function', 'myFunction')
.addToUi();
}
function myFunction() {
SpreadsheetApp.getUi()
.alert('Running My Function');
}
After writing and saving this code:
Close and re-open your google sheets document.
After a few seconds, a new menu "My Custom Menu" will appear at the
top, next to File, Edit, View, ..., Help.
Click on "My Custom Menu" and then click on "Run My Function" in order to invoke
the function myFunction.

Class Session not working in some Google spreadsheets

I have a problem with the class Session in Google Apps Scripts
I wrote a script that records the date and user who edits a sheet in a spreadsheet. It works good with my test page, but when I put it in another spreadsheet (a copy named like "test") it does not work!! I have 8 Spreadsheets with the same number of sheets, all named similar, and the script only works in one of the spreadsheets (and test). This is the script:
function onEdit(event)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Last Session record
var actSht = event.source.getSheetByName("somepage")
var actRng = event.source.getActiveRange();
var lastCell0 = actSht.getRange(2,16);
var lastCell1 = actSht.getRange(2,17);
var date = Utilities.formatDate(new Date(), "GMT-6", "dd/MM/yyyy HH:mm");
var r= actSht.getActiveCell().getRow();
lastCell0.setValue("Date_Mod");
lastCell1.setValue("user");
if(r=='3'){
actSht.getRange(r,17).setValue(Session.getEffectiveUser());
actSht.getRange(r,16).setValue(date);
}
if(r=='4'){
actSht.getRange(r,17).setValue(Session.getEffectiveUser());
actSht.getRange(r,16).setValue(date);
}
}
This is the same code for two spreadsheets, in one it works OK, in the other it does not work.
How do I fix this?
It is most likely a security issue and Session calls are supposed to be avoided in simple triggers due to simple triggers limitations (look at https://developers.google.com/apps-script/guides/triggers/#restrictions, especially in regard to the very vague 5th bullet point - "They may or may not be able to determine the identity of current the user" (mistake on page) and where the link sends you back to the session page). In your case I wonder whether it worked only for the users who shared your domain name.
If you look at https://developers.google.com/apps-script/reference/base/session#getEffectiveUser() there is no reference for simple triggers (which for me is suspicious already) but the statement in regard to installable triggers tells me that it should probably not be used in simple ones.
I just went through a similar exercise myself where both getActiveUser and getEffectiveUser were returning blank when the domain of the script owner did not match the domain of the person who launched the script (in other words this happens when someone shares the script outside the domain and run it under a different username such as their personal Google account). I therefore created an installable trigger instead that calls getEffectiveUser and it works now (note that if you define your installable trigger programmatically rather than through Resources->Current project's triggers make sure the trigger function is in the invoking script file).
Hope this helps.

Categories

Resources