1

I’m trying to attach a file to an email I send in Google Apps Script with MailApp.sendEmail(). In the browser JavaScript I read in the file manually with this code because I’ve already processed the equivalent of the submit button in my HTML form, and it works:

  var file = document.getElementById('myfile');
  var fileInfo = [];
  if(file.files.length)        // if there is at least 1 file
  {
    if (file.files[0].size < maxEmailAttachmentSize)  // and its size is < 25M
    {
      var reader = new FileReader();
      reader.onload = function(e)
      {
        fileInfo[0] = e.target.result;
      };
      reader.readAsBinaryString(file.files[0]);
      fileInfo[1] = file.files[0].name;
      fileInfo[2] = file.files[0].type;
      fileInfo[3] = file.files[0].size;
    }
    console.log(fileInfo);   // here I see the full file and info. All looks correct.
  }

Then I send it up to the server.

  google.script.run.withSuccessHandler(emailSent).sendAnEmail(fileInfo);

On the server I pull out the fields and send the email like so:

  var fname    = fileInfo[1];
  var mimeType = fileInfo[2];
  var fblob    = Utilities.newBlob(fileInfo[0], mimeType, fname);
  // all looks right in the Logger at this point

  try {
    GmailApp.sendEmail(emaiRecipient, emailSubject, emailBody, 
      {
        name: 'Email Sender',  // email sender
        attachments: [fblob]
      }
    );
  catch …

This works fine when the file is a text file or HTML file but doesn’t when the file is anything else. The file is sent but it's empty and apparently corrupt. Can anyone see what’s wrong with this code? (It doesn’t work with MailApp.sendEmail() either.) I did see in another stackoverflow post that the document has to be saved once, but that is something I definitely don’t want to do. Isn’t there any other way? What am I missing?

jeff
  • 109
  • 1
  • 1
  • 9
  • 1
    Please add more details. What kind of file are you triying to attach? What is the file size? Does the file has content that might be harmful, suspicious, spammy? FileReader is asyncrohous. How is your client-side code handling this? Also, please add a [mcve]. – Rubén Sep 04 '20 at 18:08
  • 1
    I've tried sending .txt, .HTML, .JPG, .PNG and .PDF. Only the first 2 work. They're all relatively small and in my console output I see the the file read in on the browser side is complete before it's sent to the server. On the server side I can see that the log of the file contents is the same as that on the client. The JPG and PNG files, for instance, are small and clearly not suspicious or spammy. Mimetypes on the server side are correct for each file. I must have a bug in my code, but what? – jeff Sep 04 '20 at 19:29
  • 2
    Does this answer your question? https://stackoverflow.com/questions/7431365 – TheMaster Sep 04 '20 at 21:11
  • @TheMaster It helps a lot. Thank you. I tried to just change readAsBinaryString() to readAsArrayBuffer() keeping all else the same and I get an exception on the server of Uncaught TypeError: Failed due to illegal value in property: 10. Is there a straightforward change I can make in my existing client code to use readAsArrayBuffer()? I can't use Tanaike's excellent example because we are running on the Rhino JavaScript engine, not V8. We don't have support or more recent JavaScript syntax. Thx! – jeff Sep 05 '20 at 17:06
  • 1
    @jeff Update to v8. In any case, Tanaike's solution is fully client side. No need for v8. If you're having issues, it's due to something else and needs to be investigated separately. – TheMaster Sep 05 '20 at 18:52
  • Hi ! Do you exclusively want to use ```MailApp``` or are you also open to be using [Gmail API](https://developers.google.com/gmail/api) in Apps Script through enabling *Advanced Google Services*? Also, have you tried applying the updated answer Tanaike has provided? Do you get any error using the new updated answer? – Mateo Randwolf Sep 07 '20 at 10:55
  • @MateoRandwolf - I've gone back to using GmailApp but both work. Out of curiosity is there an advantage to using the advanced service GmailApp vs. MailApp? Thx. (See my response at the bottom of the page. Yes, it's working!) – jeff Sep 08 '20 at 15:08

1 Answers1

3

Modification points:

  • FileReader works with the asynchronous process. This has already been mentioned by Rubén's comment.
  • In the current stage, when the binary data is sent to Google Apps Script side, it seems that it is required to convert it to the string and byte array. This has already been mentioned by TheMaster's comment.
  • In order to use your Google Apps Script, in this case, I think that converting the file content to the byte array of int8Array might be suitable.
    • For this, I used readAsArrayBuffer instead of readAsBinaryString.

When above points are reflected to your script, it becomes as follows.

Modified script:

HTML&Javascript side:

// Added
function getFile(file) {
  return new Promise((resolve, reject) => {
    var reader = new FileReader();
    reader.onload = (e) => resolve([...new Int8Array(e.target.result)]);
    reader.onerror = (err) => reject(err);
    reader.readAsArrayBuffer(file);
  });
}

async function main() {  // <--- Modified
  var file = document.getElementById('myfile');
  var fileInfo = [];
  if(file.files.length) {
    if (file.files[0].size < maxEmailAttachmentSize) {
      fileInfo[0] = await getFile(file.files[0]);  // <--- Modified
      fileInfo[1] = file.files[0].name;
      fileInfo[2] = file.files[0].type;
      fileInfo[3] = file.files[0].size;
    }
    console.log(fileInfo);   // here I see the full file and info. All looks correct.
    google.script.run.withSuccessHandler(emailSent).sendAnEmail(fileInfo);
  }
}
  • Although I'm not sure about your whole script from your question, at the modified script, it supposes that main() is run. When main() is run, the file is converted to the byte array and put it to fileInfo[0].
  • At Google Apps Script side, from fileInfo, var fblob = Utilities.newBlob(fileInfo[0], mimeType, fname); has the correct blob for Google Apps Script.

Google Apps Script side:

In this modification, your Google Apps Script is not modified.

References:

Added:

This code looks good but we can't use it because we are running on the Rhino JavaScript engine, not V8. We don't have support for newer JavaScript syntax. Could you give us an example of how it's done with older syntax? Ref

From your above comment, I modified as follows.

Modified script:

HTML&Javascript side:

function main() {
  var file = document.getElementById('myfile');
  var fileInfo = [];
  if(file.files.length) {
    if (file.files[0].size < maxEmailAttachmentSize) {
      var reader = new FileReader();
      reader.onload = function(e) {
        var bytes = new Int8Array(e.target.result);
        var ar = [];
        for (var i = 0; i < bytes.length; i++) {
          ar.push(bytes[i]);
        }
        fileInfo[0] = ar;
        fileInfo[1] = file.files[0].name;
        fileInfo[2] = file.files[0].type;
        fileInfo[3] = file.files[0].size;
        console.log(fileInfo);   // here I see the full file and info. All looks correct.
        google.script.run.withSuccessHandler(emailSent).sendAnEmail(fileInfo);
      }
      reader.onerror = function(err) {
        reject(err);
      }
      reader.readAsArrayBuffer(file.files[0]);
    }
  }
}

Google Apps Script side:

In this modification, your Google Apps Script is not modified.

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • This code looks good but we can't use it because we are running on the Rhino JavaScript engine, not V8. We don't have support for newer JavaScript syntax. Could you give us an example of how it's done with older syntax? – jeff Sep 05 '20 at 17:09
  • 1
    @jeff Thank you for replying. I apologize for the inconvenience. From your question, I couldn't notice that your browser cannot use V8 runtime. I deeply apologize for this. I updated my answer. Could you please confirm it? But if you are not using V8 runtime at the script editor for Google Apps Script, above modification might not affect to your current issue. Because HTML&Javascript is not run the server side. At that time, in order to correctly understand about your current situation, can you provide your whole script for replicating your issue? I would like to confirm it. – Tanaike Sep 05 '20 at 23:31
  • TheMaster Mateo Randwolf It works! Tanaike 's second example works perfectly! Thank you so much. And thank you all! I had switched the server runtime to V8 and it didn't help, but TheMaster you're right we should be using it. (Actually we were but a few months ago some minor bug fix Google made broke it and we had to switch back to Rhino. All good now.) As far as MailApp vs. Gmail App I only switched from MailApp because things weren't working. I will switch back. Thank you thank you thank you, especially Tanaike ! – jeff Sep 07 '20 at 15:13