Creating a Mailing List in ColdFusion (Part 1)
One of the features you may be asked to add to a web site is a mailing list. A mailing list lets people subscribe to get the latest news and updates from whatever your site may be offering. In this series I'll talk about how to add this feature to your web site. Before going on, let me go over the features of this application and what it will provide.
- Users will be able to fill out a form to subscribe to the mailing list.
- Administrators will be able to see a list of people subscribed. They will also be able to remove or add a user manually.
- Administrators will be able to send an email to everyone in the list.
- Users will be able to remove themselves from the list.
There it is, short and sweet. Of course, I remember saying my Model-Glue series would be simple as well, and we all know how that turned out.
In this entry, I'll cover step one, allowing users to subscribe to the list. A client may ask for many different things from their users, but at minimum, all I need to ask for is an email address. So let's start off with the simplest of forms and validation:
<cfparam name="form.emailaddress" default="">
<cfset showForm = true>
<cfif structKeyExists(form, "subscribe")>
<cfif isValid("email", form.emailAddress)>
<cfset application.maillist.subscribe(form.emailaddress)>
<cfset showForm = false>
<cfelse>
<cfset error = "Your email address isn't valid.">
</cfif>
</cfif>
<h2>Subscribe to Foo</h2>
<cfif showForm>
<cfif structKeyExists(variables, "error")>
<cfoutput>
<p>
<b>#error#</b>
</p>
</cfoutput>
</cfif>
<p>
<form action="subscribe.cfm" method="post">
<table>
<tr>
<td>Your Email Address</td>
<cfoutput><td><input type="text" name="emailaddress" value="#form.emailaddress#"></td></cfoutput>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="subscribe" value="Subscribe"></td>
</tr>
</table>
</form>
</p>
<cfelse>
<p>
Thank you for subscribing!
</p>
</cfif>
So there really isn't anything too special about this form, but let talk about some particulars. First off - the form just checks for an email address. As I said, a client may want a lot more information about the user, however none of that really applies to what the goal is - subscribing to a list. Since the form is somewhat simple, I only needed to validate the email field. Note that I used the isValid function, so this code will only work in ColdFusionMX 7. (Just this one line though.)
After ensuring the email address is actually valid, I pass off to the main CFC I'm going to be using throughout this series:
<cfset application.maillist.subscribe(form.emailaddress)>
In case your curious, I'm loading the CFC via onApplicationStart. (Included in the Application.cfc file that is in the zip.) Now let me show you the CFC. Right now it is pretty short, but I'll be adding to it as the series goes on.
<cfcomponent displayName="MailList" output="false">
<cffunction name="init" returnType="maillist" output="false" access="public">
<cfargument name="dsn" type="string" required="true">
<cfset variables.dsn = arguments.dsn>
<cfreturn this>
</cffunction>
<cffunction name="subscribe" returnType="boolean" output="false" access="public"
hint="Adds a user to the mailinst list, if and only if the person wasn't already on the list.">
<cfargument name="email" type="string" required="true">
<cfset var checkIt = "">
<cfif not isValid("email", arguments.email)>
<cfthrow message="#arguments.email# is not a valid email address.">
</cfif>
<!--- only add if the user doesn't already exist. --->
<cflock name="maillist" type="exclusive" timeout="30">
<cfquery name="checkIt" datasource="#variables.dsn#">
select email
from subscribers
where email = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.email#">
</cfquery>
<cfif checkIt.recordCount is 0>
<cfquery datasource="#variables.dsn#">
insert into subscribers(email,token)
values(<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.email#">,<cfqueryparam cfsqltype="cf_sql_varchar" value="#createUUID()#">)
</cfquery>
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cflock>
</cffunction>
</cfcomponent>
Outside of the init method, right now the only code here is the subscribe method. The subscribe method does a few basic things. First it checks to ensure the email address sent to it was valid. Why am I doing this again? I mean, I know my form checked, so why bother? Well I think it is important to remember that the CFC is separated from the client enough that the CFC may not what to actually trust the client. The CFC should validate information as much, if not more than, the client is. So once again I use the isValid function for a quick way to validate email addresses. If you plan on using my code on a MX6 machine, this is the one line you will need to change. The rest of the method is rather simple. I see if the email address already exists, and if not, I add one to the subscribers table. Note two things. First off - I use a lock around the code block. This lock ensures that if I have multiple requests coming in with the same email address, I don't accidentally insert the same email address more than once. I could use the database to handle that as well, but I wanted to handle it on the CF side for this demonstration. Secondly, notice I insert a UUID into a column named token. I'll be explaining that in step four.
So - if you want to test this, download the zip attached to this article. Create a database with the included SQL file and make a DSN named maillist. Copy the files to a folder and run subscribe.cfm. In the next session I'll add a subscriber administrator tool. This will let the admin see who is subscribed and quickly add or remove individuals.
Comments
Not to question your ways, but why not use a cfform and the built in validate="email"? Are there advantages/disadvantages to either method that I'm not aware of?
<cfform action="subscribe.cfm" method="post">
<table>
<tr>
<td>Your Email Address</td>
<cfoutput><td><cfinput type="text" name="emailaddress" value="#form.emailaddress#" validate="email" validateat="onsubmit" message="Your email address isn't valid."></td></cfoutput>
</tr>
<tr>
<td> </td>
<td><cfinput type="submit" name="subscribe" value="Subscribe"></td>
</tr>
</table>
</cfform>
Title of next series: "How to Retire on AdSense Revenue"
;-)
Enjoyed the last one. Looking forward to this one.
<cfset mailsuccess=true>
<cftry>
<cfmail>send the signup email</cfmail>
<cfcatch><cfset mailsuccess=false></cfcatch>
</cftry>
<cfif mailsuccess>
<insert user into database>
<cfelse>
Tell them it failed.
</cfif>
This way, you *really* know if they've entered a valid email address before inserting them in the db...
Very true, however, I'm not using it to trap bounces - just to be sure that the <cfmail> tag is happy with the destination email *before* it gets into the database.
$50, but well worth it if you send out any volume of mail.
The email should have a link with a url to enter the email in the db and use a var e=encodedEmailAddress. Do not use "email" for the variable or it will count towards spam scores.
And always remember: the universe has decreed that butterfly catching is evil.
I put the email address in the db with a UUID as an identifier (sounds like Ray is doing the same using a field called token). I send the provided address an email with a confirmation URL which has the identifer in the address. The user clicks the URL in the email and I ask them to confirm the email address again. If the UUID identifier matches with the email address then all's well so I capture the confirmation date/time and this is used as the flag to say the user is valid to go on the mailing list.
I then have a scheduled task to delete unconfirmed applications to join the mailing list that were inserted over, say, 10 days ago.
I don't see the point in doing anything at all until they validate it for you.
I've run my own mailing list for years. Tons of junk gets entered otherwise, even addresses that are good but will never get delivered anyway because of filters, or someone adds someone else, etc.
thank you for the mailist tutorial...i've learned alot about CFCs and how the work.... i'm trying to customize the form, for a personal newsletter mailist..by adding more info to the db..
...i have one small simple question for you if i could ask...i'm just starting out in CF coding 'newbie'...new to CFCs...just so u know...k
i'd like to add a few fields like 'name' and 'city' to the form on the subscribe.cfm page...but i get an error when i do that...
what do i have to change on that page & maybe some other code on the mailist.cfc aswell?....
any code would really help me out..
thank you in advance...
btw...i attend your CFC session at CFUnited this year...good work..you have real good presentation skills when u explain coldfusion and i appreciated that...thanks...
greg
thanks i got it working...
you pointed me in the right direction...
appreciated...
greg
For my first project I decided to build a mailing list since I thought it would be a good way to start (I have your book, btw). I just can't figure out what to do with that tables.sql file in the zip file. There is no application associated with .sql in my laptop to get it to open and my hosting provider (Godaddy) doesn't have a way for me to open .sql files and create a database out of them.
Would appreciate any feedback you might be able to spare on this. Great site, looks like a wealth of knowledge I'll be ttapping into in the future.
Thanks,
Carlos
#1064 - You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near 'if exists (select * from dbo.sysobjects where id = object_id(N'
Godaddy tells me they are using MySQL 4, I take it your SQL syntax is for MySQL 5??
I can't figure out what the Attribute value is but I think the Null value is "NOT NULL".
That's pretty much all I could make of it. Not sure what the other stuff is for. Do you know if there's a script converter somewhere that would be able to convert SQL scripts to MySQL scripts?
I'd hate to bother you with my stupid inability to read a simple script...
And what about the Attribute value? and are they all auto_increment (i would think only id would be...)?
Also, I have id set as primary and email set as unique, but did not specify anything for token, is this ok?
Thanks again!
The error occurred in D:\Hosting\los1sol\Application.cfc: line 35
33 : <cfargument name="eventname" type="string" required="true">
34 :
35 : <cfdump var="#arguments.exception#" label="Error">
36 : </cffunction>
37 :
You should know that I wasn't able to name the DSN "maillist" because the name is automatically generated by the system. However, when I looked into the files you created in my hosting directory, I notice that some things have changed. Specifically, places that once had "mailist" have now been changed to "mysqlcf_subscriptionLT" which happens to be my DSN name. So I think that's okay.
But I still have no idea what that error message means.
To further confuse things, I switched hosting packages to a windows box (still using godaddy) which now allows me to use a SQL server if I want (i'm still using mysql). I would normally switch to that and use the table.sql script you created but I can't figure out how to actually run it in their SQL admin page (and they don't know either).
So just a bit off-topic, but can you recommend a good, reasonably priced, coldfusion hosting service? One that would be able to help a novice like myself?
Thansk a bunch.
I won't touch Hosting - it typically brings out 10,000 posts. :)
I just started using Macromedia Studio 8 and have developed a webpage. I would like to make my page interactive where the person viewing the page can post a comment or question. I would like that question to post directly to my email (with out going through their email client) and then have the question or comment disappear upon pressing submit. Is there a way to do this using cold fusion, dreamweaver or other software?
should consist of a firstname, lastname and email address? Is this correct?
Then this is simple enough. if I am correct? then, I'm using a Cartweaver
shopping cart, can I us the data in this db? lets say any table/tables that
contain the first last and email information. if is sound like I'm up on
this :) I'm not! just trying to learn. thanks so much for you so speedy
reply :)
I can't speak to Cartweaver.
My code should work fine in CF8. CF is normally very backwards compatible.

I am looking forward to part 2 through...
Leif