Like I have done previously with Outlook Automation, I wanted to explorer the possibilities of embedding images in Thunderbird via VBA Automation
I’ve previously covered the subject of Thunderbird Automation in my article:
but this was for standard HTML content and I hadn’t, up until recently, explored what it would take to incorporate images directly in the e-mail body.
Thunderbird?
If you aren’t already aware, Thunderbird is an exceptional e-mail client brought to you by Mozilla. Yes, that’s right the same people that bring us the FireFox web browser.
Thunderbird truly is a great e-mail client easy to setup and work with and if you’re looking to get away from Outlook, this one’s worth seriously considering!
What Is Required To Embed Images Via Automation?
The short of it is that Thunderbird requires pure HTML content be passed to it. Thus, we need to encode image in base64 and use that to build a proper HTML img tag. So as long as we build proper HTML, it will display it properly.
Encoding an Image
Hence, we need a function, or functions, to convert an image into proper Base64 encoding.
So we need to take an image, such as: C:\Users\DevGuy\Downloads\RedDot.png, and transform it into:
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH5goGDg0tmf0acAAAANxJREFUGJWNjj1qw0AQhd/seKuVIGVyAxeGFLlCCBhfw/gYjo3JkUJukSKXSECdFGmZ3VlNKv/IbjLwwRTf4z0yM8M/bnbpXWeI6PS7ozCmhHR4Q5wvEOcLpNc9xpTOYVU1EbF+u7PfcDeh3+5MRCznbCQipqoYH59A3z+Tanu4B399gpnhVBUicrPvOElEkHM+i3G1vBHjagkRgapiVkqBqqLfrCGloH7/AAC0L8+ImzVCzvDeg5qmsa7r0LYthmFAjBHMDO89qqpCXdcIIcAREYgIzjkw8wnn3IQ/w+mJH97B6tIAAAAASUVORK5CYII=
which is its representation in base64 encoding.
So, I created the following 2 functions to do exactly that:
- one to read the file and return it as a byte array
- one to convert a byte array into a base64 encoded string
'---------------------------------------------------------------------------------------
' Procedure : ReadFileAsBinary
' Author : Daniel Pineault, CARDA Consultants Inc.
' Website : http://www.cardaconsultants.com
' Purpose : Reads a file as binary content
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
' (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft ActiveX Data Objects X.X Library
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sFile : Fully qualified path and filename of the file to read
'
' Revision History:
' Rev Date(yyyy-mm-dd) Description
' **************************************************************************************
' 1 2022-10-05 Initial Public Release
'---------------------------------------------------------------------------------------
Public Function ReadFileAsBinary(ByVal sFile As String) As Variant
On Error GoTo Error_Handler
'#Const EarlyBind = 1 'Use Early Binding
#Const EarlyBind = 0 'Use Late Binding
#If EarlyBind Then
Dim oADOStream As ADODB.Stream
#Else
Dim oADOStream As Object
Const adTypeBinary = 1
#End If
Dim fileBytes() As Byte
#If EarlyBind Then
Set oADOStream = New ADODB.Stream
#Else
Set oADOStream = CreateObject("ADODB.Stream")
#End If
With oADOStream
.Open
.Type = adTypeBinary
.LoadFromFile sFile
fileBytes() = .Read
.Close
End With
ReadFileAsBinary = fileBytes()
Error_Handler_Exit:
On Error Resume Next
If Not oADOStream Is Nothing Then
oADOStream.Close
Set oADOStream = Nothing
End If
Exit Function
Error_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Source: ReadFileAsBinary" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Function
'---------------------------------------------------------------------------------------
' Procedure : EncodeBase64
' Author : Daniel Pineault, CARDA Consultants Inc.
' Website : http://www.cardaconsultants.com
' Purpose : Encodes the supplied byte array as a base64 string used for HTML img tags
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
' (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Late Binding -> None required
' Early Binding -> Microsoft XML, v6.0
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' fileBytes : binary byte array of the file to convert to base64
'
' Usage:
' ~~~~~~
' sB64 = EncodeBase64(ReadFileAsBinary("C:\Temp\OrgChart.jpg"))
'
' Revision History:
' Rev Date(yyyy-mm-dd) Description
' **************************************************************************************
' 1 2022-10-05 Initial Public Release
'---------------------------------------------------------------------------------------
Public Function EncodeBase64(fileBytes() As Byte) As String
On Error GoTo Error_Handler
'#Const EarlyBind = 1 'Use Early Binding
#Const EarlyBind = 0 'Use Late Binding
#If EarlyBind Then
Dim oXML As MSXML2.DOMDocument60
Dim oNode As MSXML2.IXMLDOMElement
Set oXML = New MSXML2.DOMDocument60
#Else
Dim oXML As Object
Dim oNode As Object
Set oXML = CreateObject("MSXML2.DOMDocument.6.0")
#End If
Set oNode = oXML.createElement("b64")
With oNode
.DataType = "bin.base64"
.nodeTypedValue = fileBytes
EncodeBase64 = Replace(.text, vbLf, "")
End With
Error_Handler_Exit:
On Error Resume Next
If Not oNode Is Nothing Then Set oNode = Nothing
If Not oXML Is Nothing Then Set oXML = Nothing
Exit Function
Error_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Source: EncodeBase64" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Function
Building the Img Tag
Next, I created a simple function that took the results from the above functions and created the proper HTML img tag that we could simply call when building an e-mail’s HTML body content:
'---------------------------------------------------------------------------------------
' Procedure : TB_AddImageFile
' Author : Daniel Pineault, CARDA Consultants Inc.
' Website : http://www.cardaconsultants.com
' Purpose : Convert an image file into an HTML img tag base64 entry
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
' (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: None required
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sFile : fully qualified path and file name of the image file to convert
' (so far tested with jpg, gif, png and bmp)
'
' Usage:
' ~~~~~~
' sHTML = TB_AddImageFile("C:\Temp\OrgChart.jpg")
' sHTML = TB_AddImageFile("C:\Temp\OrgChart.png")
' sHTML = TB_AddImageFile("C:\Temp\OrgChart.gif")
' sHTML = TB_AddImageFile("C:\Temp\OrgChart.bmp")
'
' Revision History:
' Rev Date(yyyy-mm-dd) Description
' **************************************************************************************
' 1 2022-10-05 Initial Release
'---------------------------------------------------------------------------------------
Public Function TB_AddImageFile(sFile As String) As String
On Error GoTo Error_Handler
Dim sOutput As String
Dim sFileName As String
Dim sFileExt As String
sFileName = Right(sFile, Len(sFile) - InStrRev(sFile, "\"))
sFileExt = Right(sFile, Len(sFile) - InStrRev(sFile, "."))
sOutput = "<img src=""data:image/"
sOutput = sOutput & sFileExt
If sFileExt = "bmp" Then 'Special case for bmp files!
sOutput = sOutput & ";filename=" & sFileName
End If
sOutput = sOutput & ";base64,"
sOutput = sOutput & EncodeBase64(ReadFileAsBinary(sFile))
sOutput = sOutput & """>"
TB_AddImageFile = sOutput
Error_Handler_Exit:
On Error Resume Next
Exit Function
Error_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Source: TB_AddImageFile" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Function
So taking the above example and now encoding and building the img tag we can get
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH5goGDg0tmf0acAAAANxJREFUGJWNjj1qw0AQhd/seKuVIGVyAxeGFLlCCBhfw/gYjo3JkUJukSKXSECdFGmZ3VlNKv/IbjLwwRTf4z0yM8M/bnbpXWeI6PS7ozCmhHR4Q5wvEOcLpNc9xpTOYVU1EbF+u7PfcDeh3+5MRCznbCQipqoYH59A3z+Tanu4B399gpnhVBUicrPvOElEkHM+i3G1vBHjagkRgapiVkqBqqLfrCGloH7/AAC0L8+ImzVCzvDeg5qmsa7r0LYthmFAjBHMDO89qqpCXdcIIcAREYgIzjkw8wnn3IQ/w+mJH97B6tIAAAAASUVORK5CYII=">
which we can use in any HTML document or HTML compliant e-mail.
Putting It All Together
Now with all the pieces of the puzzles, we can easily build an e-mail with embedded images by simply doing something like:
Sub TestMe()
On Error GoTo Error_Handler
Dim sHTML As String
sHTML = "<html><body>" 'Not strictly necessary, but still good practice
sHTML = sHTML & "Hello <b>World</b>!" & vbLf
sHTML = sHTML & "<br><br><br>" & TB_AddImageFile(Application.CurrentProject.Path & "\test.jpg") & vbLf
sHTML = sHTML & "<br><br>" & TB_AddImageFile(Application.CurrentProject.Path & "\test.png") & vbLf
sHTML = sHTML & "<br><br>" & TB_AddImageFile(Application.CurrentProject.Path & "\test.gif") & vbLf
sHTML = sHTML & "<br><br>" & TB_AddImageFile(Application.CurrentProject.Path & "\test.bmp") & vbLf
sHTML = sHTML & "</body></html>" 'Not strictly necessary, but still good practice
Call TB_SendEmail("abc@xyz.com;def@wuv.ca;", "My Subject", sHTML)
'Include in the closing of the form or database
' If TempVars!bUseHTMLFile = True Then Kill Environ("TEMP") & "\TB_HTML.html"
Error_Handler_Exit:
On Error Resume Next
Exit Sub
Error_Handler:
If Err.Number <> 2501 Then
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Source: TestMe" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
End If
Resume Error_Handler_Exit
End Sub
Final Remarks
I say proper HTML in this approach because it generates valid HTML, that if you save, will render in a web browser just fine. You’ll also see other e-mail client support the same HTML format. So in fact, not only have we developed image embedding in Thunderbird, but moreover we’ve developed generating HTML pages with embedded images which can have much broader uses.
Do note however, that this approach, even when using .HTMLBody, with MS Outlook does NOT work. Outlook requires that you attach each image, set the attachment property, give them each a unique CID and reference the CID in the HTML.
Lastly, another great thing about this code is it is application independent so you can use it in Access, Excel, Outlook, PowerPoint, Word, … (any VBA environment), is bitness independent (works with both 32 and 64-bit installations) and requires no APIs. Win-win-win!
Download a Demo Database
Feel free to download a 100% unlocked copy by using the link provided below:
Disclaimer/Notes:
If you do not have Microsoft Access, simply download and install the freely available runtime version (this permits running MS Access databases, but not modifying their design):Microsoft Access 2010 Runtime
Microsoft Access 2013 Runtime
Microsoft Access 2016 Runtime
Microsoft 365 Access Runtime
In no event will Devhut.net or CARDA Consultants Inc. be liable to the client/end-user or any third party for any damages, including any lost profits, lost savings or other incidental, consequential or special damages arising out of the operation of or inability to operate the software which CARDA Consultants Inc. has provided, even if CARDA Consultants Inc. has been advised of the possibility of such damages.
Download “VBA - Thunderbird Automation - Embedded Images - Demo.zip” Thunderbird-EmbeddedImages-Demo.zip – Downloaded 5798 times – 261.00 KB
Great article, Daniel. I was not aware of the Base64-encoding technique for embedding raw image data into HTML. I feel like I have a shiny new hammer and I need to go looking for nails now.
What’s also fun is you can do the inverse, take a web image and read the base64 and convert it back to a byte array and save it locally. I do that in my Bar Code and QR Code databases.