Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with:

Creating An Image Thumbnail Service Using Email Yak Attachments And ColdFusion

By Ben Nadel on
Tags: ColdFusion

Yesterday, I started looking at the Email Yak SaaS (Software as a Service) platform for creating bidirectional email communication in web applications. I was really excited by the way email complexities were nicely abstracted behind a JSON (JavaScript Object Notation) API which allowed for seamless communication between a user's email client and your web application's public API. To further the exploration, I wanted to take a look at how attachments were handled by the Email Yak SMTP proxy. As a context for exploration, I'm going to use ColdFusion to create a simple Image Thumbnailing service in which images sent to a given email address would be returned, via email, as thumbnails.

For this demo, I wanted to create a new email address that would be used specifically for the image thumbnail service. In yesterday's demo, I explained that Email Yak email addresses could be activated in their online Sandbox, their API, or by simply using a given email address to send outbound emails. For this demo, let's take a look at activating a new email address using the Email Yak API.

In the following code, I'm going to activate the address "thumbnail@bennadel.simpleyak.com". Notice that I am still using the convenience testing domain, "simpleyak.com" as a way to get up and running without having to have an existing domain or make any MX changes to my DNS records.

  • <!---
  • Define the base API url. All Email Yak resources will be based
  • off of this value.
  • --->
  • <cfset apiUrl = "https://api.emailyak.com/v1/#application.apiKey#" />
  •  
  •  
  • <!---
  • We are going to be registering a new email address for the
  • Image Thumbnail feature. We could simply do this on the Email Yak
  • sandbox; but, I wanted to try this in script format for practice.
  • --->
  • <cfset addressPost = {} />
  •  
  • <!--- Define the new email address for our thumbnailing service. --->
  • <cfset addressPost[ "Address" ] = "thumbnail@bennadel.simpleyak.com" />
  •  
  • <!---
  • Let's define an alternate callback URL to the one being used by
  • the domain itself. This way, we can have specific end points for
  • different features.
  •  
  • NOTE: This is optional - if not supplied, Email Yak will simply
  • use the callback URL defined for the parent domain.
  • --->
  • <cfset addressPost[ "CallbackURL" ] = "http://...../thumbnail.cfm" />
  •  
  • <!---
  • Set the given inbox to use PUSH alerts for new emails. This way,
  • our callback will be automatically invoked when someone needs an
  • image thumbnailed.
  • --->
  • <cfset addressPost[ "PushEmail" ] = "true" />
  •  
  •  
  • <!---
  • Post the request to the Email Yak API. Notice that we must post
  • the content as JSON and that the API URL includes the API key as
  • part of the resource definition.
  • --->
  • <cfhttp
  • result="apiResponse"
  • method="post"
  • url="#apiUrl#/json/register/address/">
  •  
  • <!--- Set the content type to be JSON. --->
  • <cfhttpparam
  • type="header"
  • name="content-type"
  • value="application/json; charset=utf-8"
  • />
  •  
  • <!--- Specify that we can accept JSON as well. --->
  • <cfhttpparam
  • type="header"
  • name="accept"
  • value="application/json; charset=utf-8"
  • />
  •  
  • <!--- Post the new email address registration. --->
  • <cfhttpparam
  • type="body"
  • value="#serializeJSON( addressPost )#"
  • />
  •  
  • </cfhttp>
  •  
  •  
  • <!--- Dump out the response. --->
  • <cfdump
  • var="#apiResponse#"
  • label="Email Yak API Response"
  • />
  •  
  • <br />
  •  
  • <!--- Deserialize and dump out the body. --->
  • <cfdump
  • var="#deserializeJSON( toString( apiResponse.fileContent ) )#"
  • label="API Response Data"
  • />

In addition to passing in the email address to be activated, notice that we can also pass in a specific callback URL to be used exclusively for this inbox. This means that we can have a default callback URL for our domain - bennadel.simpleyak.com - plus, callbacks for individual inboxes owned by said domain.

The code outputs the HTTP response to a log file; but, it's not terribly interesting. So, just trust me that this comes back with a 200 OK status code response. And, once the email address has been activated, we can start to send in emails.

To do this, I first went to Flickr to find something fun in their Creative Commons library. I wanted to use multiple images to make sure that multiple attachments could be processed at a single time. For this demo, I found this woman wearing a nerdy tee shirt about Content Management Systems, or CMS (Image 1, Image 2).

Once I had the images, I attached them to an email and sent them off to the activated Email Yak address. When sending the email, I put the dimensions of the bounding box to be used in the resize within the outgoing email's subject line.


 
 
 

 
 Sending image attachments to Email Yak in order to get them thumbnailed by ColdFusion. 
 
 
 

As you can see, this outgoing email has two image attachments that need to be resized to a box no greater than 250 x 250. I'll be using a Regular Expression to extract these dimensions, so it doesn't really matter what language or format you use so long as you have one number (width) followed by a second number (height).

This email will go out to the Email Yak SMTP proxy which will parse it and then post it to the callback URL we defined when we activated the "thumbnail" email address. The callback will pull down the images, thumbnail them, and then send them back to the user using the Email Yak JSON (JavaScript Object Notation) API. We'll take a look at the code in a minute, but for the time being, let's take a look at the email that the user gets back as a response:


 
 
 

 
 ColdFusion created image thumbnails and then sent them back using the Email Yak JSON API. 
 
 
 

As you can see, the two images, which were originally about 300Kb, are now about 8Kb in size since having been turned into thumbnails. Notice that the file names and file extensions have been maintained during the entire process.

Now that we see what's supposed to happen, let's take a look at the ColdFusion code behind the thumbnail process. In the following workflow, the incoming email data arrives as an HTTP POST from the Email Yak SaaS (Software as a Service) platform. The POST contains, among other things, a list of URLs that represent the attachments. All attachements are stored securely on the Email Yak servers. We use ColdFusion to load the image URLs locally as image objects; we resize them; and then, we send them back to the user as Base64-encoded thumbnail images.

  • <!---
  • Increase the processing time since we are dealing with image
  • manipulation. This won't be wicked fast (perhaps).
  • --->
  • <cfsetting requesttimeout="#(3 * 60)#" />
  •  
  • <!---
  • Include any UDFs that we need for dealing with the Email Yak API.
  • For example, we need to create a MD5 Hash Digest of the incoming
  • request in order to proove its origin.
  • --->
  • <cfinclude template="./udf.cfm" />
  •  
  •  
  • <!--- Define a log file for local CFDump'ing. --->
  • <cfset logFile = (
  • getDirectoryFromPath( getCurrentTemplatePath() ) &
  • "log.htm"
  • ) />
  •  
  • <!--- Define a scratch file for image manipulation. --->
  • <cfset scratchFile = (
  • getDirectoryFromPath( getCurrentTemplatePath() ) &
  • "scratch.tmp"
  • ) />
  •  
  •  
  • <!---
  • Define the base API url. All Email Yak resources will be based
  • off of this value.
  • --->
  • <cfset apiUrl = "https://api.emailyak.com/v1/#application.apiKey#" />
  •  
  •  
  • <!---
  • Since we are dealing with an POST that may not be from a known
  • source, let's wrap the processing in a Try/Catch where we can
  • handle errors more appropriately.
  • --->
  • <cftry>
  •  
  •  
  • <!--- Get a reference to the post headers. --->
  • <cfset httpHeaders = getHttpRequestData().headers />
  •  
  •  
  • <!--- Make sure the Email Yak auth key exists. --->
  • <cfif !structKeyExists( httpHeaders, "X-Emailyak-Post-Auth" )>
  •  
  • <!--- We can't authorize with out the right headers. --->
  • <cfthrow
  • type="NotAuthorized"
  • message="Authorization failed."
  • detail="Request cannot be authorized without the [X-Emailyak-Post-Auth] HTTP request header."
  • />
  •  
  • </cfif>
  •  
  • <!--- Get the secure digest of the content using our API key. --->
  • <cfset hexDigest = toHexDigest(
  • application.apiKey,
  • toString( getHttpRequestData().content )
  • ) />
  •  
  • <!--- Check to make sure the digests match. --->
  • <cfif (httpHeaders[ "X-Emailyak-Post-Auth" ] neq hexDigest)>
  •  
  • <!--- The digest does not match - source may be malicious. --->
  • <cfthrow
  • type="NotAuthorized"
  • message="Authorization failed."
  • detail="The provided message digest did not match the calculated digest."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to make sure the content is valid JSON. --->
  • <cfif !isJSON( toString( getHttpRequestData().content ) )>
  •  
  • <!--- Can't process this data. --->
  • <cfthrow
  • type="BadRequest"
  • message="Request content must be valid JSON."
  • detail="The content of the post was not valid JSON."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- Parse the JSON content. --->
  • <cfset email = deserializeJSON(
  • toString( getHttpRequestData().content )
  • ) />
  •  
  •  
  • <!---
  • Check to see if there are any attachments with this email
  • that we need to thumbnail.
  • --->
  • <cfif !structKeyExists( email, "attachments" )>
  •  
  • <!--- No images to process. --->
  • <cfthrow
  • type="BadRequest"
  • message="Request must contain at least one attachment."
  • detail="There are no attachments found in this email. At least one attachment must be provided."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- Log the email data that came through. --->
  • <cfdump
  • var="#email#"
  • output="#logFile#"
  • hide="Headers"
  • format="html"
  • label="Email Yak Data"
  • />
  •  
  •  
  • <!---
  • Now that we have an email, let's see if we can respond to
  • the user with the thumbnailed version of any images that
  • they attached.
  • --->
  •  
  • <!--- Create the email post. --->
  • <cfset emailPost = {} />
  •  
  • <!---
  • We'll send it back from the thumbnail address. Since the
  • user sent it to THIS address, we can just use the inbound
  • TO to define the outbound FROM.
  • --->
  • <cfset emailPost[ "FromAddress" ] = email.toAddress />
  •  
  • <!--- Set the from name. --->
  • <cfset emailPost[ "FromName" ] = "Thumbnail Service" />
  •  
  • <!---
  • When sending, send back to the user who sent the incoming
  • email message.
  • --->
  • <cfset emailPost[ "ToAddress" ] = email.fromAddress />
  •  
  • <!--- Set the subject. --->
  • <cfset emailPost[ "Subject" ] = "Your thumbnails have been created" />
  •  
  • <!--- Set a simple email body. --->
  • <cfset emailPost[ "HtmlBody" ] = "See attached thumbnails." />
  • <cfset emailPost[ "TextBody" ] = "See attached thumbnails." />
  •  
  • <!--- Create an array to hold our attachments. --->
  • <cfset emailPost[ "Attachments" ] = [] />
  •  
  • <!---
  • When it comes to the attachments, we are going to use the
  • subject line to define the bounding box for our thumbnails.
  • We are expecting something in the form of 150x150 in which
  • the first value is the width and the second value is the
  • height.
  • --->
  • <cfset dimensions = reMatch( "\d+", email.subject ) />
  •  
  • <!--- In case we didn't find any matches, let's param them. --->
  • <cfparam name="dimensions[ 1 ]" type="numeric" default="150" />
  • <cfparam name="dimensions[ 2 ]" type="numeric" default="150" />
  •  
  • <!---
  • Now, let's loop over the attachments in the email to load
  • them locally and create thumbnails of them. The attachments
  • are represented as secure, remotely available URLs hosted
  • by the Email Yak platform.
  • --->
  • <cfloop
  • index="imageUrl"
  • array="#email.attachments#">
  •  
  • <!--- Create a ColdFusion image of the attachment. --->
  • <cfset image = imageNew( imageUrl ) />
  •  
  • <!--- Turn on anti-aliasing for better resizing. --->
  • <cfset imageSetAntialiasing( image, "on" ) />
  •  
  • <!---
  • Scale the image to be the appropriate size. For this,
  • we will be using the dimensions provided in the email
  • subject in which the first value is width and the
  • second value is height.
  • --->
  • <cfset imageScaleToFit(
  • image,
  • dimensions[ 1 ],
  • dimensions[ 2 ]
  • ) />
  •  
  • <!---
  • Write the image to disk as a base64 encoding. When we
  • post the file to the Email Yak API, it has to go across
  • in text/JSON format; as such, we need to sent it over
  • as a flat text value.
  • --->
  • <cfset imageWriteBase64(
  • image,
  • scratchFile,
  • listLast( imageUrl, "." )
  • ) />
  •  
  • <!--- Create an attachment entry. --->
  • <cfset attachment = {} />
  • <cfset attachment[ "Filename" ] = getFileFromPath( imageUrl ) />
  • <cfset attachment[ "Content" ] = fileRead( scratchFile ) />
  •  
  • <!--- Att the attachments to our outbound collection. --->
  • <cfset arrayAppend( emailPost.attachments, attachment ) />
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • Send out the email using the Email Yak API. Notice that we
  • must post the content as JSON and that the API URL includes
  • the API key as part of the resource definition.
  • --->
  • <cfhttp
  • result="apiResponse"
  • method="post"
  • url="#apiUrl#/json/send/email/">
  •  
  • <!--- Set the content type to be JSON. --->
  • <cfhttpparam
  • type="header"
  • name="content-type"
  • value="application/json; charset=utf-8"
  • />
  •  
  • <!--- Specify that we can accept JSON as well. --->
  • <cfhttpparam
  • type="header"
  • name="accept"
  • value="application/json; charset=utf-8"
  • />
  •  
  • <!--- Post the email. --->
  • <cfhttpparam
  • type="body"
  • value="#serializeJSON( emailPost )#"
  • />
  •  
  • </cfhttp>
  •  
  •  
  • <!--- Log the response. --->
  • <cfdump
  • var="#deserializeJSON( toString( apiResponse.fileContent ) )#"
  • output="#logFile#"
  • format="html"
  • label="API Response"
  • />
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <!--- Catch any unspecified errors. --->
  • <cfcatch>
  •  
  • <!--- Log exception. --->
  • <cfdump
  • var="#cfcatch#"
  • output="#logFile#"
  • format="html"
  • label="CFCATCH - Unexpected Exception"
  • />
  •  
  • </cfcatch>
  •  
  • </cftry>

NOTE: The UDF library being included at the top contains only the toHexDigest() method that was used in my previous Email Yak blog post. I will not be showing it in this blog post.

The majority of this code handles the validation and parsing of the incoming Email Yak request. Once the incoming JSON (JavaScript Object Notation) data has been parsed, loading and resizing the images takes just a few lines of code. But, since the Email Yak API uses JSON as its transport mechanism, we can't simply upload the thumbnails as binary values (as we might when sending an email using ColdFusion's CFMail and CFMailParam tags). As such, the thumbnail attachments have to be added as flattened, Base64-encoded strings within the outgoing JSON data.

During this lifecycle, the incoming Email Yak data and API response gets written to a log file:


 
 
 

 
 Email Yak sends email attachments as secure URLs in the JSON POST data.  
 
 
 

Notice again that the email attachments are stored securely and conveniently on the Email Yak file servers. The free account (which is what I'm testing with) has a 1GB storage limit. Every other paid plan allows for unlimited attachment storage.

All in all, Email Yak really makes all this email communication stuff incredibly easy. Once you have cool tools like this, the fun part is trying to figure out where they can be integrated with your existing software to make it betters.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.