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)>
<cfset errors = "">
<cfif not len(form.title)>
<cfset errors &= "You must include a title.<br/>">
</cfif>
<cfif not len(form.body)>
<cfset errors &= "You must include a body.<br/>">
</cfif>
<cfif not len(errors)>
<!--- send the message out --->
<cfoutput>
Your message, #form.title#, has been sent to your subscribers.
</cfoutput>
<cfabort>
</cfif>
</cfif>
<form method="post">
<h2>Newsletter</h2>
<cfoutput>
<cfif structKeyExists(variables, "errors")>
<p>
<b>Please correct the following:<br/>#errors#</b>
</p>
</cfif>
<p>
Title: <input type="text" name="title" value="#form.title#">
</p>
<p>
Body:<br/>
<textarea name="body" cols="60" rows="20">#form.body#</textarea>
</p>
</cfoutput>
<p>
<input type="submit" name="send" value="Send">
</p>
</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.
#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>
<cfsavecontent variable="fakemsg"><cfoutput>
Date: Mon, 17 Oct 2011 14:14:13 -0400 (EDT)
From: #fromaddress#
To: #fromaddress#
Subject: #form.title#
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.
<!--- 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)> <cfset errors = ""> <cfif not len(form.title)>
<cfset errors &= "You must include a title.<br/>">
</cfif> <cfif not len(form.body)>
<cfset errors &= "You must include a body.<br/>">
<cfelse>
<!--- check for spammyness ---> <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>
</cfif> <cfif not len(errors)>
<!--- send the message out --->
<cfoutput>
Your message, #form.title#, has been sent to your subscribers.
</cfoutput>
<cfabort>
</cfif> </cfif> <form method="post"> <h2>Newsletter</h2> <cfoutput>
<cfif structKeyExists(variables, "errors")>
<p>
<b>Please correct the following:<br/>#errors#</b>
</p>
</cfif> <p>
Title: <input type="text" name="title" value="#form.title#">
</p> <p>
Body:<br/>
<textarea name="body" cols="60" rows="20">#form.body#</textarea>
</p>
</cfoutput> <p>
<input type="submit" name="send" value="Send">
</p> </form>
<cfparam name="form.title" default="">
<cfparam name="form.body" default="">
Archived Comments
This is cool. How about taking it to the next level and creating a SpamAssassin API in ColdFusion? :)
@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...
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?
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.
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.
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...
Well, I'd probably take so-so at minimum. :)
This is great! How about CFPOP an email, then check it against Postmark. That would have all the "raw" data in it, would it?
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.
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.
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.