0

I've built a spreadsheet to automate sending formulaic emails. It sends to the client's email (defined in spreadsheet) and CCs an email address that needs to receive all projects. The number of projects/emails will change each time I use the sheet.

I've run into trouble because the blank rows in the spreadsheet currently trigger emails to the CC email address. I don't think I can use something like getLastRow to fix this because I have formulas in every cell in some columns. Can you help me figure out how to only send an email if there is data in Column A?

Here is my current script:

function uploadsender() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A2:AA");
var UserData = range.getValues();
for (i in UserData) {
var row = UserData[i];
var emailaddress = row[2];
var bccaddress = row[3];
var subject = row[4];
var comments = row[5];
var filenames = row[7];
  MailApp.sendEmail(emailaddress, subject, filenames + "\n\n" + comments, {
                cc: "example@gmail.com",
                bcc: bccaddress,
                name: "John Doe"}
             );
} 
}

It seems like something like this answer might be my solution:

var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;

But I assume this will conflict with lines 3 and 4 of my current script?

3 Answers3

1

Your larger problem appears to be the existence of formulas in every Row in the spreadsheet in column H. They return as having SOME value.

I would remove all the formulas in row H and in cell H1 place:

=ARRAYFORMULA(IF(ROW(A1:A)=ROW(A1),"Email Body", IF(ISBLANK(A1:A), ,J1:J&char(10)&K1:K&char(10)&L1:L) ))

Which breaks down as follows: The ARRAYFORMULA will apply this to ever row (in this case because we use arrays of rows) from A1:A

There are then 2 nested IF statements. The first one test to see if we are in Row 1. If we are, it put Email Body in the cell. If we are not, then the second IF statement comes into play. If Cell A in this row is Blank, then do nothing (NOTE that there is NOT a "" in the true area here as that actually applies a value to the cell, unlike Excel) If cell A is not blank, create your text string.

Do this and Cooper's original answer to replace sheet.getRange("A2:AA") with sheet.getDataRange() is completely valid as you should only get data passed with getDataRange() for rows with data.

Another option would be to wrap everything in an IF(){} to check for a value in column A:

var row = UserData[i];
if(if(UserData[i][0] && UserData[i][0] !== 'ID'){){
   var row = UserData[i];
   //...  Down to sending the email
}

EDIT: Apparently getDataRange() even gets formulas via the ARRAYFURMULA if they don't return a value. So it still gets too many rows. So a quick search lead me to the answer of getting the last row via a function. That changed the code to this:

function uploadsender() {
   var sheet = SpreadsheetApp.getActiveSheet();
   var lr = getLastPopulatedRow(sheet);
   var range = sheet.getRange(2,1,lr,8);
   var UserData = range.getValues();
   for (i in UserData) {
     if(UserData[i][0] && UserData[i][0] !== 'ID'){
       //Continue to the Send Email
     }
   }
}

function getLastPopulatedRow(sheet) {
  var data = sheet.getDataRange().getValues();
  for (var i = data.length-1; i > 0; i--) {
    for (var j = 0; j < data[0].length; j++) {
      if (data[i][j]) return i+1;
    }
  }
  return 0; // or 1 depending on your needs
}

If you need more column than H change the 8 to the appropriate value in the getRange() call

Karl_S
  • 3,364
  • 2
  • 19
  • 33
  • I love the idea of using an Array Formula to populate the data in columns. I hadn't thought of that, and will make use of that for other projs. The problem is that when I finish building out this worksheet, I will still have some columns that need formulas in them. I can't do it all with array formulas because some fields will need to be manually edited before sending the emails (which would break the array formula). – Michael Collins Jun 22 '17 at 21:33
  • Because of this constraint, I tried your IF(){} suggestion, which seems like exactly what I need. When I drop that into my script I get an error: "Syntax error. (line 7, file "sender v.2")." – Michael Collins Jun 22 '17 at 21:35
  • See my edit. Also: Do you know how to run the [debugger](https://developers.google.com/apps-script/guides/support/troubleshooting)? You can click a line number in the editor and then use the debugger to run the code to that point. You will see the variables defined up to that point, which can be helpful. – Karl_S Jun 22 '17 at 21:43
  • Thanks, @Karl_S. Right now the LastPopulatedRow function is still hanging up, giving me this error: "TypeError: Cannot call method "getDataRange" of undefined. (line 24, file "sender v.2")" I'll read up on the debugger and see if I can get a better understanding of why it isn't working. – Michael Collins Jun 22 '17 at 22:05
  • Alright, I figured out that somehow the script had started trying to operate on a different worksheet in my spreadsheet. I added `getSheetByName` which seems to have things working again. For whatever reason the script doesn't seem to like `var range = sheet.getRange(2,1,lr,8)`, but when I change it to `var range = sheet.getRange("A2:AA")` it seems to work as intended. Is that dangerous? Going to sleep on it, and see if I understand it better tomorrow. – Michael Collins Jun 22 '17 at 22:53
  • With the if() statement limiting the rows worked on, you can use `var range = sheet.getRange("A2:AA")` I have working code in your sample file using `var range = sheet.getRange(2,1,lr,8)`. Not sure what your version didn't like but make sure: 1) You are sending the correct sheet to the function `getLastPopulatedRow(sheet)`. It should return the last row to have actual data. 2) That the IF in the ARRAYFORMULA doesn't return "" for an empty value, as noted in my answer. Also note that `getLastPopulatedRow(sheet)` could be copied and modified to return the last column as well. – Karl_S Jun 23 '17 at 12:13
0

Instead of this var range = sheet.getRange("A2:AA")how about this sheet.getDataRange()

This loops works but there seems to be something wrong with the sendMail statement and I don't want to mess with it.

function troubleinMailVille()
{
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var UserData = range.getValues();
var s='<h1>Sending Mail</h1>';
for (var i=1;i<UserData.length;i++) 
  {
    var emailaddress = UserData[i][2];
    var bccaddress = UserData[i][3];
    var subject = UserData[i][4];
    var comments = UserData[i][5];
    var filenames = UserData[i][7];

    s+= '<br />1.  ' + emailaddress + ', ' + bccaddress + ', ' + subject + ', ' + comments + ', ' + filenames;
  } 
  //MailApp.sendEmail(emailaddress, subject, filenames + "\n\n" + comments, {cc: "example@gmail.com",bcc: bccaddress,name: "John Doe"});
  var html=HtmlService.createHtmlOutput(s).setWidth(800).setHeight(450);
  SpreadsheetApp.getUi().showModelessDialog(html, 'Sending Emails without actually sending them');
}
Cooper
  • 59,616
  • 6
  • 23
  • 54
  • I replaced `sheet.getRange("A2:AA")` with `sheet.getDataRange()` and it didn't work. I also tried `sheet.getDataRange("A2:AA")` which didn't work either. Am I doing it wrong? – Michael Collins Jun 22 '17 at 19:34
0

You can use:

sheet.getLastRow()

By this way, your script just will take the cells that have a value and this will reduce the execution time of the original script.


function uploadsender() {
var sheet = SpreadsheetApp.getActiveSheet();
Get the last row with a value
var last = sheet.getLastRow()-1;
//Then you can get the range that is useful for your function
// Just replace: "var range = sheet.getRange("A2:AA");" 
var UserData = sheet.getRange(2, 1, last, numColumns).getValues();
//Replace numColumns, you can use "sheet.getLastColumn();" or just writing how many columns you want to get.
for (i in UserData) {
var row = UserData[i];
var emailaddress = row[2];
var bccaddress = row[3];
var subject = row[4];
var comments = row[5];
var filenames = row[7]; 
MailApp.sendEmail(emailaddress, subject, filenames + "\n\n" + comments, {  
  cc: "example@gmail.com",          
  bcc: bccaddress,         
  name: "John Doe"
    });
 }
}
SMN947
  • 1
  • 4
  • If a formula is applied to an entire column `sheet.getLastRow()` will return all the rows until the last one where a formula is applied. So in the sample spreadsheet this still return all the rows. – Karl_S Jun 23 '17 at 12:02