In my last entry, I talked about the code behind sending out an entry to the list subscribers. While in general this was rather simple, I built in a variable replacement system that let the emails be personalized for each list member. Today I'm going to add a critical part of the application - the unsubscribe feature. As part of this feature I will discuss how the variable replacement system is used to help with this feature.
As always, I like to start with code and then describe what I'm doing. Today's code may be a bit confusing at first - but it will make sense:
<cfif structKeyExists(url, "token") and isValid("uuid", url.token)>
<cfset application.maillist.unsubscribe(url.token)>
<p>
You have been unsubscribed from the mail list.
We hope you subscribe again in the future!
</p>
<cfelse>
<p>
Sorry, but you were not unsubscribed. Please ensure that you have copied the URL correctly
from your mail client.
</p>
</cfif>
Seems a little brief, right? All I'm doing is checking for the existence of a URL variable named token. If it exists and is a valid UUID (who here also loves the isValid() function?), then I call the same unsubscribe I had mentioned in part two If for some reason the token didn't exist or wasn't valid, I present an error message asking folks to check their mail client.
So where did this value come from? As you can guess, it came from their email address. Remember that the unsubscribe method uses the token I associated with each user. Also remember that I allow for variable substitution in each email sent to the subscribers. I can put this all together and allow for a custom unsubscribe link by simply adding this to the email:
http://someurl/unsubscribe.cfm?token=%token%
When the user gets his or her email, the token value at the end will equal their own token. When clicked, everything will be automatic. They will be automatically removed from the list. Since I'm using UUIDs, this also ensures that no one else can unsubscribe a member since it will be (near) impossible to guess another UUID value.
That's it for this installment. Luckily this series is actually quite simple and not "exploding" as the Model-Glue series did. I have one more entry planned and that is a verification service. While the code so far should be enough, when it comes to mail services, you typically want to go above and beyond and really be sure the person wants to subscribe. By the way - is anyone reading this series? The first two entries got decent responses, but the last one had no comments.
Archived Comments
Yes, I amreading these posts, more than likely, quite a few people are following them, just not posting.
Nice to know. :) Well, I only have one more post to make. Thankfully this didn't turn into an 11 post series like the Model-Glue one. :)
I am reading and enjoying. Don't stop now! :)
When the Jedi Master teaches, Paduan Learner auditory sensors on highest input setting
My girlfriend does emailing thingies all the time, maybe now I can help her out... with Coldfusion!
I check for updates daily. Thanks for working on this during busy times and on weekends.
Yes, reading -- just began reading your blog b/c new to CF in general, and this series is helping me learn general syntax and best practices, as well as the task of making a mailing list. Can't wait to read more, both in future posts and as I search the archive. Thanks.
Keep up the great articles Ray! I don't know how you find the time to do it all! :)
Ray,
Thanks for the tutorial!
Will you throw in an extra benefit and show us how to do a mail spool (I think that is what it's called)? If we send out several hundred/thousand emails, it is nice to have the script pause for a few seconds every 100 emails or so. How is this done?
Don't forget that under MX, CF can send like 2 trillion emails an hour. Ok, not a trillion, but MX was updated to support a butt load of emails at one time. (I don't have the official numbers with me.)
One thing I'd love to see is how to create a system that will scan the email content (the actual HTML that will make up the email) and inserts some sort of tracking ability.
This will only work if the email is sent and received as HTML, but it could still be good to know how to do.
Still don't fancy padding this tutorial out? ;¬)
Actually, you could do this by adding links like so,
href="foo.cfm?r=%token%"
Of course, foo.cfm would need to notice it. I'd actually write something in Application.cfc to notice url.r and log it (token plus script loading).
Thanks for this tutorial, Ray. I'm a pretty experienced ColdFusioneer - I've been writing ColdFusion for a decade now. And as usual, reading through your stuff, I found myself saying, "Yes, I do that...Yes, I do that too...Yep...Me too....OH! that's a good idea, I hadn't thought of that!"
In short, even advanced coders can benefit from seeing a step-by-step series of someone else's code.
As usual, well written, easy to read, and the example code is clear and concise. Thanks.
Thank you. For those waiitng for p5, it is actually written, just not 100%, and since I didn't wrap it before leaving for Detroit, I probably won't get to it till Friday.
How would you recommend saving a standard unsubscribe form that is loaded at run-time; text in the database or a standard text form that is attached? Thanks again for the series.
John, I don't think I get what you mean. Why would you put the text of the unsubscribe fom n the db?
I was trying to think of the best way to save the email text so it wouldn't have to be keyed in by hand each time, and have the ability to be updated by a non-programmer. I was thinking more of a path in the db pointing to a file.
Oh, I get what you mean. You could simply auto-include it at the end I suppose. The problem with auto-inclusion is that for html emails, it may not show up right. What I'd probably do is add some generic help text to the form, like, "Don't forget to link to the unsub page" or some such.
Ray,
I understand that CF can send out tons of emails at a time, but I've gathered that if you send out that many emails all at once your domain can become registered in some db out there as a source of spam. Some developers have gotten around this by pausing the process for a few secconds every 100 or so emails.
Maybe it's just a conspiracy theory... ha, ha.
I can't imagine delaying the mail a bit would help. I just think you want to ensure you follow the rules and regulations. I'm not very knowledgeable about it, but I know you can find info online.
Shared hosts will often restrict your CFMAIL output (well, send you a concerned email if you are pushing out too many mails in too short a time).
The method I found and re-use is to send each message to a database instead of actually mailing it out. And then have a scheduled script that runs every so often, checks the count of rows in the database and sends a burst of messages (deleting each message after it's been sent).
Wish I could remember where I grabbed the code -- it was from a tutorial somewhere online.
Ray, I've finally gotten set up with a coldfusion hosting and am up to step 4 of your tutorial. Everything has worked so far up until now. When I attempt to send mail I get a weird error:
An exception occurred when invoking a event handler method from Application.cfc The method name is: onRequest.
followed by a bunch of stuff I don't understand. But you can look at it here if you want: http://www.dragonfireintera...
Just type in anything in the subject and body field to test it out.
Right, cause I need to tell you that...
Sorry.
Im trying my hosting company for help but they've admitted to me they just lost their coldfusion genius so I better submit a "ticket" for support.
anyway, thanks for any help in the matter.
Carlos
Add this to app.cfc/onapplicationstart:
<cfset application.maillistfrom = "ray@camdenfamily.com">
please change the addy
Ray, I already have that line up there (I had forgotten to replace your email address though). This is what my application.cfc file looks like:
<cfcomponent output="false">
<cfset this.name = "MailListDemo">
<cfset this.applicationTimeout = createTimeSpan(0,2,0,0)>
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfset application.maillist = createObject("component", "maillist").init("maillist")>
<cfset application.maillistfrom = "carlos@dragonfireinteractive.com">
<cfreturn true>
</cffunction>
<cffunction name="onApplicationEnd" returnType="void" output="false">
<cfargument name="applicationScope" required="true">
</cffunction>
<cffunction name="onRequestStart" returnType="boolean" output="false">
<cfargument name="thePage" type="string" required="true">
<cfif structKeyExists(url, "reinit")>
<cfset onApplicationStart()>
</cfif>
<cfreturn true>
</cffunction>
<cffunction name="onRequest" returnType="void">
<cfargument name="thePage" type="string" required="true">
<cfinclude template="#arguments.thePage#">
</cffunction>
<cffunction name="onRequestEnd" returnType="void" output="false">
<cfargument name="thePage" type="string" required="true">
</cffunction>
<cffunction name="onError" returnType="void" output="true">
<cfargument name="exception" required="true">
<cfargument name="eventname" type="string" required="true">
<cfdump var="#arguments.exception#" label="Error">
</cffunction>
<cffunction name="onSessionStart" returnType="void" output="false">
</cffunction>
<cffunction name="onSessionEnd" returnType="void" output="false">
<cfargument name="sessionScope" type="struct" required="true">
<cfargument name="appScope" type="struct" required="false">
</cffunction>
</cfcomponent>
You forgot to reinit the app. :) I added ?reinit=1 to the url and now I get a new error saying you didn't set a mail server in the cf admin. Once you do that, it will work.
You forgot to reinit the app. :) I added ?reinit=1 to the url and now I get a new error saying you didn't set a mail server in the cf admin. Once you do that, it will work.
Thanks Ray. Btw, at the risk of coming off like a total newbie, what do you mean by "reinit the app"?
When should I have done that?
Also, I thought that you set the mail server by specifying a valid email address (from my domain) on line 8 of application.cfm. Is this not the case?
Ray, I just got off the phone w/ my hosting company. Turns out that because I'm on shared hosting I need to use my smtp server to send it out (the SMTP server is not set up within the CF administrator, this is specified in the CF tag) so is there something I can do or add to my application.cfc file to get it to work?
According to the knowledge base articles, they advise me to do the follwoing (but Im concerned I will mess up your app if I try it):
For those on ColdFusion MX hosting plans, you can use the following code:
server="mail.YourDomain.com"
username=“AnyEmail@YourDomain.com”
password=“YourEmailPassword”
from="AnyEmail@YourDomain"
to="AnyEmail@AnyDomain.com"
subject="Any Subject">
This is the body of the message.
Any ideas?
No, it won't break my app. Just find any cfmail tag and add the server, username, and password attributes. The other attributes should exist.
Sorry for the delay - been a busy day.
Sorry the 'reinit' wasn't clear. Model-Glue apps cache a lot of their data. To reinit the app, you add ?init=true to the URL.
Awesome! It worked, thanks Ray!
Btw, what the heck is a model glue app?
Sorry, I was thinking of another app. :) Glad you got it working.