This issue has been discussed in detail here and here. But none of the scripts I´ve seen and analyzed with a great deal of attention seems to solve my problem. That´s why I am calling on all brilliant minds out there to help me.
Here are the details of my problem:
ISSUE: My current script (see it below) sends out an html email to each recipient listed in my sheet and then set the status for each recipient receiving that html email to “EMAIL SENT” in column K. Now I want to send 3 additional html emails to each of these recipients exactly on the dates that I´ve already specified in column L, M and N for each recipient.
Example: recipient A received the first html email on 11.10.2021 (format used: day/month/year) and he is scheduled to receive the second html email on 25.10.2021, the third html email on 27.10.2021 and the forth/last html email on 29.10.2021. These dates for recipient A are specified in column L (25.10.2021), column M (27.10.2021) and column N (29.10.2021).
QUESTION:How can I ensure that each recipient receives the additional emails on the dates specified for him in column L, M and N?
I´d really appreciate any help or hint to solve this problem.
Here is my current script:
function sendEmail() {
// variables for the html template (html file) and the list of recipients (google sheet)
const anrede = 2;
const nachname = 3;
const emailAdresse = 5;
const terminTag = 6;
const terminUhrzeit = 8;
const terminURL = 9;
let emailTemp = HtmlService.createTemplateFromFile('HTML_TEMPLATE');
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("LIST_OF_RECIPIENTS");
// send email based on my html template and recipients list
var adminV = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("admin"); // open the sheet containing the subject of the email ("subject in German is "betreff")
var betreff = adminV.getRange("A20").getValue(); // get the actual content of the sheet containing the subject ("subject in German is "betreff")
var aliases = GmailApp.getAliases(); // get the alises of my gmail-account
const sr = 3;//start row of data/recipients list from the google sheet
const data = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, 11).getValues();
data.forEach((row, i) => {
if (row[10] == "EMAIL NOT SENT YET") {
emailTemp.anrede1 = (row[anrede]);
emailTemp.nachname1 = (row[nachname]);
emailTemp.emailAdresse1 = (row[emailAdresse]);
emailTemp.terminTag1 = (row[terminTag]);
emailTemp.terminUhrzeit1 = (row[terminUhrzeit]);
emailTemp.terminURL1 = (row[terminURL]);
var htmlMessage = emailTemp.evaluate().getContent();
GmailApp.sendEmail(row[emailAdresse],
betreff, "This is an html email. Please change your seting to be able to read it.",
{ from: aliases[0], htmlBody: htmlMessage, replyTo: aliases[0] });
sh.getRange(i + sr, 11).setValue("EMAIL SENT");//stops emails this particular email from being sent again, if the recipient has already received it.
}
});
}
Since you want to send the emails for dates which are in the future, the best solution would be to use a time-based trigger. In this way, your function sendEmail will end up running every day and by checking the current date with the dates in L, M, N columns the email will be sent accordingly.
function sendEmail() {
let today = new Date();
let todayDate = Utilities.formatDate(today, "GMT", "dd.mm.yyyy").toString();
// the rest of the code
// add the rest of it in the for loop
data.forEach((row, i) => {
let lCell = sh.getRange(i + 1, 12).getValue();
let mCell = sh.getRange(i + 1, 13).getValue();
let nCell = sh.getRange(i + 1, 14).getValue();
if (lCell == todayDate || mCell == todayDate || nCell == todayDate) {
// send email
}
}
}
function createTimeDrivenTrigger() {
ScriptApp.newTrigger('sendEmail')
.timeBased()
.everyDays(1)
.create();
}
As for the createTimeDrivenTrigger function, this is the function that will create the trigger for the sendEmail function and by using everyDays(1) you are ensuring that this will end up running every day.
Reference
Apps Script Manage Triggers Programmatically.
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:
Updated the code based on suggestions below, The email does not contain the summary, any help fix this would be appreciated! The test file is attached below,
function sendEmail(){
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("2021-12 {3600950}").activate();
var ss =
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//data on sheet, filter for any row where the status is still "assigned"
var data = ss.getDataRange().getValues()
var assigned = data.reduce(function(acc, curr) {
if(curr[5] === "assigned") {
acc.push(curr)
}
return acc
},[])
// unique list of emails
var Assignee = [0]
var Summary = [2]
var compareAssignee = []
for (i=0; i<assigned.length; i++){
compareAssignee.push(assigned[i][0])
}
//loop unique emails, all the tasks for each of those users, and send a simple email with the tasks
for (var i=0; i<Assignee.length; i++){
var Summary = assigned.reduce(function(acc, curr) {
if(curr[0] === Assignee[i])
{
acc.push(String.fromCharCode() + "pending task: " + curr[2] +
Summary[2])
//this puts a line break before each task, so when the email is
sent each one is on its own line.
}
return acc
},[])
console.log(Summary)
MailApp.sendEmail
MailApp.sendEmail(compareAssignee[0],"pending RPP task(s)",Summary[2])
}
}
function scheduleEmails(){
// Schedules for the first of every month
ScriptApp.newTrigger("sendEmail")
.timeBased()
.onMonthDay(28)
.atHour(1)
.create();
}
You want to send an email at the end of every month to users who have tasks that are still in "assigned" status.
The sendEmail script below finds all the tasks for each user and sends an email to them listing each of their tasks that are still "assigned". EDIT: You indicated in a comment above that emails are in Col 1, tasks are in Col 3 and status is in Col6. I updated the code to reflect that below.
Check out this sample email to see the results.
The second function creates a trigger that runs sendEmail every month. You indicated you wanted to send the email on the last day of the month, but it seems Google has a hard time with that. Some other folks came up with workarounds. I like this one: send the reminder on the 1st of the month, but at 1 in the morning! You can see more of their work here
function sendEmail(){
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("status").activate();
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//grab all the data in the sheet, and then filter for any row where the status is still "assigned"
var data = ss.getDataRange().getValues()
var assigned = data.reduce(function(acc, curr) {
if(curr[5] === "assigned") {
acc.push(curr)
}
return acc
},[])
// From all the tasks still in "assigned" status, get a unique list of emails.
var uniqueEmails = []
var compareEmails = []
for (i=0; i<assigned.length; i++){
compareEmails.push(assigned[i][0])
}
uniqueEmails = [...new Set(compareEmails)]
//loop through the unique emails, grab all the tasks for each of those users, and send a simple email with the tasks listed.
for (var i=0; i<uniqueEmails.length; i++){
var tasksPerUser = assigned.reduce(function(acc, curr) {
if(curr[0] === uniqueEmails[i]) {
acc.push(String.fromCharCode(10) + "pending task: " + curr[2]) //this puts a line break before each task, so when the email is sent each one is on its own line.
}
return acc
},[])
console.log(tasksPerUser)
MailApp.sendEmail(uniqueEmails[i],"pending tasks",tasksPerUser)
}
}
function scheduleEmails(){
// Schedules for the first of every month
ScriptApp.newTrigger("sendEmail")
.timeBased()
.onMonthDay(1)
.atHour(1)
.create();
}
I have bug where when I run my send email function. its sending multiple emails instead of just one email notification here is my code what am I doing wrong??!?! I got 31 of the same emails. I believe the issue the for loop is sending an email each time the if statement is true instead of just one time if its true help.
here is my code:
function sendEmail(){
var ss = SpreadsheetApp.getActiveSpreadsheet(); //get active spreadsheet only! to get the url for the filter view
var SpreadsheetID = ss.getSheetId(); // get the sheet Id
var spreadsheetURL = ss.getUrl(); // get the current active sheet url
var SpreadsheetID = spreadsheetURL.split("/")[5]; // using the last / for getting the last parts of the email
var filterViewName = 'PO_Log Precentage'; // Name of the filter view you want to get the url from & MAKE SURE Title matches view name account for "spaces" too
var filterViewID = filterId(SpreadsheetID, filterViewName); // Getting filter view id
var url = createURL(spreadsheetURL, filterViewID); // creating the url to send the filter view id
Logger.log(url);// Testing to see the correct url is created
var po_numID = ss.getSheetByName("Purchase Orders List").getRange("A2").getDisplayValue().substr(0,3);// Gets the Purchase Order List Sheet and the PO# the first 3 Characters of the PO in A2
Logger.log(po_numID);
var email_va = ss.getSheetByName("Purchase Orders List");
//gonna build statuses to look for into array
var statusesToEmail = ['On-going', '']
//"Status" is in Column T (Col 2)
//"Precent" is in Column Q (Col 3)
var data = email_va.getDataRange().getValues()
// //var headerRowNumber = 1; // When checking for emails in the sheet you want to exclude the header/title row
var emailDataSheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/17G0QohHxjuAcZzwRtQ6AUW3aMTEvLnmTPs_USGcwvDA/edit#gid=1242890521").getSheetByName("TestA"); // Get The URL from another spreadsheet based on URL
Logger.log(emailDataSheet.getSheetName());
var emailData = emailDataSheet.getRange("A2:A").getDisplayValues().flat().map(po => po.substr(0,3));
Logger.log(emailData)///Working to get the first 3 charcters in column A
var subject = po_numID + " Po Log Daily Notification "; // Unique PoTitle of the email
var options = {} // Using the html body for the email
options.htmlBody = "Hi All, " + "The following" + '<a href=\"' +url+ '" > Purchase Orders </a>' + "are over 90% spent" + "";
for(var i = 0; i < data.length; i++){
let row = data[i];
if( statusesToEmail.includes(row[1]) & (row[2] >= .80)){
emailData.every((po, index) => {
if (po == po_numID){
const email = emailDataSheet.getRange(index + 2,7).getValue();//Getting the last colmun on the same row when the Po# are the same.
console.log(email);
MailApp.sendEmail(email, subject, '', options); // Sending the email which includes the url in options and sending it to the email address after making sure the first 3 Charcters Of the PO_log are the same as
return false;
} else {
return true;
}
});
}
}
}
here is the spreadsheet
https://docs.google.com/spreadsheets/d/1QW5PIGzy_NSh4MT3j_7PggxXq4XcW4dCKr4wKqIAp0E/edit#gid=611584429
you have to use the break function if u wish to stop the loop once the loop has been fulfiled, because if im not wrong , the email is sent if the IF condition is met , thus in the block that has mailapp.sendemail , you have to add in a break otherwise the loop will keep on happening. this is the basic of javascript and you should read up more about the FOR loop here
break as in just type "break" at the end of the code so the script will not continue to loop once the condition has been met.
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
This is my first post so apologies in advance if I am posting to the wrong place or asking a question that has been answered elsewhere - go easy on me!
In a nutshell, I have a Google Form and a connected Google Sheet. I need to automate it so that, when a new form is submitted, an email is sent to a specific colleague (the student's supervisor). I have had a good go myself but am now totally stuck!
I have created the form, linked it to a sheet, written the code, added the trigger and tested by submitting a form. Nothing happened! No error messages, just... nothing!
Any advice hugely appreciated. I am very new to this and still taking baby steps.
The code I have cobbled together (which is probably full of errors!) is as follows:
function wa132657(e) {
//setup the spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//get the range from OnFormSubmit
var range = e.range;
Logger.log("DEBUG: the range is "+range.getA1Notation());//DEBUG
// get the data for the range
var response = row.getValues();
// get the supervisor name from the form submission
var supervisor = response[0][1];
Logger.log("DEBUG: Supervisor = "+supervisor);// DEBUG
// get the emails list
var emailSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SupEmails");
// get ALL the data from this sheet
var emaildata = emailSheet.getDataRange().getValues();
// check how many rows of data
var emailLastRow = emailSheet.getLastRow();
// start the loop through the emails data
for (var i=1; i<emailLastRow; i++){
// if the supervisor is equal to SupervisorEmail
if (supervisor == emaildata[i][0]){
// there is a match
//Next, get the email address
var emailSupervisor = emaildata[i][1];
Logger.log("DEBUG: supervisor = "+emaildata[i][0]+", email address: "+emailSupervisor);// DEBUG
// Finally, send the Email.
var theirName = e.values[2];
var theirProgramme = e.values[3];
var theAbsenceReason = e.values[8];
var theAbsenceStart = e.values[5];
var theAbsenceEnd = e.values[6];
var subject = "Student Absence Report";
var message = "New Absence Report: " + theirName + " \n Programme: " + theirProgramme; + " \n\n
Reason for Absence: \n" + theAbsenceReason + " \n Start of Absence" + theAbsenceStart + "\n End of Absence:" + theAbsenceEnd;
MailApp.sendEmail(emailSupervisor, subject, message);
}
}
}