To figure out how to do this, you need to understand what Outlook (and some other email clients) are doing behind the scenes. You are, in fact, still "attaching" the images as separate files... but the body of the email message makes a special reference to the file that displays the image inline.
A lot of variables are at work, here, though. In order to make this special reference to the file, you code an HTML
tag into the message body. This, of course, means that the target email client must understand HTML messages. And even if it understands email messages, not all of them understand the special inline coding. So support for this solution is spotty at best.
But here's how I did it for an Intranet-based project, for which I was certain that Outlook would be used as the email client.
First, I created an array that described each of the images I wanted to use. While it looks complex, it tended to simplify things quite a bit when it came time to add additional images, etc. Each element in the array was actually a structure... the keynames of which should be fairly self-explanatory at this point:
Code:
<!--- create a new array that will hold descriptions of the images --->
<CFSET aryImageArray = ArrayNew(1)>
<!--- make the element of the array a structure --->
<CFSET aryImageArray[1] = StructNew()>
<!--- and begin to describe the image --->
<CFSET aryImageArray[1].filename = "firstimage.gif">
<CFSET aryImageArray[1].contenttype = "image/gif">
<CFSET aryImageArray[1].height = "190">
<CFSET aryImageArray[1].width = "193">
<CFSET aryImageArray[1].align = "left">
<CFSET aryImageArray[1].alt = "The first image">
<CFSET aryImageArray[2] = StructNew()>
<CFSET aryImageArray[2].filename = "secondimage.jpg">
<CFSET aryImageArray[2].contenttype = "image/jpeg">
<CFSET aryImageArray[2].height = "161">
<CFSET aryImageArray[2].width = "100">
<CFSET aryImageArray[2].align = "right">
<CFSET aryImageArray[2].alt = "The second image">
and load up the actual image data:
Code:
<!--- now we need to loop through each of our files for additional information --->
<CFLOOP from="1" to="#ArrayLen(aryImageArray)#" index="whichImage">
<!--- read the file as binary data... "expandpath()" assumes that the image is in the same directory as the current .cfm file... adjust as needed --->
<CFFILE action="READBINARY" file="#expandpath(aryImageArray[whichImage].filename)#" variable="rawBinaryImage">
<!--- now turn that binary data into character (ASCII) data by running it through the Base64 encoder --->
<CFSET base64data = toBase64(rawBinaryImage)>
<!--- and insert a linefeed every 72 character (otherwise we'll overrun the mail client's maximum line length and it'll get truncated, meaning it won't be able to make sense of the image data) --->
<CFSET lfPos = 72>
<CFLOOP condition="lfPos LT Len(base64data)">
<CFSET base64data = Insert(Chr(10),base64data,lfPos)>
<CFSET lfPos = lfPos + 73>
</CFLOOP>
<!--- save the character image data to our array --->
<CFSET aryImageArray[whichImage].base64data = charImage>
<!--- while we're at it, create a unique ID for the image. You can change this so that your ID's are of your chosing, if you wish... but this is easier if you're going to be making updates. The caveat being that CreateUUID() doesn't necessarily make a universally compatible ID for all mail clients... but it works great for Outlook --->
<CFSET aryImageArray[whichImage].contentid = CreateUUID()>
</CFLOOP>
Now the fun part... the actual email message. What you're going to be doing is creating a "multipart" email. To do this, you need to use CFMAILPARAM to set up multipart boundaries for each section of the email (plain text content, HTML content, and your images). This is not always an exact science... or, maybe it is... it generally takes a try or two to get it right.
Code:
<!--- first, decide on a boundary marker. The easiest way I've found to do this is by calling CreateUUID() again. Your mileage may vary. Basically what the boundary marker says to the mail client is "this is where the previous 'part' of this multipart email ends and a new one begins"... so you want it to be incredibly unique... something that isn't likely to appear ordinarily in your body content of image data. --->
<CFSET sImageBoundary = CreateUUID()>
<!--- now start our actual CFMAIL tag and include a couple of CFMAILPARAM tags to set up the proper MIME header and the content description (including the boundary marker definition --->
<CFMAIL to="joe_smith@mycompany.com" from="you@yourcompany.com" subject="Summer Newsletter">
<cfmailparam name="mime-version" value="1.0">
<cfmailparam name="content-type" value="multipart/alternative; boundary=""#sImageBoundary#""">
--#sImageBoundary#
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
<!--- here's the plain text version that the user would get if their mail client doesn't understand HTML emails... you can put whatever content you want here. --->
This is the text version.
Sorry... you don't get any images.
Be sure to visit our webpage for the full experience.
--#sImageBoundary#
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: 7bit
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<!--- here's the HTML version --->
<html>
<body>
<p><font face="Arial" size="-1">Here is the first image</font><br />
<!--- we reference the images we're going to attach with a standard IMG tag, but the source needs to be an inline reference... adding "cid:" at the begining of the reference, and referencing the image by it's content-id is how we do this. The IMG tags can go within any standard HTML element... like a table cell, or whatever. --->
<DIV><img SRC="cid:#aryImageArray[1].contentid#" height="#aryImageArray[1].height#" width="#aryImageArray[1].width#" align="#aryImageArray[1].align#" alt="#aryImageArray[1].alt#" /></DIV></p>
<p> </p>
<p><font face="Arial" size="-1">Here is the second image</font><br />
<DIV><img SRC="cid:#aryImageArray[2].contentid#" height="#aryImageArray[2].height#" width="#aryImageArray[2].width#" align="#aryImageArray[2].align#" alt="#aryImageArray[2].alt#" /></DIV></p>
</body>
</html>
<!--- now we "attach" the images. We do this by declaring more boundaries, setting the content header for each one, and then outputting the character image data --->
<CFLOOP from="1" to="#ArrayLen(aryImageArray)#" index="whichImage">
--#sImageBoundary#
Content-Type: #aryImageArray[whichImage].contenttype#
Content-ID: <#aryImageArray[whichImage].contentid#>
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename="C:\WINDOWS\TEMP\#aryImageArray[whichImage].filename#"
#aryImageArray[whichImage].base64data#
</CFLOOP>
--#sImageBoundary#
</CFMAIL>
The newsletter has been sent at <CFOUTPUT>#now()#</CFOUTPUT>
If you have any problems, let me know.
-Carl