Skip to main content
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Chris Scott
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Chris Scott

Creating An Image Thumbnail Service Using Email Yak Attachments And ColdFusion

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel