Postmark release free Spam API

This post is more than 2 years old.

Earlier today Postmark released a free (*) API to perform SpamAssasin checks. This means you can now integrate a free spam check into your application. The API, documented here, is very easy to use. You simply pass in your email and get either a score back or a score and a full report on how the score was generated. The only real difficult part fo the API is that you are intended to send a full email document. By that I mean the text file that represents what an email looks like "raw". Here's how I built a checker in ColdFusion. I did this all in about twenty minutes so please feel free to do this better/faster/quicker.

First, I did a "typical" self-posting form with basic error checking. The idea is that the form would allow an admin to send out a newsletter to his or her users. The code just handles form and validation. An actual newsletter implementation would take about five more minutes. (cfquery to cfmail is about as simple as you can make it!)

<cfparam name="form.title" default=""> <cfparam name="form.body" default="">

<!--- used for fake email ---> <cfset fromaddress = "raymondcamden@gmail.com">

<cfif structKeyExists(form, "send")> <!--- auto trim ---> <cfset form.title = trim(form.title)> <cfset form.bod = trim(form.body)>

&lt;cfset errors = ""&gt;

&lt;cfif not len(form.title)&gt;
	&lt;cfset errors &= "You must include a title.&lt;br/&gt;"&gt;
&lt;/cfif&gt;	

&lt;cfif not len(form.body)&gt;
	&lt;cfset errors &= "You must include a body.&lt;br/&gt;"&gt;
&lt;/cfif&gt;	

&lt;cfif not len(errors)&gt;
	&lt;!--- send the message out ---&gt;
	&lt;cfoutput&gt;
	Your message, #form.title#, has been sent to your subscribers.
	&lt;/cfoutput&gt;
	&lt;cfabort&gt;
&lt;/cfif&gt;

</cfif>

<form method="post">

&lt;h2&gt;Newsletter&lt;/h2&gt;

&lt;cfoutput&gt;
&lt;cfif structKeyExists(variables, "errors")&gt;
	&lt;p&gt;
	&lt;b&gt;Please correct the following:&lt;br/&gt;#errors#&lt;/b&gt;
	&lt;/p&gt;
&lt;/cfif&gt;
	
&lt;p&gt;
Title: &lt;input type="text" name="title" value="#form.title#"&gt;
&lt;/p&gt;

&lt;p&gt;
Body:&lt;br/&gt;
&lt;textarea name="body" cols="60" rows="20"&gt;#form.body#&lt;/textarea&gt;
&lt;/p&gt;
&lt;/cfoutput&gt;

&lt;p&gt;
	&lt;input type="submit" name="send" value="Send"&gt;
&lt;/p&gt;

</form>

Next I added the spam check. As I said above, you are expected to mimic a "real" email. If you send just the text (in my case, form.body), you will get dinged badly for missing email headers. I went to Gmail, picked a random message, and viewed the original version. I took those headers and stripped it down as much as possible. In my report I get a few minor dings compared to the original version, but I felt like this was an acceptable compromise.

<cfsavecontent variable="fakemsg"><cfoutput> Date: Mon, 17 Oct 2011 14:14:13 -0400 (EDT) From: #fromaddress# To: #fromaddress# Subject: #form.title#

#form.body# </cfoutput> </cfsavecontent>

<cfhttp url="http://spamcheck.postmarkapp.com/filter" method="post"> <cfhttpparam type="formfield" name="email" value="#trim(fakemsg)#"> <cfhttpparam type="formfield" name="options" value="long"> </cfhttp> <cfset respRaw = cfhttp.filecontent.toString()> <cfset response = deserializeJSON(respRaw)> <cfif response.score gt 5> <cfset errors &= "Your report scored too high on the SpamAssasin check (#response.score#).<br/>"> <!--- possibly show response.report for more detail ---> </cfif>

The report key in the result is a plain text list of how your score was generated. You can display that in a PRE block or - conversely - parse it up and do what you will with it. You can test this below. Note - this will never send any emails, so feel free to test as many times as you would like. I've included the full code below.

  • Note that while this service is free, Postmark said they do not guarantee it will remain free, available, useful, etc for the rest of time.

<cfparam name="form.title" default=""> <cfparam name="form.body" default="">

<!--- used for fake email ---> <cfset fromaddress = "raymondcamden@gmail.com">

<cfif structKeyExists(form, "send")> <!--- auto trim ---> <cfset form.title = trim(form.title)> <cfset form.bod = trim(form.body)>

&lt;cfset errors = ""&gt;

&lt;cfif not len(form.title)&gt;
	&lt;cfset errors &= "You must include a title.&lt;br/&gt;"&gt;
&lt;/cfif&gt;	

&lt;cfif not len(form.body)&gt;
	&lt;cfset errors &= "You must include a body.&lt;br/&gt;"&gt;
&lt;cfelse&gt;
	&lt;!--- check for spammyness ---&gt;
		
	&lt;cfsavecontent variable="fakemsg"&gt;&lt;cfoutput&gt;

Date: Mon, 17 Oct 2011 14:14:13 -0400 (EDT) From: #fromaddress# To: #fromaddress# Subject: #form.title#

#form.body# </cfoutput> </cfsavecontent>

	&lt;cfhttp url="http://spamcheck.postmarkapp.com/filter" method="post"&gt;
		&lt;cfhttpparam type="formfield" name="email" value="#trim(fakemsg)#"&gt;
		&lt;cfhttpparam type="formfield" name="options" value="long"&gt;			
	&lt;/cfhttp&gt;
	&lt;cfset respRaw = cfhttp.filecontent.toString()&gt;
	&lt;cfset response = deserializeJSON(respRaw)&gt;
	&lt;cfif response.score gt 5&gt;
		&lt;cfset errors &= "Your report scored too high on the SpamAssasin check (#response.score#).&lt;br/&gt;"&gt;
		&lt;!--- possibly show response.report for more detail ---&gt;
	&lt;/cfif&gt;
&lt;/cfif&gt;	

&lt;cfif not len(errors)&gt;
	&lt;!--- send the message out ---&gt;
	&lt;cfoutput&gt;
	Your message, #form.title#, has been sent to your subscribers.
	&lt;/cfoutput&gt;
	&lt;cfabort&gt;
&lt;/cfif&gt;

</cfif>

<form method="post">

&lt;h2&gt;Newsletter&lt;/h2&gt;

&lt;cfoutput&gt;
&lt;cfif structKeyExists(variables, "errors")&gt;
	&lt;p&gt;
	&lt;b&gt;Please correct the following:&lt;br/&gt;#errors#&lt;/b&gt;
	&lt;/p&gt;
&lt;/cfif&gt;
	
&lt;p&gt;
Title: &lt;input type="text" name="title" value="#form.title#"&gt;
&lt;/p&gt;

&lt;p&gt;
Body:&lt;br/&gt;
&lt;textarea name="body" cols="60" rows="20"&gt;#form.body#&lt;/textarea&gt;
&lt;/p&gt;
&lt;/cfoutput&gt;

&lt;p&gt;
	&lt;input type="submit" name="send" value="Send"&gt;
&lt;/p&gt;

</form>

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by kc posted on 10/18/2011 at 2:47 AM

This is cool. How about taking it to the next level and creating a SpamAssassin API in ColdFusion? :)

Comment 2 by Seb Duggan posted on 10/18/2011 at 2:54 AM

@kc: You mean like this one?

https://github.com/sebdugga...

Wrote it earlier today in response to Postmark's request for API wrappers in different languages...

Comment 3 by Raymond Camden posted on 10/18/2011 at 5:08 AM

Seb - any reason why you do the more complex http post then mine? Ie, you serialize, you pass the accepts header, etc?

It would really rock if your CFC added the ability to generate the fake email for you. Ie, pass in the title, body, and a sender email, and it does what mine does above. Know what I mean?

Comment 4 by Randy Merrill posted on 10/18/2011 at 7:04 AM

Hehe, I also made a wrapper as part of the call out to developers on the Postmark Blog:

https://github.com/emberfea...

Mine doesn't have all the fancy functions that Seb's does and it requires full script CFCs.

Comment 5 by Bjorn Jensen posted on 10/18/2011 at 11:22 AM

A big problem I'm seeing with this is that the information you provide for the spamassassin instance is incorrect and hence you will not get the "real" score, if such a thing can be assumed.
What I mean is that you need to pass the exact raw email as it is received, since spamassassin uses all the information in the email to make it's decisions. It uses the Received IP's to check against RBL's. It uses the html/text/whatever body and headers in the bayes filters.
It also looks at encodings in the email and how it all match up.

So I'd say that unless you're actually passing the entire raw email (and even then there's issues if you can't configure trusted relayes) your results are bound to be so-so.

Disclaimer: I'm the email/spamassassin admin at our work.

Comment 6 by Seb Duggan posted on 10/18/2011 at 11:56 AM

Ray: Bjorn's got it right - the data that the service expects is the full email source, including all headers.

In other words, what you'll see in your email app or Gmail if you view the original source.

This contains all the routing information for the email, among other things, which is a large part of seeing whether the message gets flagged as spam.

To be honest, I'm not sure how useful the API is to check how spammy an outgoing message might be...

Comment 7 by Raymond Camden posted on 10/18/2011 at 3:19 PM

Well, I'd probably take so-so at minimum. :)

Comment 8 by Rex posted on 10/18/2011 at 9:44 PM

This is great! How about CFPOP an email, then check it against Postmark. That would have all the "raw" data in it, would it?

Comment 9 by Raymond Camden posted on 10/18/2011 at 9:47 PM

Well, by the time you cfmail it to the person, it is too late. ;) You could use a testing account though. cfmail it to them and cfpop it. This would need to be done async though.

Comment 10 by Bjorn Jensen posted on 10/19/2011 at 11:39 AM

I've just tested their service today with some of the spam we have received and I can see it's certainly a very conservative ruleset they are using. This means that they score way way lower than our own installation, failing to block most of the spam.
I can only imagine this is because if anyone is actually using this service, they are afraid they will block too much legit email.

While it's a cool idea and certainly has it's uses, spamfiltering should really be applied at the mailserver. What you can use this for is a second level of spamfiltering, such as checking if any of the ip's or url's has been blacklisted since the time the email was received and until it's fetched from the server.

If you plan on using this service, make sure you can send the raw email as it was received (don't think this is possible with cfpop) and remove any trusted relayes yourself so you are sure you won't hit incorrect rbl's.
Even better, set up your own spamassassin installation, it's free, has great client tools, is dirt easy to use and you can customize the rules to your liking, including setting up correct trusted relayes.

Comment 11 by Randy Merrill posted on 10/19/2011 at 7:27 PM

I think that the intent of the service isn't to be used to filter incoming mail. If you want to do that you should probably install your own installation of spam assassin.

This service seems to be more in line with helping you to not be the one the sending out spam.

It very useful when your application allows the user to send emails to others. Especially since if you are using DKIM and SPF, as you do with the normal postmark API, it will affect all the email that you send if you are sending out spam from your users.

To me it is an extremely easy way to test your emails before you send them so that your entire application doesn't get marked as spam because some of your users are abusing the system to send spam.