solutions icon indicating copy to clipboard operation
solutions copied to clipboard

GMail/Sheets images embedded inline in draft email get sent as attachments only

Open ChrisE40 opened this issue 4 years ago • 6 comments

I thought this might be a GMail problem but am now thinking it must be caused by the script that sends the emails. The embedded image looks fine in the GMail draft. The recipient gets an image placemark with an x in the middle (not found) and the image is there as an attachment. Means I can't have a letterhead on my emails.

ChrisE40 avatar Nov 27 '20 02:11 ChrisE40

Same Problem, Have you found any solution yet?

vivekagr2021 avatar Jan 05 '21 05:01 vivekagr2021

I suspect it is some sort of security measure by Google. I got around it by using a URL image.

Sent from my iPhone

On 5 Jan 2021, at 4:47 pm, vivekagr2021 [email protected] wrote:

 Same Problem, Have you found any solution yet?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

ChrisE40 avatar Jan 05 '21 07:01 ChrisE40

Subscribing! It's a problem for me too!

BenjaminCharlton avatar Jan 28 '21 11:01 BenjaminCharlton

in your draft do not upload or drag and drop the picture

Solution: Click on insert photo and select web address URL (the URL should be public) and paste the URL of your pic. Note: if your photo is local, you can use imgBB to upload your pic online and get the URL

rawaps avatar Jan 31 '21 00:01 rawaps

Ran into this issue as well while setting up mail merge for one of our staff members.

Began by looking around the Web; found code at https://stackoverflow.com/a/49621562 which provided a possible fix, but after modding the @mhawksey code to incorporate the code from the SO answer as part of getGmailTemplateFromDrafts_() I still couldn't get things working properly.

After a few (dozen) iterations of banging my head on it, I sent a message with inlines to myself, and compared the HTML code from "Show Original" to both a Logger.log of msg.getBody() from the Draft message and a "Show Original" from one of my failed Merge messages, and noted the following:

Draft message HTML Body <img> tag (from msg.getBody()) <img data-surl="cid:ii__abcdefghi_" src="cid:ii__abcdefghi_" alt="_Inserted Inline Image_.jpg" width="250" height="150">

Mail Merge Sent message HTML Body <img> tag <img data-surl="cid:ii__abcdefghi_" src="cid:ii__abcdefghi_" alt="_Inserted Inline Image_.jpg" width="250" height="150">

Human Sent message HTML Body <img> tag <img src="cid:ii__abcdefghi_" alt="_Inserted Inline Image_.jpg" width="250" height="150">

Essentially, the "draft" message <img> tags incorporate the "data-surl" parameter, and the failed Merge message copies those verbatim...but the Human message <img> tags do not.

My deduction is that Google changed how they handled Inline images at some point between when @mhawksey wrote getGmailTemplateFromDrafts_() and February 8th, 2021 (which is when I started noodling with this). I found backup for that deduction at https://issuetracker.google.com/issues/36757948 and https://issuetracker.google.com/issues/36755796.

Now, knowing what a "proper" <img> tag should look like for an inline image, and having learned from https://developers.google.com/apps-script/reference/gmail/gmail-app#sendEmail(String,String,String,Object) that one of the optional Object parameters is inlineImages, I was able to mod the SO code and getGmailTemplateFromDrafts_() to:

  • create an html_Body variable to contain the contents of msg.getBody() for modification;
  • modify the <img> tags in html_Body to conform to the new format for actual messages (vs drafts);
  • iteratively extract just the Inline images from attachments (keying by RegEx .match with data-surl="cid:) into an inlineImages array; and
  • return both modified items to emailMessage for use in GmailApp.sendEmail() which was also modified to incorporate retrieval of the inlineImages array value.

Here's the code I put together; it could probably be more optimized, but programming isn't my forte.

Line 98 (becomes 2 lines):

          attachments: emailTemplate.attachments, 
          inlineImages: emailTemplate.inlineImages

Lines 114-133 (too many changes to break out):

  /**
   * Get a Gmail draft message by matching the subject line.
   * @param {string} subject_line to search for draft message
   * @return {object} containing the subject, plain and html message body and attachments
   * 
   * Incorporates code to correctly parse and insert inline images
   * @see https://stackoverflow.com/a/49621562
  */
  function getGmailTemplateFromDrafts_(subject_line){
    try {
      // get drafts
      const drafts = GmailApp.getDrafts();
      // filter the drafts that match subject line
      const draft = drafts.filter(subjectFilter_(subject_line))[0];
      // get the message object
      const msg = draft.getMessage();
      // getting attachments so they can be included in the merge
      const attachments = msg.getAttachments();
      // get HTML body text
      var html_Body = msg.getBody();
      // Configure a RegExp for identifying draft Image Tags that indicate "inline" images
      const regDraftImgTag = new RegExp('img data-surl="cid:', "g");

      if (html_Body.match(regDraftImgTag) != null) {
        var inlineImages = {};
        var imgVars = html_Body.match(/<img[^>]+>/g);
        var imgToReplace = [];
        if(imgVars != null){
          for (var i = 0; i < imgVars.length; i++) {
            if (imgVars[i].search(regDraftImgTag) != -1) {
              var id = imgVars[i].match(/src="cid:([^"]+)"/);
              if (id != null) {
                var imgTitle = imgVars[i].match(/alt="([^"]+)"/);
                if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id[1]]);
              }
            }
          }
        }
        for (var i = 0; i < imgToReplace.length; i++) {
          for (var j = 0; j < attachments.length; j++) {
            if(attachments[j].getName() == imgToReplace[i][0]) {
              inlineImages[imgToReplace[i][2]] = attachments[j].copyBlob();
              attachments.splice(j, 1);
              var newImg = imgToReplace[i][1].replace(/ data-surl="[^"]+"/, "");
              html_Body = html_Body.replace(imgToReplace[i][1], newImg);
            }
          }
        }
      }
      return {message: {subject: subject_line, text: msg.getPlainBody(), html: html_Body }, 
              attachments: attachments, inlineImages: inlineImages};
    } catch(e) {
      throw new Error("Oops - can't find Gmail draft");
    }

@mhawksey, I'd be happy to submit this as a patch to your code if you want.

tyochelson-lwhs avatar Feb 11 '21 01:02 tyochelson-lwhs

@tyochelson-lwhs thanks for your investigation and reporting. Looking into this I think you are right the the inlineImages object needs to be rebuild. As it happens I had also recently come across a SO contribution, which provided a way to rebuild the inlineImages object whilst using the existing cid references. I've submitted a patch which is included in this modified version of the solution Google Sheet . If you could test and provide feedback that would be great

mhawksey avatar Feb 14 '21 16:02 mhawksey