A few weeks back, a reader came to me with an interesting question. He knew how to use ColdFusion to work with email, cfpop and cfmail make that easy enough. But he also wanted the ability to download mail to the user's desktop. That should be easy, right? Take the text and just stream it to the user. But that's not what he meant. He wanted the ability to download a .eml file. This would be supported in the end user's desktop mail client like Outlook or Thunderbird. I did some research into this and found out something interesting. Java itself has the ability to generate dynamic eml files. Here's a quick demo I created. It isn't fully feature (and doesn't support attachments at all), but works well.
To begin, let's simply get the email and display a nice list. We will use a download link to provide a way to get a particular message.
<cfpop action="getHeaderOnly" name="messages"
server="pop.aol.com" username="bob.camden@aol.com"
password="nope" > <h2>Mail Test</h2> <cfif messages.recordCount gt 0> <table width="100%" border="1">
<tr>
<th>From</th>
<th>Subject</th>
<th>Sent</th>
<td> </td>
</tr>
<cfoutput query="messages">
<tr>
<td>#from#</td>
<td>#subject#</td>
<td>#dateFormat(date)# #timeFormat(date)#</td>
<td><a href="?download=#urlEncodedFormat(uid)#">Download</a></td>
</tr>
</cfoutput>
</table> <cfelse> Sorry - no mail in the account. </cfif>
Nothing crazy there. Do note though that my download link makes use of the UID value. We will be fetching an individual email later and while cfpop allows us to get a message by number too, a UID is normally prefered. If you fetch by number, and the mailbox changed, then your results will not be correct. (Note: In my testing I ran across an issue where a UID failed to get a message but a number did not. I'll be doing more testing to see if I can get a precise bug to report.) Here's the result in my usual, lovely, design:
Ok, so what happens when you click? I begin by getting just the one message.
<cfpop action="getAll" name="message"
server="pop.aol.com" username="bob.camden@aol.com"
password="foo"
uid="#url.download#"
>
Obviously, the server/username/password values would be dynamic. Now, let's get into the Java.
m = createObject("java", "javax.mail.internet.MimeMessage").init(s); m.setFrom(createObject("java", "javax.mail.internet.InternetAddress").init(message.from));
m.setSubject(message.subject); if(len(message.htmlBody)) m.setText(message.htmlBody, "UTF-8", "html");
else m.setText(message.body); toField = createObject("java", "javax.mail.Message$RecipientType").TO; m.setRecipients(toField, [createObject("java", "javax.mail.internet.InternetAddress").init(message.to)]); tempFile = getTempFile(getTempDirectory(), "msg");
fis = createObject("java", "java.io.FileOutputStream").init(tempFile);
m.writeTo(fis);
fis.close();
</cfscript>
<cfscript>
p = createObject("java", "java.util.Properties").init();
s = createObject("java", "javax.mail.Session").getInstance(p);
Even if you've never seen these classes before, you can take a good guess as to what is happening. I create a blank message and then populate the various values. Note that I do only HTML or Plaintext, whereas a real message could contain both. I also don't handle CC or BCC. That should be trivial to add to the picture. Finally, let's download this beast:
<cfheader name="Content-disposition" value="attachment;filename=#message.subject#.eml">
<cfcontent type="image/jpeg" file="#tempFile#">
And here is the file opened in Windows Mail:
And the same in Thunderbird:
Pretty interesting, eh? My thanks to Lawtrac and Jim Harris for spurring all of this. Here is the complete code template. You will need to change the server information.
<cfif structKeyExists(url, "download")>
<cfpop action="getAll" name="message"
server="pop.aol.com" username="bob.camden@aol.com"
password="foo"
uid="#url.download#"
> <cfscript>
p = createObject("java", "java.util.Properties").init();
s = createObject("java", "javax.mail.Session").getInstance(p); m = createObject("java", "javax.mail.internet.MimeMessage").init(s); m.setFrom(createObject("java", "javax.mail.internet.InternetAddress").init(message.from));
m.setSubject(message.subject); if(len(message.htmlBody)) m.setText(message.htmlBody, "UTF-8", "html");
else m.setText(message.body); toField = createObject("java", "javax.mail.Message$RecipientType").TO; m.setRecipients(toField, [createObject("java", "javax.mail.internet.InternetAddress").init(message.to)]); tempFile = getTempFile(getTempDirectory(), "msg");
fis = createObject("java", "java.io.FileOutputStream").init(tempFile);
m.writeTo(fis);
fis.close();
</cfscript> <cfheader name="Content-disposition" value="attachment;filename=#message.subject#.eml">
<cfcontent type="image/jpeg" file="#tempFile#"> <cfabort>
</cfif> <cfpop action="getHeaderOnly" name="messages"
server="pop.aol.com" username="bob.camden@aol.com"
password="foo" > <h2>Mail Test</h2> <cfif messages.recordCount gt 0> <table width="100%" border="1">
<tr>
<th>From</th>
<th>Subject</th>
<th>Sent</th>
<td> </td>
</tr>
<cfoutput query="messages">
<tr>
<td>#from#</td>
<td>#subject#</td>
<td>#dateFormat(date)# #timeFormat(date)#</td>
<td><a href="?download=#urlEncodedFormat(uid)#">Download</a></td>
</tr>
</cfoutput>
</table> <cfelse> Sorry - no mail in the account. </cfif>
<!--- I really, really, REALLY hope we fix this for Zeus --->
<cfset javaSystem = createObject("java", "java.lang.System") />
<cfset jProps = javaSystem.getProperties() />
<cfset jProps.setProperty("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory") />
<cfset jProps.setproperty("mail.pop3.port",995) />
<cfset jProps.setProperty("mail.pop3.socketFactory.port", 995) />