Excel VBA: Convert an Image to Base64 and Embed It in an Outlook Email
You have a logo or a chart export in Excel VBA and you want it inside the email body — not as a paperclip attachment. Forum threads going back a decade converge on two different problems that get mixed up: encoding the image to Base64 (easy, four lines of ADODB + MSXML) and getting it to render in Outlook (where the popular data-URI advice silently fails). This page separates them: the encoding snippet, the CID pattern that actually renders in Outlook, and the pitfalls that break each.
First pick your path
Most VBA email-image questions are really three different jobs:
- Send via Outlook automation and show the image inline → you need the hidden-attachment CID pattern (below). You do not need Base64 for this at all —
Attachments.Addtakes the file path directly. - Get the Base64 string itself — to store in a worksheet/XML, POST to an API, or feed CDO/SMTP → you need the ADODB.Stream + MSXML snippet.
- Paste a
data:image/...;base64,URI into the HTML body and hope → this is the path that fills old forum threads. It renders in Gmail, Apple Mail, and most webmail, but Outlook desktop never renders data URIs. If your recipients are Outlook, this path is dead on arrival.
Encode an image to Base64 in VBA
VBA has no native Base64 function. The standard pair is ADODB.Stream (reads the file as binary) and MSXML2.DOMDocument (does the encoding):
Function EncodeFileToBase64(ByVal path As String) As String
Dim xml As Object, node As Object, stream As Object
Set xml = CreateObject("MSXML2.DOMDocument.6.0")
Set node = xml.createElement("b64")
node.DataType = "bin.base64"
Set stream = CreateObject("ADODB.Stream")
stream.Type = 1 ' adTypeBinary
stream.Open
stream.LoadFromFile path
node.nodeTypedValue = stream.Read
stream.Close
EncodeFileToBase64 = node.Text ' NOTE: wrapped at 76 chars
End Function
Two things to know about the output:
- It contains line breaks every 76 characters (RFC 2045). Before building a data URI or a JSON payload, strip them:
clean = Replace(Replace(EncodeFileToBase64(p), vbCr, ""), vbLf, "") - It is raw Base64 — no
data:prefix. Add the MIME wrapper yourself:"data:image/png;base64," & clean. The prefix must match the actual format (png vs jpeg).
To produce the string from a workbook image instead of a file, export the shape or chart first — Shape.CopyPicture plus Chart.Export to a PNG temp file — then run the same function on the file.
Want the encoded string without touching VBA? Encode in the browser with the image to Base64 converter — nothing is uploaded — and paste the result where you need it.
Embed in Outlook: the CID pattern
The way Outlook itself embeds inline images is a hidden attachment referenced by Content-ID. This macro attaches the image, hides it from the attachment bar, and points an <img> at it:
Sub SendEmailWithEmbeddedImage()
Dim ol As Object, mail As Object, att As Object
Const imgPath As String = "C:\Users\you\Pictures\logo.png"
Const cid As String = "logo001"
Set ol = CreateObject("Outlook.Application")
Set mail = ol.CreateItem(0) ' olMailItem
Set att = mail.Attachments.Add(imgPath)
att.PropertyAccessor.SetProperty _
"http://schemas.microsoft.com/mapi/proptag/0x3712001F", cid ' PR_ATTACH_CONTENT_ID
att.PropertyAccessor.SetProperty _
"http://schemas.microsoft.com/mapi/proptag/0x7FFE000B", True ' PR_ATTACHMENT_HIDDEN
mail.HTMLBody = "<html><body>" & _
"<p>Weekly report attached below.</p>" & _
"<img src=""cid:" & cid & """ width=""200"">" & _
"</body></html>"
mail.To = "someone@example.com"
mail.Subject = "Weekly report"
mail.Display ' use .Send when tested
End Sub
Why this works where the data URI doesn't: Outlook re-assembles the message as MIME on send, and cid: references resolve inside that MIME structure — the one mechanism every Outlook build renders. The 0x7FFE000B property (PidTagAttachmentHidden) keeps the image off the recipient's attachment bar, so the mail looks like a body image, not an attachment.
The same CID logic is what the low-code paths use — see the Power Automate Base64 image in email and Apps Script Base64 image in email guides for the hosted equivalents.
Why the data URI fails in Outlook
Outlook desktop renders HTML email with the Word rendering engine, and that engine has never supported data: URIs. Outlook on the web goes further and strips them during sanitization. So a <img src="data:image/png;base64,..."> in an Outlook-generated mail renders fine in Gmail or Apple Mail — and shows a blank box in Outlook. This is the single most common outcome in the old forum threads on the topic.
| Mechanism | Renders in Outlook desktop | Needs VBA Base64 code | Best for |
|---|---|---|---|
| Hidden attachment + cid: | Yes | No — file path only | Outlook recipients (the default choice) |
| data: URI in HTMLBody | No | Yes | Gmail/webmail recipients only |
| Plain attachment | Yes (as file) | No — but shows paperclip | Files the recipient should keep |
The client-by-client breakdown of this restriction is in the Base64 image not showing in Outlook guide; the signature-specific variant is in Base64 image in email signature.
Pitfalls
- Wrapped Base64 pasted into an HTML attribute. MSXML output breaks at 76 characters; inside
src="..."the line breaks terminate the value and the image is blank. Always stripvbCr/vbLfbefore use. - Wrong MIME prefix.
data:image/pngon JPEG bytes (or a missing prefix) breaks rendering in every client that otherwise supports data URIs. Attachments.Addgiven a Base64 string. It expects a path. If all you have is encoded text, decode to a temp file first — or skip encoding entirely and point it at the original file.- cid mismatch. The
content_idproperty and thecid:reference must match exactly, including case. A silent blank box is the only symptom. - Image too heavy. Base64 adds ~33% over binary size, and the payload rides in the message. A 300 KB logo becomes a 400 KB email. Compress first — see compress PNG to Base64 / compress JPG to Base64, and size it with the Base64 size calculator.
Frequently asked questions
Do I need to Base64-encode the image at all when sending from VBA via Outlook?
No — the Outlook automation path doesn't need it. Attachments.Add takes a file path, and the hidden CID attachment is the inline-image mechanism. Encoding matters when the string goes elsewhere: XML, JSON, an API, or CDO/SMTP.
Why doesn't the data URI image show in Outlook?
Outlook desktop renders with the Word engine, which has no data: URI support; Outlook web strips them. Use the hidden attachment + cid: pattern for Outlook recipients.
How do I hide the inline attachment icon?
Set 0x7FFE000B (PidTagAttachmentHidden) to True via PropertyAccessor on the attachment, and reference it as cid: in the HTML body.
Why does the Base64 string have line breaks every 76 characters?
MSXML's bin.base64 type emits RFC 2045 output. Strip the breaks with Replace before building a data URI or JSON payload.
Can I pass a Base64 string directly to Attachments.Add?
No — it needs a file path. Use the original file, or decode to a temp file first.
How large can the embedded image be?
No hard limit, but the payload inflates the message (~33% over binary). Keep logos and chart exports to a few hundred KB; compress before embedding.
Producing the Base64 string for an API or a webmail audience? Encode it in the browser with the Image to Base64 converter, check the weight with the size calculator, and copy the HTML or data-URI output straight from the results panel.