Last week I wrote a quick proof of concept that demonstrated a simple mail throttler for ColdFusion. The idea was that you may have some automatic mail process that you do not want to get overwhelmed with. Read the previous blog entry for full details and the original code. Dan Switzer commented that my method for "merging" dynamic emails (with a regex) could be done much simpler if I allowed people to simply name the emails. So with that in mind I've added the support for a cachename value. This actually takes priority over regex as the more I think about it, the more I think folks will prefer it over the regex option. Here is the new component.
<cfcomponent output="false">
<cfset variables.cache = {}>
<cffunction name="throttleSend" access="public" output="false" returnType="boolean">
<cfargument name="mail" type="struct" required="true" hint="Structure of args for the mail.">
<cfargument name="limit" type="numeric" required="true" hint="Number of minutes to wait before sending again.">
<cfargument name="cachename" type="string" required="false" hint="Used to name the email in terms of uniqueness. If used, the email contents won't be used to check for uniqueness.">
<cfargument name="regex" type="string" required="false" hint="This regex is performed on your mail body. Helps remove items that may be dynamic in the body but should not be considered for caching.">
<!--- used for required mail tags --->
<cfset var reqlist = "to,from,subject,body">
<cfset var l = "">
<cfset var body = "">
<cfset var cacheBody = "">
<cfset var hashKey = "">
<!--- quickly validate the mail object --->
<cfloop index="l" list="#reqlist#">
<cfif not structKeyExists(arguments.mail, l)>
<cfthrow message="mail object is missing required key #l#">
</cfif>
</cfloop>
<!--- Ok, first, create the hash --->
<cfset body = arguments.mail.body>
<cfif structKeyExists(arguments, "cachename")>
<cfset hashKey = arguments.cachename>
<cfelseif structKeyExists(arguments, "regex")>
<cfset cacheBody = rereplace(body,regex,"","all")>
<cfset hashKey = hash(arguments.mail.to & " " & arguments.mail.subject & " " & cacheBody)>
<cfelse>
<cfset hashKey = hash(arguments.mail.to & " " & arguments.mail.subject & " " & body)>
</cfif>
<!--- If we already sent it and it hasn't expired, don't do squat --->
<cfif structKeyExists(variables.cache, hashKey) and dateCompare(now(), variables.cache[hashKey]) is -1>
<cfreturn false>
</cfif>
<!--- Ok, so we need to mail --->
<cfmail attributecollection="#arguments.mail#">#body#</cfmail>
<cfset variables.cache[hashKey] = dateAdd("n", arguments.limit, now())>
<cfreturn true>
</cffunction>
</cfcomponent>
And here is a new test script that shows all three options .
<cfapplication name="mtdemo">
<cfif not structKeyExists(application, "throttler") or structKeyExists(url,"init")>
<cfset application.throttler = new mailthrottle()>
</cfif>
<cfset mailOb = {
to="ray@camdenfamily.com",
from="ray@camdenfamily.com",
subject="Error about X!",
body="This is the body of the email."
}>
<cfset res = application.throttler.throttleSend (mailOb,2)>
<cfoutput>result was #res#</cfoutput>
<p/>
<cfset mailOb = {
to="ray@camdenfamily.com",
from="ray@camdenfamily.com",
subject="Error about X!",
body="This is the body of the email. Random: #randRange(1,100)#"
}>
<cfset res = application.throttler.throttleSend (mail=mailOb,limit=2,regex="Random: [0-9]{1,3}")>
<cfoutput>result was #res#</cfoutput>
<p/>
<cfset mailOb = {
to="ray@camdenfamily.com",
from="ray@camdenfamily.com",
subject="Error about Y!",
body="This is the body of the email. Random: #randRange(1,100)#"
}>
<cfset res = application.throttler.throttleSend (mail=mailOb,limit=2,cacheName="Error Email Y")>
<cfoutput>result was #res#</cfoutput>
The final example shows the cacheName in action. Useful?