A common workflow solution in Google Apps Script is to use a Gmail draft message as a template for sending emails. For the user the benefit is they can draft a message in a familiar environment adding formatting without having to worry about writing HTML. This is the model I chose to use in the ‘Create a mail merge using Gmail and Google Sheets’ example in the Google Workspace Solutions Gallery.
Contents
Problem
So here’s the problem – using the draft as a template generally works well but when you call GmailApp.sendEmail()
or MailApp.sendEmail()
you are not sending the actual draft message, instead you are constructing a new email setting parameters such as subject
, htmlBody
and attachments
.
Anatomy of Gmail images
The tricky bit is when you want to include images as the user can add these to the message in different ways: inserting from Google Photos; an upload; web address (url); or copy/paste:

When an image is inserted by URL or copy/pasted from a publicly accessible source, such as a Google Doc that has been published to the web, Gmail will keep the reference to the image source but proxy it via one of its public content servers (googleusercontent.com). For example, if I insert an image by web address for a ‘chubby cat’ on Wikimedia Commons with the url:
https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Chubby_Cat.jpg/320px-Chubby_Cat.jpg
In the sent email it is referenced as:
<img src="https://ci5.googleusercontent.com/proxy/LoR..._HUA=s0-d-e1-ft#https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Chubby_Cat.jpg/320px-Chubby_Cat.jpg" width="320" height="213" class="CToWUd a6T" tabindex="0">
If instead images are uploaded or copy/pasted from a private source to the Gmail draft when .sendEmail()
is used an inlineImages
object is required to handle these inline images. Below is the excerpt from the GmailApp.sendMail()
documentation:
Name | Type | Description |
---|---|---|
|
| if set, devices capable of rendering HTML will use it instead of the required body argument; you can add an optional |
|
| a JavaScript object containing a mapping from image key ( |
The image Content-ID
(cid
)
In the case of the ‘chubby cat’ image inserted from Google Photos the Gmail draft htmlBody
already includes a cid
references:
<img data-surl="cid:ii_kl736chs6" src="cid:ii_kl736chs6" alt="320px-Chubby_Cat.jpg" width="320" height="213">
So if we are copying the htmlBody
of the draft to use in .sendMail()
our inlineImages
object would look like this, where BlobSource
is the appropriate image data:
GmailApp.sendEmail({
to: '[email protected]',
subject: 'Chubby Cat',
htmlBody: 'inline chubby cat <img data-surl="cid:ii_kl736chs6" src="cid:ii_kl736chs6" alt="320px-Chubby_Cat.jpg" width="320" height="213"> images!',
inlineImages:
{
ii_kl736chs6: BlobSource
}
});
When we make our request for the Gmail draft we can also get the inline attachments using the .getAttachments(options)
method. This returns an array of image data objects as BlobSource
:
GmailMessage.getAttachments({includeInlineImages: true, includeAttachments: false})
The cid
kicker
Now here’s the kicker GmailAttachment[]
doesn’t have a method to return the appropriate cid
AND you can’t rely on the array order as the returned GmailAttachment[]
is constructed in the order the user added images to the draft, not the order that they appear in the message. I’ve updated a related Google Apps Script issue ticket, which you can star for email updates.
Solution
Option 1 – theoretical solution
Instead of the GmailApp
Service you could use the Gmail API users.drafts.get
method and work with the response (here is a Stack Overflow answer for doing this in Javascript that could be used in Apps Script). Once you extract the attachmentId
you would then have to build your own method for calling users.messages.attachments.get
and storing the inline images returned as blobs.
Option 2 – the alt/name fudge
While GmailAttacment[]
doesn’t include any cid
data there is a .getName()
method that returns the image filename. The filename is useful because the Gmail draft uses this in the alt
element:
<img data-surl="cid:ii_kl736chs6" src="cid:ii_kl736chs6" alt="320px-Chubby_Cat.jpg" width="320" height="213">
Important: It is possible for a Gmail draft to have duplicate filenames for inline images in the same message. This can happen with copy/paste or uploading more than one image with the same filename. As such you might want to throw an error when duplicate image filenames are detected.
There are various code samples for extracting the cid
and alt
data from Gmail draft messages. Below (or in this gist if you are reading via RSS) is my code based on this answer on Stack Overflow from Andras Sztrokay:
Summary
So there you go everything you ever wanted to know about Gmail draft inline images and Google Apps Script but were afraid to ask. If you have a better solution for handling inline images from Gmail drafts I would love to hear it. I hope you found this post useful and please feel free to incorporate it into your own Apps Script projects.