How Can I Throttle Down CFMAIL's Speed (v3.0)?
Several years ago we introduced one of our favorite tools, the mail trickler, which was used to fight off some of the problems ColdFusion had (back when it was made by Allaire and called Cold Fusion) with sending large volumes of mail on a shared server.
In 2004 ColdFusion MX, now a product of Macromedia, had largely eliminated those problems, but we found the mail trickler was still useful for entirely different reasons, so we kept it around and described how we beefed it up, adding a very necessary failover capability.
We also did something else shortly afterwards that we never wrote up: Built in the ability for the trickler to sense bad addresses and gracefully recover from trying to send an email with one. See, an undocumented change occurred in the CFMail tag in ColdFusion MX 6.0. In previous versions if you fed CFMail a badly formatted email address it would simply try to send it anyway. The mail server would then reject the attempt and the bad email would sit in the ColdFusion Administrator Undeliverable directory. It caused quite a stir at the time as one mailing routine after another collapsed into a puddle of goo.
Thats ancient history now, and ColdFusion developers have cleaned up their sloppy habits and test for inputs before they let something into their databases that purports to be an email address (right?). But what if something happens to creep in? It sure would be nice if the routine would collect bad addresses it comes across and notify the system administrator of them so the offending entries can be cleaned up. Particularly if it does so without falling on its face.
So we'll do that here. Once again, as with the 2004 update, we will make no attempt to cover old ground. Use the previous two articles to bring yourself up to speed on the process as it has evolved. We will only cover what has changed here.
First, an overview of the general process that we are adding is in order. We are going to surround the mail process with a try/catch block. That try/catch block will, if it encounters an error, log the error into a struct and allow the routine to continue. At the end of the process, all errors will be collected and emailed to the system administrator. Note that to keep this example simple we will not try to do much with the error process other than to identify the bad email address. It is up to you to use your imagination and expand on this process (hint: start by putting errors into a database instead of a struct).
At last its time to roll up our sleeves. Read the comments below to understand what is going on from this point forward.
<!--- Initialize the simple error array that will contain the bad email addresses. In a production environment it is probably smarter to use a database and collect more information, such as #cfcatch.message#, since far more can go wrong with a cfmail statement than a bad address. ---> <cfset variables.errArray=ArrayNew(2)> <!--- set the email address and server of the poor sap who gets the email listing all of the errors. A way to make this more robust is to keep the data in a database and simply notify the designated sap that errors exist to review. There are MANY useful things you can do with this error information. ---> <cfset variables.errEmail="poorSlob@foohbar.gov"> <cfset variables.errServer="mail.foohbar.gov"> <!--- Run Rate is the number of seconds between refresh. To optimize this make this setting about 2 seconds longer than the ColdFusion Server's mail spool fetch rate. Otherwise mail will pile up in the spooler and partially defeat the purpose of trickling out the mail ---> <cfset variables.RunRate=5> <!--- Query Run is the number of query rows (email addresses) that will be processed on each execution of this template ---> <cfset variables.QueryRun=5> <!--- pull the ID field so we can get a record count. ---> <cfquery datasource="#request.myDSN" name="MailList"> SELECT myMessages.ID FROM myMessages WHERE myMessages.EmailAddr IS NOT NULL ORDER BY myMessages.ID ASC </cfquery> <!--- Do some stuff to prevent page caching, which was found to be a problem on some client browsers ---> <cfheader name="Expires" value="Sun, 06 Nov 1994 08:49:37 GMT"> <cfheader name="Pragma" value="no-cache"> <cfheader name="cache-control" value="no-cache, no-store, must-revalidate"> <!--- Are we there yet? ---> <cfif MailList.RecordCount lt 1> <!--- yes. Notify the email administrator if there are any errors ---> <cfif not ArrayIsEmpty(variables.errorArray)> <!--- The array is not empty so there must be errors. Send the error email. Note that if errors are in the mail server itself this will never get sent, which is another reason to make this routine more robust. ---> <cfmail to="#variables.errEmail#" from="#variables.errEmail#" subject="OOPS!" server="#variables.errServer#" type="HTML"> <cfdump var="#variables.errArray#" label="Error List"> </cfmail> </cfif> <!--- finish the job by showing the "All Done" screen. ---> <html><head><title>FINISHED</title></head> <h1>All Done</h1> <b>Close this browser window!</b> <p><b>Do NOT press your BACK button</b> to leave.</p> </body></html> <cfelse> <!--- display the "in-progress" page ---> <html> <cfoutput> <!--- This next line re-runs the template automatically at the run rate. Note the UUID inserted into the url. This is another part of ensuring the browser doesn't cache this page. ---> <meta http-equiv="REFRESH" content="#variables.RunRate#;URL=#cgi.script_name# ?UniqueURL=#UrlEncodedFormat(CreateUUID())#"> </cfoutput> <head><title>Low Volume Mass Mailer with Failover</title></head> <!--- Send the actual mail. ---> <cfloop query="MailList" startrow="1" endrow="#variables.QueryRun#"> <cfquery name="ThisEmail" datasource="#request.myDSN#"> SELECT myMessages.EmailAddr, myMessages.EmailMsg, myMessages.EmailType, myMessages.EmailServer, myMessages.EmailSubject, myMessages.EmailFrom FROM myMessages WHERE myMessages.ID= <cfqueryparam cfsqltype="CF_SQL_NUMERIC" value="#MailList.ID#"> </cfquery> <cftry> <cfmail to="#ThisEmail.EmailAddr#" from="#ThisEmail.EmailFrom#" subject="#ThisEmail.EmailSubject#" server="#ThisEmail.EmailServer#" type="HTML"> #ThisEmail.EmailMsg# </cfmail> <cfcatch type="ANY"> <!--- if the cfmail statement throws an error it gets dealt with here. First we append the ID and email address to the error array. ---> <cfset arrayAppend(variables.errArray[1],MailList.ID)> <cfset arrayAppend(variables.errArray[2],ThisEmail.EmailAddr)> </cfcatch> </cftry> <!--- now that the mail has either been sent or flagged as bad, delete its database record ---> <cfquery datasource="#request.myDSN#"> DELETE FROM myMessages WHERE myMessages.ID= <cfqueryparam cfsqltype="CF_SQL_NUMERIC" value="#MailList.ID#"> </cfquery> </cfloop> <!--- give the user a visual cue as to where the operation is at the moment. You can get quite fancy here with pretty html and time-to-completion calculations based on your record count, refresh rate etc. ---> <cfset variables.LeftToGo=MailList.RecordCount-variables.QueryRun> <cfoutput> <p><b>Total left to send: #variables.LeftToGo#</b>.</p> <p>Each run is #variables.RunRate# seconds apart.</p> </cfoutput> <p>Leave this system alone and wait for this job to complete.</p> <p>When it finishes, you will be notified.</p> </body></html> </cfif>
|