Its strange to discuss a procedure that makes a ColdFusion function less efficient. Especially in light of how well the ColdFusion MX 6.1 CFMAIL tag works (up to
1,000,000 messages per hour). Nonetheless, some realities in this
world are inescapable, and doing this will fix one of them.
A Bit of Background, First...
Years ago I used commercial shared hosting. Those systems have
notorious resource issues, for reasons that are fairly well known. Also
at that time (this is the ColdFusion 4.0/4.5 era) CFMAIL had a reputation
for being buggy when the server was under heavy load. So what was
necessary was a way to "throttle down" the speed at which ColdFusion transmitted
mail, so a taxed, shared server could handle it.
Flash forward to The Present Day...
ColdFusion can now reliably handle large volumes
of mail. But I still
throttle it down: Beginning some time in 2003 many large dialup vendors
began instituting short-term, temporary blocks on senders who pumped what
the provider considered to be too much mail to users of their networks
(the idea is that spammers don't do mail retries for extended periods,
making this a part of the spam control process). And it didn't take
much to trigger the filters. Only a few dozen emails to any provider was
enough to get blocked for a short time.
Services like AOL, MSN and Yahoo in particular
were identified as doing this. The
typical behavior was for mail to be refused at the receiving server as a
nonexistent address, when in fact the user existed. Resends
later in the day worked. The solution discussed on the Ipswitch Imail list
was to crank up the retry attempt count and interval so the retry period
exceeded the blockage period.
During this time I became aware that my older,
throttled-down systems were having no problems. It turned out that the
throttled speed had a byproduct: it kept the mailing rates underneath
the trigger threshold of this new antispam 'radar'.
On to the code (finally!)
The idea is to send mail slowly and to have
it sent at this slower speed unattended, so its not necessary
to break a list up into pieces and whatnot. What
we do is take advantage of an HTML meta refresh to re-execute our template,
and ColdFusion to manage the operation until its completion.
<cfsilent>
<!---
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=17>
<!---
Query Run is the number of query rows (email addresses)
that will be processed on each execution of this template
--->
<cfset variables.QueryRun=20>
<!---
pull the email addresses. Cache this query since its
going to be re-used every few seconds.
--->
<cfquery datasource="#request.myDSN#" name="MailList" cachedwithin="#CreateTimeSpan(0,1,0,0)#">
SELECT mylist.myEmail
FROM mylist
WHERE mylist.myEmail IS NOT NULL
ORDER BY mylist.myPrimaryKey ASC
</cfquery>
<!---
Set the starting point of the mail run to the first
record of query output
--->
<cfparam name="url.CurrStart" default="1"
type="numeric">
<!---
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">
</cfsilent>
<!---
Are we there yet?
--->
<cfif url.CurrStart
GT MailList.RecordCount>
<!---
yes. Inform the user
--->
<html>
<head>
<title>FINISHED</title>
</head>
<body ONLOAD="self.location='history.go(1)';">
<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>
<!---
No. Calculate the *next* starting point, and some other
values used in the display below
--->
<cfset variables.NextRun=url.CurrStart+variables.RunRate>
<cfset variables.NextShow=url.CurrStart+(variables.RunRate-1)>
<cfif variables.NextShow
GT MailRun.RecordCount>
<cfset variables.NextShow=MailRun.RecordCount>
</cfif>
<!---
display the "in-progress" page
--->
<html>
<cfoutput>
<!---
This next line re-runs the template automatically at the run rate,
with the incremented starting value. 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#?CurrStart=#variables.NextRun#
&UniqueURL=#UrlEncodedFormat(CreateUUID()#">
</cfoutput>
<head>
<title>Low Volume Mass Mailer</title>
</head>
<body ONLOAD="self.location='history.go(1)';">
<!---
Send the actual mail. This is a very simple version, with
hardcoded values in many places where, in a real application,
you'd be plugging in default values or output from other
queries (such as the mail server name, the sender address and
the email message text.
--->
<cfset variables.CharSet="text/html; charset="&chr(34)&"utf-8"&Chr(34)>
<cfmail
to="#MailList.myEmail#"
from="blah@blah.com"
subject="SnailMail"
server="mail.blah.com"
query="MailRun"
maxrows="#variables.QueryRun#"
startrow="#url.CurrStart#"
type="HTML">
<h1>YOUR EMAIL TEXT GOES
HERE</h1>
<cfmailparam
name="Reply-To"
value="#blah@blah.com#">
<cfmailparam
name="Message-ID"
value="<#CreateUUID()#@#mail.blah.com#>">
<cfmailparam
name="Content-Type"
value="#variables.CharSet#">
<cfmailparam
name="Mime-Version"
value="1.0">
</cfmail>
<!---
give the user a visual cue as to where the operation is at the moment
--->
<cfoutput>
<p>Sending messages #url.CurrStart# thru #variables.NextShow#<br>
<b>Total to send: #MailRun.RecordCount#</b></p>
</cfoutput>
<!---
This is a good place to do some extra calculations so you can show
the user how many messages have been processed, how many are left
and an extimated time to completion
--->
<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 technique has obvious size limits. It clearly won't run on truly
large lists, but for the small timer it should be fine. We have clients
with 5000+ mailing lists that have no problems with it or their large volume
of AOL/MSN recipients. Finally, the run rates here are only suggestions. You
may find that higher numbers will work fine, thereby increasing this system's
capacity to handle mail in a more timely fashion.