The Robertson Team


 

 
 
 
 
 
  Programmers' Tools >  The Free Stuff >  Free ColdFusion Tutorials >  How Can I Throttle Down CFMAIL's Speed (v3.0)?
 

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>


The Robertson Team, TheKing@mysecretbase.com
1.559.360.1717 


HostMySite.com is a leader in ColdFusion web hosting and managed dedicated servers.