Sending Mail And Monitoring Bounce Backs With ColdFusion And Postmark
After I found out that you cannot use a GMail account to monitor bounce backs with the CFMail FailTo attribute, I wanted to take a look at Postmark. Postmark is an API-based service that helps web applications send email and track bounce backs. The service charges for email delivery - $1.50 for every 1,000 emails; but, you get 1,000 free credits when you sign up (no billing information required), so I figured it would be worth trying.
The Postmark API is simple and straightforward - they have one API for sending emails and one API for monitoring bounce backs. While I am not yet sure how to most effectively leverage the bounce back API methods, it took all of about 3 minutes to get some sample code up and running. The API is based on JSON (Javascript Object Notation) data packets that define your email parameters. Using CFHTTP, all you have to do it post your API key and your serialized JSON data structures.
In the following demo code, I'm gonna use CFHTTP to send an email to an invalid email address using Postmark.
<!---
Define the outgoing email properties. We are going to be using
CFHTTP post post the JSON (Javascript Object Notation) version
of these propreties to the PostMark API.
--->
<cfset emailSettings = {
to = "nat@natalie-gets-way-naughty.com",
from = "ben+from@bennadel.com",
subject = "PostMark Bounce Back Testing",
htmlBody = "<strong>All your emails are belong to us!</strong>"
} />
<!--- Post the email to the PostMark server. --->
<cfhttp
result="post"
method="post"
url="http://api.postmarkapp.com/email">
<!---
Alert the server that the we can accept JSON as the type of
data returned in the response.
--->
<cfhttpparam
type="header"
name="accept"
value="application/json"
/>
<!---
Alert the server that the email content will be serialized
in the post body as JSON text.
--->
<cfhttpparam
type="header"
name="content-type"
value="application/json"
/>
<!--- Define the API key to authorize post. --->
<cfhttpparam
type="header"
name="X-Postmark-Server-Token"
value="#request.apiKey#"
/>
<!---
Post the serialized JSON email properties as the HTTP
message body.
--->
<cfhttpparam
type="body"
value="#serializeJSON( emailSettings )#"
/>
</cfhttp>
<!--- Output the post response (returned in JSON format). --->
<cfdump
var="#deserializeJSON( post.fileContent )#"
label="PostMark CFHTTP Response"
/>
As you can see, I first define my email settings in a ColdFusion structure. Then, when I invoke the Postmark API, I simply serialize the email settings using the serializeJSON() method and post the resultant JSON string as the CFHTTP body content. The Postmark API then returns a JSON response, which I can deserialize using deserializeJSON(). When we run the above code, we get the following API response:
Postmark has never seen that email address before - nat@natalie-gets-way-naughty.com - so it can offer us no insight at this time. As far as we know, Postmark has successfully sent out the given email. Of course, since we know that this email address is not valid, Postmark will eventually get a hard bounce back. Once Postmark has the hard bounce back on file, it can start to give us some more comprehensive feedback at "Send" time. If we wait a minute and re-execute the code above, we'll get the following output:
As you can see, the API call still executed without error; but, this time, Postmark knows that the given email address has previously caused a hard bounce back and will let us know that the given email address is not active. I am not 100% sure if this feedback is purely for user-insight; or, if it means that the given email was not sent.
Using the Postmark API to check for bounce backs is just as easy; all we need is the API key, some filtering, and the CFHTTP tag. It looks like Postmark keeps all bounce backs on file; as such, when you ask for bounce back information, you have to request it in a quasi-paginated manner using a page size and offset index. When doing so, the most recent bounce backs are listed first (offset zero).
In the following code, we are going to check to see if the above email address - nat@natalie-gets-way-naughty.com - caused a bounce back:
<!---
Get the bounce-back information from the PostMark server. When
doing this, we have a number of possible filters. For our use,
we're just gonna filters on email LIKE'ness.
--->
<cfhttp
result="get"
method="get"
url="http://api.postmarkapp.com/bounces">
<!---
Alert the server that the we can accept JSON as the type of
data returned in the response.
--->
<cfhttpparam
type="header"
name="accept"
value="application/json"
/>
<!--- Define the API key to authorize post. --->
<cfhttpparam
type="header"
name="X-Postmark-Server-Token"
value="#request.apiKey#"
/>
<!---
Pass in the email filter. Notice that we are filtering on
the *partial* email address. You don't have to do this - you
can use the full email address.
--->
<cfhttpparam
type="url"
name="emailFilter"
value="nat@natalie-gets-way-naughty"
/>
<!---
Pass in the number of bounce backs that we want to list
(PostMark provides implicit pagination of all bounce-back
records, starting with the most recent first).
--->
<cfhttpparam
type="url"
name="count"
value="1"
/>
<!---
Pass in the paging offset (which bounce back index will
start the given page) - zero is the first page.
--->
<cfhttpparam
type="url"
name="offset"
value="0"
/>
</cfhttp>
<!---
Deserialize the response JSON. This should give us a structure
that contains the returned bounces plus the total number of
bounces in the system (returned or otherwise) that match the
given set of filtering criteria.
--->
<cfset response = deserializeJSON( toString( get.fileContent ) ) />
<!--- Output the bounce back response. --->
<cfdump
var="#response#"
label="PostMark Bounce Backs"
/>
As you can see, I am searching for emails that are LIKE "nat@natalie-gets-way-naughty;" I left off the ".com" just to demonstrate that this filtering does not use exact text matching. As with the previous CFHTTP example, the request and response are both transferred in JSON format. When we get the Postmark API response, we need to deserialize the JSON string before we can use it. Running the above code, we get the following CFDump output:
As you can see, the email address we were searching for did, indeed, generate a hard bounce back.
The Postmark API seems like a very powerful and easy way to send emails and monitor their delivery status. I'm still feeling my way around parts of the bounce back API; but, the Postmark support staff has been both very fast at responding and very open to new ideas regarding their functionality.
Want to use code from this post? Check out the license.
Reader Comments
Thanks Ben! I wanted to follow up regarding "I am not 100% sure if this feedback is purely for user-insight; or, if it means that the given email was not sent."
Right now we don't record the sent/pending status of an email in the API. This is coming in a few weeks though. You will be able to see if an email is sent or bounced at any given time.
Chris
@Chris,
Sounds good. After some more testing, it looked like sending subsequent emails to the inactive email address did not generate further bounce-backs (at least based on the bounce back timestamp). So, I guess the API is smart enough to know now to waste its own time.
Never heard of Postmark. Looks like a great service. I'll have to look into it.
You'd probably also like the emailParse CFC by Tom Mollerus: http://www.mollerus.net/tom/projects/emailParseCFC/
We've got this (in somewhat modified form) running as a scheduled task to deal with bounces from our newsletter send outs. Saving soft bounces for later processing, and unsubscribing all hard bounces automatically. In addition we're also using it to process those that reply with 'unsubscribe' in the subject.
The only problem I've ever run into is some hotmail reply that it can't handle, but I'm placing the blame for that one in M$FT's court!
Very nice article, thanks for sharing this with us!
V.
@Ben
I had a look at this yesterday afetr you mentioned in your last article; it is indeed very powerful and pretty easy to use.
I also found this a pretty nice CFC that a guy named Jonathan Lane has created to integrate into there API:
http://github.com/wildbit/postmark-coldfusion
:)
@Sam,
If I can ask, how are you dealing with the bounce-backs? Are you just grabbing the TO email? Or are you parsing out more in the header? This morning, I just experimented with Postmarks and parsing out custom headers (X-Customer-ID). But, I am not sure how much I can actually depend on such headers to be returned in the bounce back.
@Aaron,
It seems pretty slick. Still experimenting.
@Tom,
I'll take a look at that. I think they actually linked to that on the Postmark resources list, but I didn't have a chance to look at it just yet.
@Ben basically it processes the bounces and splits out the originalTo, currentFrom, currentTo (this one is usually overkill), bouncetype, subject, body and UID.
The scheduled task takes a first stab at the emails, unsubscribing all that have a bouncetype 'hard'. Anything other than hard gets saved into a db table, so this would be the 'soft bounces' and 'unknown' (the actual replies with UNSUBSCRIBE as subject fall under this, but also some out of offices etc). As the cfc is right there, I've also adjusted this over time to deal with other typical status codes that should be hard but are producing soft etc.
We then run another batch of queries on this table to trim it down, unsubscribe the ones that have that in the subject etc.
Thanks for the article, always looking for ways to improve email stability. I have also found issues in using the failTo attribute to find email that has bounced.
I have not been able to find an API for gmail services, but would anyone know where to find a list of supported and unsupported elements.
@Sam,
And are just originalTo, bouncetype, and UID from the body of the email? Or is a combination of body and headers? I'm just trying to get a sense of what information people find is there on a dependable basis.
@Bryan,
What do you mean an API for GMail? They have POP and IMAP functionality that you can connect to.
It's a combination, ie. subject is partly used to deduce bouncetype, as are specific codes that are included in the body that is returned. So far we've been able to always retrieve the fields we're after, although the originalTo can be a little messed up at times (wrong original address, or there is forwarding involved etc.) depending on the mail server. I think that's just the kind of thing you pretty much have to live with though :(
@Sam,
Ok cool - good to know you've had a consistent experience with it.
Great post! I must say you have done a great job by providing this valuable information. I really appreciate your work.