Skip to page content or Skip to Accesskey List.

Work

Main Page Content

Simple Cross Language Bulk Mailer Part Ii

Rated 3.92 (Ratings: 2)

Want more?

 

Daniel Cody

Member info

User since: 14 Dec 1998

Articles written: 146

The Cold Fusion architecture.

In the first installment of this article, we got our database in place and laid out the foundation for our application. Now, let's take a peak at what the Cold Fusion end of our application will look like. (Just a side note here, but I wanted to point out that my coding style may or may not be different than yours. You may scope your variables and floss your teeth differently than me, and that's cool. That's what makes us all unique little snowflakes.)

After working with .jeff for about a year and a half now on the evolt site, I've started picking up some of his coding habits, one of which is the nearly obsessive use of <cfinclude> and templates. Therefore, each of our main 'sections' will be contained within their own directory underneath the root of our application. Combined with some methods similar to the fusebox style, this allows for nice separation between all the major functions of our application. Therefore, we'll have a directory named home for our default pages, images should be self-explanatory, login will hold the files that we'll use for authentication, msg contains files relating to the sending and posting of messages, and user will pertain to things like subscribing and unsubscribing users. Now then, onto the code!

<--- our Application.cfm file --->

<CF_fu2a>

<cfapplication name="lists" clientmanagement="no" sessionmanagement="yes" setclientcookies="yes" >

<--- data source name --->

<cfparam name="data" default="lists">

<cfparam name="session.loggedin" default="no">

<cfparam name="url.a" default="home">

<cfparam name="url.b" default="">

<cfparam name="url.c" default="">

<cfparam name="url.d" default="">

As you can see, our Application.cfm file is fairly simple. The <CF_fu2a> declaration at the top is a highly modified

version of the FormURL2Attributes tag from Fusebox. We've turned on session management, set our default datasource, and declared four URL variables a, b, c, and d (named for simplicity). The session.loggedin variable is what we'll be using to authenticate people, which is set to a negative value to start.

Next, lets take a look at our 'index.cfm' and header/footer files. The 'index.cfm' page is simply a switch expression to check whether the user has been logged in yet using the session.loggedin variable, and a header/footer include.

<cfinclude template="index.cfm">

<cfswitch expression="#session.loggedin#">

<cfcase value="yes">

<cfinclude template="dsp_home.cfm">

</cfcase>

<cfdefaultcase>

<cfinclude template="login/act_login.cfm">

<cfinclude template="login/dsp_login.cfm">

</cfdefaultcase>

</cfswitch>

<cfinclude template="dsp_footer.cfm">

Just to make sure you're still following, our cfswitch statement looks at the session.loggedin variable. If it sees that the variable is set to 'yes', we get sent to 'dsp_home.cfm' - otherwise, we include the 'act_login.cfm' and the 'dsp_login.cfm' files from the login directory. Regardless of that, our application is still wrapped in a header and footer that contain basic HTML at this time. Let's follow the natural progression here and assume that we haven't logged in yet; in that case, we get shown the 'login/act_login.cfm' and 'login/dsp_login.cfm' files.

<!--- dsp_login.cfm --->

<p>

Welcome, please enter your list name and login:

</p>

<form action="/lists/index.cfm" method="post">

<strong> Username:</strong> <input type="text" name="list_name" size="15" value="#form.list_name#"><br>

<strong> Password:</strong> <input type="password" name="list_pass" size="15" value="#form.list_pass#"><br>

<input type="submit" value=" Go > ">

</form>

A fairly simple form. We're asking the user for a list name and the password for the list they have control over. Take notice of the structure of the URL in our form action where we set the url.a variable to 'login' and the url.b variable to 'process'. Also take note that even though we included both 'act_login.cfm' and 'dsp_login.cfm', nothing from 'act_login.cfm' gets included because no url.b variable was declared yet. Therefore, the '<defaultcase>' section of our '<cfswitch>', which is empty, gets included.

<!--- act_login.cfm --->

<cfparam name="form.list_name" default="">

<cfparam name="form.list_pass" default="">

<cfparam name="form.submit" default="">

<cfswitch expression="#url.b#">

<cfcase value="process">

<cfquery name="checklogin" datasource="#data#">

select * from lists

where list_name = '#trim(form.list_name)#'

and list_passwd = '#trim(form.list_pass)#'

</cfquery>

<cfif checklogin.recordcount>

<cfset session.loggedin="yes">

<cfset session.list_id="#checklogin.list_id#">

<cfset session.list_email="#checklogin.list_email#">

<cfset session.list_name="#checklogin.list_name#">

<cflocation url="/lists" addtoken="no">

<cfelse>

<cflocation url="/lists/index.cfm/a/login/b/error" addtoken="no">

</cfif>

</cfcase>

<cfcase value="error">

<strong>Sorry, there was an error signing you in, please try again.</strong>

</cfcase>

<cfcase value="logout">

<cfset session.loggedin="no">

<cfset session.list_id="">

<cflocation url="/lists/index.cfm">

</cfcase>

<cfdefaultcase>

</cfdefaultcase>

</cfswitch>

Let's explain what is going on in 'act_login.cfm'. We're setting a cfswitch on the url.b variable, which we declared in our Application.cfm to have no value. However, the form we just submitted set the url.b variable to 'process'. The cfswitch

catches that, and runs a simple query on the submitted information against the lists table from our database. If both the username

and the password match (hence a recordset is returned), we set the session.loggedin variable to 'yes', session.list_id

to the list_id associated with the username and password we entered, the session.list_email and session.list_name also get set to their respective values from the lists table in the database. Finally, we redirect ourselves back to the front page of our application with the '<cflocation url="/lists" addtoken="no">' line. If no records are returned from the database, we pretty much

redirect ourselves back to this page with the '<cflocation url="/lists/index.cfm/a/login/b/error" addtoken="no">' line, but this time

we set the url.b variable to 'error'. We come back to this page (because of the includes) but now the cfswitch will tell us there was an error with our login. The cool thing, in my opinion, about doing it this way is the 'dsp_login.cfm' page still gets displayed, but

there's an error message from this page that gets included letting the user know they entered incorrect information. Lastly, we include an option for a 'logout' variable that will kill our session and return us to the main page.

Whew!

I know that's a lot to digest in one sitting, so take this opportunity to grab a cold drink and stretch those tendons! .....

So, let's just do a quick recap here to make sure I'm not losing anyone. We did a check on the session.loggedin variable to verify if we were authenticated. Since we had set that variable to 'no' in our Application.cfm, we were not authenticated and were sent to the login section of the site. If we entered the correct list name and password, we set the session.loggedin variable to 'yes' and redirected ourselves back to the front page, otherwise we gave a small error, leave the session.loggedin variable set to 'no' and display the authentication form again.

Moving past the authentication.

If you'll recall from our index.cfm page above, once the session.loggedin variable was set to yes, we were passed on to the 'dsp_home.cfm' template.

<!--- dsp_home.cfm --->

<cfswitch expression="#url.a#">

<cfcase value="user">

<cfinclude template="user/act_user.cfm">

<cfinclude template="user/dsp_user.cfm">

</cfcase>

<cfcase value="info">

<cfinclude template="info/dsp_info.cfm">

</cfcase>

<cfcase value="login">

<cfinclude template="login/act_login.cfm">

<cfinclude template="login/dsp_login.cfm">

</cfcase>

<cfcase value="msg">

<cfinclude template="msg/act_msg.cfm">

<cfinclude template="msg/dsp_msg.cfm">

</cfcase>

<cfdefaultcase>

<cfinclude template="home/act_home.cfm">

<cfinclude template="home/dsp_home.cfm">

</cfdefaultcase>

</cfswitch>

You guessed it, another huge '<cfswitch>' statement! This time though, we're assessing the value of the url.a variable which we gave a default value of 'home' in our Application.cfm file. At the end of our authentication file ('dsp_login.cfm') you'll remember we didn't pass any URL variables when we used to the '<cflocation>' tag to redirect ourselves. Therefore, we slide right into the

'<defaultcase>' section of the '<cfswitch>' which again, leads us to a set of templates, this time in the home directory.

<!--- act_home.cfm --->

<cfswitch expression="#url.b#">

<cfcase value="info">

<cfquery name="listinfo" datasource="#data#">

select * from lists where

list_id = "#session.list_id#"

</cfquery>

</cfcase>

<cfdefaultcase>

<cfquery name="gethomeinfo" datasource="#data#">

select list_name, list_id, list_email, list_desc

from lists

where

list_id = '#session.list_id#'

</cfquery>

</cfdefaultcase>

</cfswitch>

Again, a '<cfswitch>' on the url.b variable. Since we haven't yet defined that variable, we slide into the '<defaultcase>' section that runs a simple query that grabs some values from the lists table based on the session.list_id variable which was set when we logged in. The other template Cold Fusion was told to process is the 'dsp_home.cfm' file from the 'home' directory.

<!--- dsp_home.cfm --->

<cfswitch expression="#url.b#">

<cfcase value="info">

<cfinclude template="act_home.cfm">

<cfinclude template="dsp_info.cfm">

</cfcase>

<cfdefaultcase>

<table width="100%" cellpadding="4" cellspacing="4" border="0">

<tr>

<td width="5%" align="left" valign="top">

 

</td>

<td align="left" valign="top">

<cfoutput query="gethomeinfo">

<p>

Welcome to <strong>#list_name#</strong>! All email will appear to come from <strong>#list_email#</strong>.<br>

Please select from the list of options on the left.

</p>

</cfoutput>

</td>

</tr>

</table>

</cfdefaultcase>

</cfswitch>

Once again, no url.b variable is currently defined, so we process what is in the '<cfdefaultcase>' tag which finally gives us an option to perform an action. We also grab some information from the 'gethomeinfo' query that we performed in 'act_home.cfm' above to just re-affirm to the user the name and email address of their list. You'll notice in the code above that it states you can 'select from a list of options on the left'. These options are static links in our 'dsp_header.cfm' file and now that we have a minute to get our bearings,

let's take a look at the header file we've been using this whole time.

<-- dsp_header.cfm # -->

<HTML>

<HEAD>

<TITLE>Mailing List Administration</TITLE>

</HEAD>

<BODY BGCOLOR="#FFFFFF" LINK="#003300" VLINK="#336666" ALINK="#CC6600">

<table border="0" cellspacing="0" cellpadding="0">

<tr>

<td valign="top" align="left" width="20%">

 

</td>

<TD VALIGN="TOP" ALIGN="left" colspan="2">

<IMG SRC="/lists/images/head_admin.gif" WIDTH="479" HEIGHT="47" BORDER="0">

</TD>

</tr>

</table>

<table border="0" cellspacing="0" cellpadding="0">

<tr>

<cfif session.loggedin eq "yes">

<td valign="TOP" align="left" width="138">

<A HREF="/lists/index.cfm/a/msg">Send</A><BR>

<A HREF="/lists/index.cfm/a/user">Who's on my list</A><BR>

<A HREF="/lists/index.cfm/a/user/b/sub">Subscribe a new user</A><BR>

<A HREF="/lists/index.cfm/a/user/b/unsub">Unsubscribe an existing user</A><BR>

<A HREF="/lists/index.cfm/a/home/b/info">Get info on your list</A><BR>

<A HREF="/lists/index.cfm/a/login/b/logout">Log out</A><BR>

</td>

</cfif>

<TD ALIGN="left" VALIGN="top">

<!-- # end dsp_header.cfm # -->

Nothing special here other than a couple of options which we give our user to interact with their list. Do take notice though of how I set the URL variables for each of the options statically. Again, this is just my preference, and will get us by for this simple bulk emailer. Obviously you could change the HTML formatting here quite a bit to suit your own tastes.

In our application, we have the ability to send a message to our list, see who's on our list, subscribe and unsubscribe people, get basic information on our list, and lastly, logout. We'll go through each of these functions starting with our main feature, sending email.

Sending email.

So, we're finally ready to send out email! First, let's summarize:

  • When we click on the 'Send' link as shown in 'dsp_header.cfm' above, the url.a variable will get set to 'msg'.
  • We have session.list_id, session.list_name, and session.list_email all defined with

    their respective values and sitting in the 'session' variable scope.
  • We're good enough, we're smart enough, and doggone it, people like us!

Now that we've had our daily affirmation, let's continue!

Because the url.a variable is now set to 'msg', our initial 'dsp_home.cfm' file up towards the top of this article includes the 'act_msg.cfm' and 'dsp_msg.cfm' templates in the msg directory. You can probably start sensing the pattern here of including two files;

one for processing and one for displaying it's particular section of our application. Here's the 'act_msg.cfm' template:

<!--- act_msg.cfm --->

<cfswitch expression="#url.b#">

<cfcase value="post">

<cfquery name="insertmsg" datasource="#data#">

insert into messages ( msg_from_id, msg_body, msg_date, msg_list_id)

values ('#form.msg_from_id#', '#form.msg_body#', '#form.msg_date#', '#form.msg_list_id#')

</cfquery>

<cfquery name="getemails" datasource="#data#">

select member_email, member_id, member_subscribed, member_subscribedto

list_id, list_email from members, lists

where

member_subscribedto = '#form.msg_list_id#'

and

list_id = member_subscribedto

and member_subscribed = '1'

</cfquery>

<cfmail query="getemails" to="#member_email#" from="#getemails.list_email#" Subject="#form.msg_subject#">

#form.msg_body#

</cfmail>

<cflocation url="/lists/index.cfm/a/msg/b/thanks" addtoken="no">

</cfcase>

<cfcase value="preview">

<cfquery name="showpreview" datasource="#data#">

select * from lists where list_id = '#session.list_id#'

</cfquery>

</cfcase>

<cfdefaultcase>

</cfdefaultcase>

</cfswitch>

As you're more than likely getting used to by now, we're performing a <cfswitch> on the url.b variable, this time grepping for the 'post' and 'preview' values. When this template sees the url.b variable set to 'post' it's going to insert the message into the database and get the addresses of the people who are subscribed to the list (based on what our 'session.list_id' is). If the url.b variable is set to 'preview', the template only performs a single query. By default, this template does nothing, so let's see how we get those url.b variables

assigned to a value in the 'dsp_msg.cfm' template.

<!--- dsp_msg.cfm --->

<cfswitch expression="#url.b#">

<cfcase value="post">

<cfinclude template="act_msg.cfm">

<cfinclude template="dsp_post.cfm">

</cfcase>

<cfcase value="preview">

<cfinclude template="act_msg.cfm">

<cfinclude template="dsp_preview.cfm">

</cfcase>

<cfcase value="thanks">

<cfinclude template="dsp_post.cfm">

</cfcase>

<cfdefaultcase>

<table width="100%" cellpadding="4" cellspacing="4" border="0">

<tr>

<td width="5%" align="left" valign="top">

 

</td>

<td align="left" valign="top">

<p>Either type or cut & paste your message and subject in the box below. Hit the send button to send the message to your users.</p>

<form action="/lists/index.cfm/a/msg/b/preview" method="post">

<table width="50%" cellpadding="4" cellspacing="2" border="0">

<tr bgcolor="#ffffff">

<td align="left" valign="top">

<p>

<strong>Subject:</strong><br><input type="text" name="msg_subject" size="30">

</p>

<p>

<strong>Message:</strong><br>

<input type="hidden" name="list_id" value="<cfoutput>#session.list_id#</cfoutput>">

<textarea wrap="virtual" cols="60" rows="15" name="msg_body" class="main"> </textarea>

</p>

</td>

</tr>

<tr>

<td align="right" valign="top">

<input type="submit" value=" Preview! ">

</td>

</form>

</table>

</td>

</tr>

</table>

</cfdefaultcase>

</cfswitch>

Ok, now we're obviously getting a bit more complex. Let's try to make sense of it all. The url.b variable is still not defined, so we process the information within the '<cfdefaultcase>' tag which displays instructions on how to write the email and two form fields for the information to go. These are the Subject(msg_subject) and the Body(msg_body) pieces of our email, so nothing too crazy there. After the user submits this information, you'll notice it doesn't get sent to the mail server right away, but rather we want the user to preview their message first to correct any typos and give it 'the once over' before sending it out. Finally, we set the url.b variable to 'preview' but keep the url.a variable to 'msg'. This means that we pretty much get sent back to this page, but the <cfswitch> catches the url.b variable and includes the 'act_msg.cfm' template above and the 'dsp_preview.cfm' template which simply reformats the message in a manner somewhat similar to how it might look in an email client.

I just want to take a minute here to explain why the hell I'm re-including the 'act_msg.cfm' template since a couple of people I know have gotten mixed up right about here. Because the 'act_msg.cfm' template does a '<cfswitch>' on the url.b variable, we can use one template to do many different things. Some people may prefer to break up every piece of the application into strictly separate templates, and as I said before, that's cool - just not how I prefer to do things. This way may seem harder and you might not grasp it at first but once you get used to it, it's really quite slick and efficient. Like I said, we're all unique little snowflakes.

So, once the user fills in the form with a Body and Subject and hits the 'Preview!' button, they're whisked right back to this same template page but this time the '<cfswitch>' picks up the 'preview' value for the url.b variable and displays the 'dsp_preview.cfm' template

instead of the information inside the '<cfdefaultcase>' tag. The 'dsp_preview.cfm' page is quite simple, and looks like this:

<!--- dsp_preview.cfm --->

<table width="100%" cellpadding="4" cellspacing="4" border="0">

<tr>

<td width="5%" align="left" valign="top">

 

</td>

<td align="left" valign="top">

<p>

Here is a preview of how your message will look:

</p>

<table width="100%" cellpadding="4" cellspacing="4" border="0">

<cfoutput query="showpreview">

<tr>

<td class="code" align="left" valign="top">

<strong>To: <em>yourusers</em></strong>

</td>

</tr>

<tr>

<td class="side" align="left" valign="top">

<strong>From:</strong> <em>#list_name# <#list_email#></em>

</td>

</tr>

<tr>

<td class="code" align="left" valign="top">

<strong>Subject:</strong><em>#form.msg_subject#</em>

</td>

</tr>

<tr>

<td class="side" align="left" valign="top">

<strong>Date:</strong> #dateformat(now(), "DDD MMM dd yyyy")#</em>

</td>

</tr>

<tr>

<td class="code" align="left" valign="top">

<strong>Message:</strong>

</td>

</tr>

<tr>

<td class="main" align="left" valign="top">

#paragraphformat(form.msg_body)#

</td>

</tr>

</cfoutput>

</table>

</td>

</tr>

<tr>

<td width="5%" align="left" valign="top">

 

</td>

<td width="50%" align="left" valign="top">

<p><hr noshade><br>

If you'd like to post this to all your members, hit the <strong>send</strong> button! Otherwise, use the '<em>back</em>' button

on your browser to edit this message again.<br>

<form action="/lists/index.cfm/a/msg/b/post" method="post">

<cfoutput>

<input type="hidden" name="msg_list_id" value="#session.list_id#">

<input type="hidden" name="msg_subject" value="#trim(form.msg_subject)#">

<input type="hidden" name="msg_from" value="#trim(showpreview.list_name)#">

<input type="hidden" name="msg_date" value="#dateformat(now(), "mm/dd/yyyy")#">

<input type="hidden" name="msg_from_id" value="#showpreview.list_admin_id#">

<input type="hidden" name="msg_body" value="#trim(form.msg_body)#">

<input type="submit" value="Send!">

</cfoutput>

</form></p>

</td>

</tr>

</table>

As you can see, we simply take the form.msg_subject and form.msg_body fields that the user entered and display them in a format that's easy on the eyes. We also grab all other list variables from the query in 'act_msg.cfm' (Because since the url.b variable was now 'preview' in that template, a simple query was run that got all list information based on the session.list_id variable ... starting to make sense now!) and stuff everything inside hidden form fields at the bottom of the template. Assuming the user likes what they see, they click on the 'Send!' button which whisks all our hidden form variables back to the 'dsp_msg.cfm' template, this time with a url.b value of 'post'.

Same as with the 'preview' function now, the '<cfswitch>' in 'dsp_msg.cfm' greps our 'post' value for url.b and includes

the 'act_msg.cfm' template (again) and the 'dsp_post.cfm' template. The 'dsp_post.cfm' template is nothing more than a file with 'Your post has been sent!' in it. All the magic this time happens in the 'act_msg.cfm' file as you can see above. Specifically, all the hidden form fields get inserted into the 'messages' table of the database for safekeeping based on the 'session.list_id' variable. An email gets built using CFMail with the 'To:' fields of the email filled in by doing a query on all records in the 'members' table that match our hidden form.msg_list_id field. This allows us to have multiple bulk mailing lists each with their own separate set of members. Hopefully you're starting to see the big picture here! Take another look at the 'act_msg.cfm' template if you like to make sure everything makes sense.

Hopefully you're starting to see the method to my madness! Based on what we've covered today, you should be able to send an email to a group of people through our web based interface. Managing that list of people by subscribing them and unsubscribing them will be covered in the next installment of this article, which you'll hopefully be looking forward to! Please feel free to leave any comments, suggestions, or requests for clarification in the comment section below :)

Dan lives a quiet life in the bustling city of Milwaukee, WI. Although he founded what would become evolt.org in 1998, he's since moved on to other projects and is now the owner of Progressive Networks, a Zimbra hosting company based in Milwaukee.

His personal site can be found at http://dancody.org/

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.org Evolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.