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:

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:

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.

Base64 image embedding in VBA-generated mail, by mechanism
MechanismRenders in Outlook desktopNeeds VBA Base64 codeBest for
Hidden attachment + cid:YesNo — file path onlyOutlook recipients (the default choice)
data: URI in HTMLBodyNoYesGmail/webmail recipients only
Plain attachmentYes (as file)No — but shows paperclipFiles 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

  1. 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 strip vbCr/vbLf before use.
  2. Wrong MIME prefix. data:image/png on JPEG bytes (or a missing prefix) breaks rendering in every client that otherwise supports data URIs.
  3. Attachments.Add given 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.
  4. cid mismatch. The content_id property and the cid: reference must match exactly, including case. A silent blank box is the only symptom.
  5. 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.