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.
Archived Comments
Wow. Great timing, Ray. Did you know how badly I need this for my user group?!?
I am looking forward to part 2 through...
Leif
Ray:
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>
Some people, me included, just like to stay away from anything having to do with cfforms. I find this feature a bit too quirky for my taste.
Hey Ray:
Title of next series: "How to Retire on AdSense Revenue"
;-)
Enjoyed the last one. Looking forward to this one.
I think FF would be overkill for simple forms like this. I'm slowly backing off my previous "love" of FF. Not that I think they suck mind you - just that - well - I'm backing off from them a bit.
I may have jumped the gun a little here, but I do the following (after doing some basic checks on the form.email):
<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...
That probably won't work well. Some mail servers take a while to send email and sometimes it'll take a while for them to bounce back if there's no valid address.
Not FF - just a plain old vanilla cfform - format="html" - the validator still works, just gives a js popup.
Oh, heh. Call it "Old Fogey" disease. I like to do my own forms and validation. ;)
>That probably won't work well. Some mail servers take a while to send email and sometimes it'll take a while for them to bounce back if there's no valid address.
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.
An even better solution is the Email Verifier jar available from cfdev.com which will actually verify that the email address is correct, do DNS checks on the domain, and then establish a connetion to the remote mail server to check if the user does indeed exist.
$50, but well worth it if you send out any volume of mail.
Never, ever, enter an email into the db for a mailing list until you send an email and they click a link to validate it.
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'm with ziggy, nearly.
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.
ziggy: Validation - one of things I'm planning on doing at the end of the series is adding a validated flag, sending an email, and having the user end up on a page that sets the validated flag to true. I agree it is important, but felt it could wait till the end.
>>I agree it is important, but felt it could wait till the end.
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.
Well, ok then ziggy. Either way, it will be in the series. Not all sites require it, so I'm not starting off with it, but I will be adding it in the last step.
ray,
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
The CFC expects the personal data to come in, so you would need to pass that in. Then the query that inserts JUST the email would need ot be modded to insert the other info as well. With me so far?
ray,
thanks i got it working...
you pointed me in the right direction...
appreciated...
greg
Hi, I'm new to cold fusion and while I've given up on setting up cold fusion to work with my intel-based macbook pro (I tried the "difinitive guide" but encountered problems), I still want to use cold fusion and have set up a hosting plan that supports it.
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
The SQL file is a file that creates a sql table. You use it with a database client. So for example, for SQL Server you could use SQL Management Studio.
What if I'm using MySQL and I don't have access to SQL Management Studio? Is there any way I can create the table manually?
Your host will have _some_ way to work with MySQL. You need to ask them how folks typically manage their MySQL dbs.
Okay, I think I'm figuring this whole thing out. There is indeed a way to import SQL files into my database. However, I got this error upon doing so:
#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??
It was for SQL Server. You could just look at what is there and recreate it manually. It isn't a complex table. If this is your very first time doing DB stuff in general - well it may be a bit tough. I'm heading out for the weekend and if you can't get it figured out, let me know and I'll make you a MySQl script.
Yes, this is my first time doing any kind of DBA stuff. I managed to get the file open on my own and went through it hoping to figure out what it all means but all I can tell is that it creates a table named "subscribers" with two fields; "email" and "token", both of which are of type "Varchar" and have a limit of "255" and "35" respectively.
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...
Well varchar simply means the type of data. MySQL supports varchar, so just create a new table, use the same column names, and use varchar.
actually, is that 3 fields or just 2? Looks like there might be 3 (ID, email and token).
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!
Two fields. Don't worry about the attributes or even primary keys for not. Keep it simple.
Ray, I tried running this script but I got a weird error:
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.
You need to send me the other part of the error. It should be the detail/message.
I won't touch Hosting - it typically brings out 10,000 posts. :)
Ray:
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?
Yes, you can absolutely do this with ColdFusion. I can't answer it in a simple blog comment, but it is very possible.
Thanks for the great tutorial. any news on the second part? thank you!!
On this blog entry, look right above the comments. There is a related entries list.
I'd like to follow up on the discussion about validating the email address... Raymond you mentioned a $50 tag that will test it, and others talked about a simple link-back email (which is something I just implemented). The problem with the former should be obvious and the problem with the latter is that the "from" address in that initiating email gets all the bounce-back messages. ziggy is there some automatic address validation taking place in your process that you failed to mention that doesn't involve the human touch? I realize there's nothing like the real thing (baby), but I want to minimize failed mail notices in this process.
the SQL script to the lesson don't work for me. MySql 5.0 administrator. I'm new to this all and trying to add a email form to a site i'm building, the error is 1064 while running the script.
Lee, the SQL script is for SQL Server. You should be able to make the tables yourself in MySQL. There is only one table with 3 columns.
I said I was new to this and I mean new, so this is for MS sql sever? the db
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 notice you mention CF MX7 i'm using CF8 developement edition, Is this ok? I don't to waste anyone time. I'm I posting this in the right area? the option reads "Add Comment"
The file I used is for MS SQL Server. You can use other databases if you feel comfortable with them. The CFML code isn't database specifc. The table I used is named subscribers. It has 3 properies: id (integer), email (varchar), and token.
I can't speak to Cartweaver.
My code should work fine in CF8. CF is normally very backwards compatible.
Ray,
A table is created in the "maillist" dsn and is called "subscribers." I also noticed that you named a file the same as what your dsn is and have similar named functions and file names, "subscribe" and "subscribers." My question is if I wanted to create a second table within the same dsn and do another set of subscription emails, how would I do that? Creating the table is easy enough, but i'm a little lost and confused in the scripts. Help?
The code handles subscribing you to one list by adding you to a table. I'd simply make the subscribe method take an argument - the name of the list to subscribe to. In the method, you check the table that is used for that maillist.