One of the many projects I'm currently working on is some code
to delay sending of e-mails until a specified window of time. We're
generating some report data for clients during the offhours,
but the clients want the results e-mailed no earlier than 8am.
Our reports often contain images or other attachments that need
to be included e-mails. One issue I really don't like about the
implementation of CFMAIL in CFMX is that it requires attachments
to be written to disk before you can send the mail. This means
if I want to use the CFMAIL tag to deliver delayed e-mails, I
would to manage attachments until I'm sure the message is delivered.
I don't like that solution, so I set out to see if there might be
other ways of generating attachments from binary data in memory.
This lead me to researching the JavaMail API—which is the API
that CFMAIL uses behind the scenes.
I quickly learned that even CFMX v7.02 still uses JavaMail
v1.3.1—which is an older version of the API. One of the
issues in v1.3.1 is that it does not include any classes
for taking a binary stream from memory and converting to an
attachment. It comes with a FileDataSource class—which will
read in a file and convert it to the correct data source. This
might be the reason that Macromedia/Adobe requires the file to
be written to disk.
In order for you to place an attachment in the e-mail, you must
get the binary data into a DataSource. All of the solutions
I found on the web, suggested writing a ByteArrayDataSource
class to handle converting an in-memory binary object so that
you could place it in the attachment, but I really didn't want
to introduce a 3rd party dependency if I didn't have to.
After doing some digging of all the available classes included
with CFMX 7, I found the org.apache.axis.attachments.OctetStreamDataSource
class which I thought might get me what I needed—and it turns
out I was write. Utilizing the OctetStreamDataSource I was able
to convert any binary data stored in a CF variable into the correct
DataSource that the JavaMail API requires. This means I can now
use tags like CFDOCUMENT or CFCHART to generate data on the fly and
send it in an e-mail without ever writing it to disk.
Below is some source code that will show you how to use the JavaMail
API to send a multipart message that contains:
-
Plain Text
-
HTML (with embedded images)
-
A file attachment from a file on disk
-
A file attachment from binary data stored in memory
I've tried to comment each line to fully explain whats going on.
This is the exact code I was using from my test script. Just
remember to update the variables in the "config" section to
use your mail server.
If you run the code below, it will actually send the e-mail and
output the SMTP message envelope. The SMTP message envelope
could be written to most SMTP server's queue or pickup folder to
be delivered. For example, you could write this file to IIS's
SMTP service's "pickup" folder and the SMTP service would automatically
queue the message for delivery. You wouldn't even have to connect
to the SMTP server.
Hopefully somebody else finds this code useful!
<!---// create a PDF document and save it to a variable //--->
<cfdocument format="pdf" name="binPdf">
<h1>
A PDF Document
</h1>
<p>
This is a document rendered by the cfdocument tag
at <cfoutput>#dateFormat(now(), "mmmm dd, yyyy")#
#lCase(timeFormat(now(), "h:mmtt"))#</cfoutput>.
</p>
<table width="50%" border="2" cellspacing="2" cellpadding="2">
<tr>
<td><strong>Name</strong></td>
<td><strong>Role</strong></td>
</tr>
<tr>
<td>Bill</td>
<td>Lead</td>
</tr>
<tr>
<td>Susan</td>
<td>Principal Writer</td>
</tr>
<tr>
<td>Adelaide</td>
<td>Part Time Senior Writer</td>
</tr>
<tr>
<td>Thomas</td>
<td>Full Time for 6 months</td>
</tr>
<tr>
<td>Michael</td>
<td>Full Time for 4 months</td>
</tr>
</table>
</cfdocument>
<!---// save the file to the following path and file //--->
<cfset sFilename = expandPath('./test-file.pdf') />
<!---// write the file to disk //--->
<cffile action="write" file="#sFilename#" output="#binPdf#">
<!---// create a png chart and save it to a variable //--->
<cfchart name="binChart" format="png" font="arialunicodeMS" xaxistitle="Month" yaxistitle="Degrees Celsius" showlegend="yes">
<cfchartseries type="line" serieslabel="Europe">
<cfloop index="i" list="Apr,May,Jun,Jul,Aug,Sep">
<cfchartdata item="#i#" value="#RandRange(12,42)#">
</cfloop>
</cfchartseries>
<cfchartseries type="line" serieslabel="USA">
<cfloop index="j" list="Apr,May,Jun,Jul,Aug,Sep">
<cfchartdata item="#j#" value="#RandRange(12,42)#">
</cfloop>
</cfchartseries>
</cfchart>
<cfscript>
// config
sMailServer = "mail.yourserver.com";
sUsername = "";
sPassword = "";
sSubject = "Using the JavaMail API!";
sAddyTo = "to@yourcompany.com";
sAddyFrom = "from@yourcompany.com";
// set javamail properties
oProps = createObject("java", "java.util.Properties").init();
oProps.put("javax.mail.smtp.host", sMailServer);
// get static recipient types
oRecipientType = createObject("java", "javax.mail.Message$RecipientType");
// create the session for the smtp server
oMailSession = createObject("java", "javax.mail.Session").getInstance(oProps);
// create a new MIME message
oMimeMessage = createObject("java", "javax.mail.internet.MimeMessage").init(oMailSession);
// create the to and from e-mail addresses
oAddressFrom = createObject("java", "javax.mail.internet.InternetAddress").init(sAddyFrom);
oAddressTo = createObject("Java", "javax.mail.internet.InternetAddress").init(sAddyTo);
// build message
// set who the message is from
oMimeMessage.setFrom(oAddressFrom);
// add a recipient
oMimeMessage.addRecipient(oRecipientType.TO, oAddressTo);
// set the subject of the message
oMimeMessage.setSubject(sSubject);
// create multipart message: only needed if you're including both plain/text and html
// or using attachments
oMimeMultipart = createObject("java", "javax.mail.internet.MimeMultipart").init();
// specifies that the message contains both inline text and html, this is so that
// images given a cid will show up when rendered by the e-mail client
oMimeMultipart.setSubType("related");
// create plain text multipart
oPlainText = createObject("java", "javax.mail.internet.MimeBodyPart").init();
// create the plain/text for the message
oPlainText.setText("You like using JavaMail in CFMX.");
// add the body part to the message
oMimeMultipart.addBodyPart(oPlainText);
// create html text multipart
oHtml = createObject("java", "javax.mail.internet.MimeBodyPart").init();
// add the html content (the setText() method shortcut/only works for "plain/text")
oHtml.setContent(
"<html><head><title>HTML E-mail</title></head><body>"
& "<h1>You like using JavaMail in CFMX.</h1>"
& "<p><img src=cid:23abc@pc27 /></p>"
& "</body></html>",
"text/html"
);
// add the body part to the message
oMimeMultipart.addBodyPart(oHtml);
// attach an inline binary object
att = createObject("java", "javax.mail.internet.MimeBodyPart").init();
// create an octet stream out of the binary file
os = createObject("java", "org.apache.axis.attachments.OctetStream").init(binPdf);
// we now convert the octet stream into the required data source. using an octet stream
// allows us pass in any binary data as a file attachment
osds = createObject("java", "org.apache.axis.attachments.OctetStreamDataSource").init("", os);
// initialize the data handler using the data source
dh = createObject("java", "javax.activation.DataHandler").init(osds);
// pass in the binary object to the message--javamail will handle the encoding
// based on the headers
att.setDataHandler(dh);
// define this binary object as a PDF
att.setHeader("Content-Type", "application/pdf");
// make sure the binary data gets converted to base64 for delivery
att.setHeader("Content-Transfer-Encoding", "base64");
// specify the binary object as an attachment
att.setHeader("Content-Disposition", "attachment");
// define the name of the file--this is what the filename will be in the e-mail client
att.setFileName("test-binary_var.pdf");
// add the body part to the message
oMimeMultipart.addBodyPart(att);
// attach an inline binary object
png = createObject("java", "javax.mail.internet.MimeBodyPart").init();
// create an octet stream out of the binary file
os = createObject("java", "org.apache.axis.attachments.OctetStream").init(binChart);
// we now convert the octet stream into the required data source. using an octet stream
// allows us pass in any binary data as a file attachment
osds = createObject("java", "org.apache.axis.attachments.OctetStreamDataSource").init("", os);
// initialize the data handler using the data source
dh = createObject("java", "javax.activation.DataHandler").init(osds);
// pass in the binary object to the message--javamail will handle the encoding
// based on the headers
png.setDataHandler(dh);
// define this binary object as a PNG
png.setHeader("Content-Type", "image/png");
// make sure the binary data gets converted to base64 for delivery
png.setHeader("Content-Transfer-Encoding", "base64");
// specify the binary object as an attachment
png.setHeader("Content-Disposition", "attachment");
// this is the cid you referenced in the html body
png.setHeader("Content-ID","<23abc@pc27>");
// define the name of the file--this is what the filename will be in the e-mail client
png.setFileName("chart-binary_var.png");
// add the body part to the message
oMimeMultipart.addBodyPart(png);
// build attachment - file
fatt = createObject("java", "javax.mail.internet.MimeBodyPart").init();
// create a data source directly from a file on the OS
fds = createObject("java", "javax.activation.FileDataSource").init(sFilename);
// initialize the data handler using the data source
dh = createObject("java", "javax.activation.DataHandler").init(fds);
// pass in the file object to the message--javamail will handle the encoding
// based on the headers
fatt.setDataHandler(dh);
// define this file as a PDF
fatt.setHeader("Content-Type", "application/pdf");
// make sure the file gets converted to base64 for delivery
fatt.setHeader("Content-Transfer-Encoding", "base64");
// specify the file as an attachment
fatt.setHeader("Content-Disposition", "attachment");
// define the name of the file--get the file name from original file
fatt.setFileName(fds.getName());
// add the body part to the message
oMimeMultipart.addBodyPart(fatt);
// place all the multi-part sections into the body of the message
oMimeMessage.setContent(oMimeMultipart);
// in this section we'll build the message into a string. you could dump
// the string to a file in most SMTP server's queue file for delivery
// this is exactly what would be pass to the SMTP server
// create a bytearray for output
outStream = createObject("java", "java.io.ByteArrayOutputStream");
// create a budder for the output stream
outStream.write(repeatString(" ", 1024).getBytes());
// save the contents of the message to the output stream
oMimeMessage.writeTo(outStream);
// save the contents of the message to the sMailMsg variable
sMailMsg = outStream.toString();
// reset the output stream (for stability)
outStream.reset();
// close the output stream
outStream.close();
// create a transport to actually send the message via SMTP
oTransport = oMailSession.getTransport("smtp");
// connect to the SMTP server using the parameters supplied; use
// a blank username and password if authentication is not needed
oTransport.connect(sMailServer, sUsername, sPassword);
// send the message to all recipients
oTransport.sendMessage(oMimeMessage, oMimeMessage.getAllRecipients());
// close the transport
oTransport.close();
</cfscript>
<cfcontent reset="true" /><cfoutput>#htmlCodeFormat(trim(sMailMsg))#</cfoutput>
<cfabort />
19 Comments
Comments for this entry have been disabled.