Sending Random SMS Text Messages With ColdFusion To Make Her Feel Loved
If you are anything like me, then you find coding in ColdFusion so much fun that when you're doing it, you tend forget about most other things like work and friends and loved ones and how early the cat wakes you up in the morning or why it's so freakin' hot in the office. As a result, those close to you might feel neglected. Here is a small, stand alone ColdFusion application that will help maintain the quality of your relationship by periodically prompting you to send a text message (SMS Message) to your girl friend (yes, I am aware of how sad this is). All you need are a few files and a scheduled ColdFusion task.
The first file is the XML data file that will define the text messages that the system will randomly select from:
<messages>
<message>
Hey honey, I was just thinking of you
</message>
<message>
Wish I was at home with you.
</message>
<message
mintime="4:00 PM">
This day is so long! Why can't we be home together already?!?
</message>
<message
mintime="3:00 PM"
days="Fri">
Thank god it's Friday. Just a few more hours till we can spend the weekend together.
</message>
<message
maxtime="11:00 AM">
I know I just saw you a few hours ago, but I am already missing you something wicked.
</message>
</messages>
Notice that there are some message node attributes being used:
mintime: This is the minimum value at which time a message can be considered valid for selection. For instance, the message "The day is so long" doesn't really make sense in the morning; and so, it is not valid until 4:00 PM.
maxtime: This is the maximum value at which time a message can be considered valid for selection. For instance, morning-related messages shouldn't be used in the afternoon.
days: This is a comma-delimited list of three-letter days in which the message is valid. For instance, weekend-related messages should not be valid early in the week, and therefore might have a Thr,Fri days attribute value.
You can, of course, put as many messages that you want. Each text message has a max length that it can be, which depends on the cellular carrier. I am not doing any validation checking for that.
The next file is the Application.cfc ColdFusion component where we define the application initialize it. The bulk of the initization imports the XML data files. We import not only the SMS message XML file, we also import a app_data.xml data file. This file contains information about the current state of the application including the time at which the next message can be send and the last three message IDs that were used.
<cfcomponent
output="true"
hint="Handle the application level events.">
<!--- Define application. --->
<cfset THIS.Name = "SMS Messages {#Hash( GetCurrentTemplatePath() )#}" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 2, 0, 0, 0 ) />
<cfset THIS.SessionManagement = false />
<cfset THIS.SetClientCookies = false />
<!--- Set page request settings. --->
<cfsetting
requesttimeout="20"
showdebugoutput="false"
enablecfoutputonly="true"
/>
<cffunction
name="OnApplicationStart"
access="public"
returntype="boolean"
output="false"
hint="Fires when the application is first run or manually reset.">
<!--- Define the local scope. --->
<cfset var LOCAL = StructNew() />
<!---
Since this application might get called
manually, we cannot depend on a single threaded
environment. But, we also have to consider the
environment... what are the chances that two
concurrent requests might come in? Zero. But, in
the stride of good practice, I will implement good
locking proactices anyway.
--->
<cflock
scope="APPLICATION"
type="EXCLUSIVE"
timeout="10"
throwontimeout="true">
<!--- Clear the application scope. --->
<cfset StructClear( APPLICATION ) />
<!---
Read in the XML file that contains are
defines our library of random text messages
(SMS Messages).
--->
<cffile
action="READ"
file="#ExpandPath( './messages.xml' )#"
variable="LOCAL.MessageData"
/>
<!---
Parse the message data into an application-
scoped XML data structure.
--->
<cfset APPLICATION.MessagesXML = XmlParse(
LOCAL.MessageData
) />
<!---
The XML is great, but for our purposes, it
would be easier to work with a query object
(we are going to need to query for messages
based on dynamic criteria). Therefore, we
are going to convert our XML object into a
ColdFusion query object.
--->
<cfset APPLICATION.Messages = QueryNew(
"id, message, min_time, max_time, days",
"INTEGER, VARCHAR, DECIMAL, DECIMAL, VARCHAR"
) />
<!---
Now, let's get a short hand reference to the
messages array within our XML document.
--->
<cfset LOCAL.Messages = APPLICATION.MessagesXML.Messages.XmlChildren />
<!---
Loop over the messages and each one of them
to the message query.
--->
<cfloop
index="LOCAL.MessageIndex"
from="1"
to="#ArrayLen( LOCAL.Messages )#"
step="1">
<!---
Get a short hand reference to the message
(XML Node) that we are currently looking at.
--->
<cfset LOCAL.Message = LOCAL.Messages[ LOCAL.MessageIndex ] />
<!--- Add a new row to the query. --->
<cfset QueryAddRow( APPLICATION.Messages ) />
<!---
Add the ID of the message. This is just
going to be the index of the XML child.
--->
<cfset APPLICATION.Messages[ "id" ][ LOCAL.MessageIndex ] = JavaCast( "int", LOCAL.MessageIndex ) />
<!---
Store the message. Be sure to trim the
message we the XML data might have white
space. When sending a SMS text message,
data efficiency is HIGH priority.
--->
<cfset APPLICATION.Messages[ "message" ][ LOCAL.MessageIndex ] = JavaCast( "string", Trim( LOCAL.Message.XmlText ) ) />
<!---
When storing the min time, check to see
if the attribute exists and is a valid
date/time object. If it does not exist,
then we are going to store a null value.
--->
<cfif (
StructKeyExists( LOCAL.Message.XmlAttributes, "mintime" ) AND
IsNumericDate( LOCAL.Message.XmlAttributes.mintime )
)>
<!--- Store the time as a decimal. --->
<cfset APPLICATION.Messages[ "min_time" ][ LOCAL.MessageIndex ] = JavaCast( "float", LOCAL.Message.XmlAttributes.mintime ) />
<cfelse>
<!---
The time did not exists. Store a NULL
value that can be checked in our query
of queries.
--->
<cfset APPLICATION.Messages[ "min_time" ][ LOCAL.MessageIndex ] = JavaCast( "null", 0 ) />
</cfif>
<!---
When storing the max time, check to see
if the attribute exists and is a valid
date/time object. If it does not exist,
then we are going to store a null value.
--->
<cfif (
StructKeyExists( LOCAL.Message.XmlAttributes, "maxtime" ) AND
IsNumericDate( LOCAL.Message.XmlAttributes.maxtime )
)>
<!--- Store the time as a decimal. --->
<cfset APPLICATION.Messages[ "max_time" ][ LOCAL.MessageIndex ] = JavaCast( "float", LOCAL.Message.XmlAttributes.maxtime ) />
<cfelse>
<!---
The time did not exists. Store a NULL
value that can be checked in our query
of queries.
--->
<cfset APPLICATION.Messages[ "max_time" ][ LOCAL.MessageIndex ] = JavaCast( "null", 0 ) />
</cfif>
<!---
When storing the valid days, check to see
if the value exists. If it does not exist,
then we are going to store a NULL value.
--->
<cfif StructKeyExists( LOCAL.Message.XmlAttributes, "days" )>
<!--- Store the days. --->
<cfset APPLICATION.Messages[ "days" ][ LOCAL.MessageIndex ] = JavaCast( "string", LCase( LOCAL.Message.XmlAttributes.days ) ) />
<cfelse>
<!---
Days did not exists. Store a NULL
value that can be checked in our
query of queries.
--->
<cfset APPLICATION.Messages[ "days" ][ LOCAL.MessageIndex ] = JavaCast( "null", 0 ) />
</cfif>
</cfloop>
<!---
In addition to the actual message data, we
are going to need data about the state of
the application. Let's set up a default
data structure value.
--->
<cfset APPLICATION.Settings = StructNew() />
<!---
This is the file path to our application
settings data file. We will need this to init
here, but also to update the file later.
--->
<cfset APPLICATION.Settings.FilePath = ExpandPath(
"./app_data.xml"
) />
<!---
We don't want to hassle ourselves... I mean we
want her to feel loved, but come on! Let's only
remind our selves on a random time that is not
too short. THis value will be the time at which
we can next send a message. For default, we
will use Now() (a new message can be sent ASAP).
--->
<cfset APPLICATION.Settings.NextMessage = Now() />
<!---
This will be the an array of the last three IDs
used. In our hectic, ColdFusion loving days,
how can we be expected to remember which text
messages have been sent.
--->
<cfset APPLICATION.Settings.PrevMessages = ArrayNew( 1 ) />
<!--- <br>
Check to see if our app settings file exists.
If it does, then we are going to want to pickup
where we left off.
--->
<cfif FileExists( APPLICATION.Settings.FilePath )>
<!--- Read in the XML data file. --->
<cffile
action="READ"
file="#APPLICATION.Settings.FilePath#"
variable="LOCAL.SettingsData"
/>
<!---
Convert the XML data into the Application
settings structure. Do not store this
directly into the application as we don't
want to lose our file path. Plus, you never
know if the structure has changed for what
ever reason.
--->
<cfwddx
action="WDDX2CFML"
input="#LOCAL.SettingsData#"
output="LOCAL.Settings"
/>
<!---
Check to see if we have the next message
data point and that it is a valid date.
--->
<cfif (
StructKeyExists( LOCAL.Settings, "NextMessage" ) AND
IsNumericDate( LOCAL.Settings.NextMessage )
)>
<!--- Store the setting value. --->
<cfset APPLICATION.Settings.NextMessage = LOCAL.Settings.NextMessage />
</cfif>
<!---
Check to see if we have the previous
messages data point and that it is a valid
data array..
--->
<cfif (
StructKeyExists( LOCAL.Settings, "PrevMessages" ) AND
IsArray( LOCAL.Settings.PrevMessages )
)>
<!--- Store the setting value. --->
<cfset APPLICATION.Settings.PrevMessages = LOCAL.Settings.PrevMessages />
</cfif>
</cfif>
</cflock>
<!--- Return out. --->
<cfreturn true />
</cffunction>
<cffunction
name="OnRequestStart"
access="public"
returntype="boolean"
output="false"
hint="Fires prior to page processing.">
<!--- Define arguments. --->
<cfargument
name="TargetPage"
type="string"
required="true"
/>
<!---
Check to see if we are manually resetting the
application. We will know to do this if the
query param "reset" exists in the URL.
--->
<cfif StructKeyExists( URL, "reset" )>
<!---
Manually call the OnApplicationStart()
event method. Let the App method take care
of locking. We will not care at this point.
--->
<cfset THIS.OnApplicationStart() />
</cfif>
<!--- Return out. --->
<cfreturn true />
</cffunction>
<cffunction
name="OnRequest"
access="public"
returntype="void"
output="true"
hint="Fires after pre-page processing is complete. Defines which template will actually be run for the request.">
<!--- Define arguments. --->
<cfargument
name="TargetPage"
type="string"
required="true"
/>
<!--- Include the requested page. --->
<cfinclude template="#ARGUMENTS.TargetPage#" />
<!--- Return out. --->
<cfreturn />
</cffunction>
</cfcomponent>
As you can see, we are converting the SMS message data into a ColdFusion query object. We are doing this because based on the XML attributes, our message selection needs to have dynamic criteria. Nothing is better suited to this than a ColdFusion query of queries (man those QoQs really rock hard core!).
The NextMessage time stamp is there to limit the number of SMS text message prompts that we receive each day. This application is going to be launched via a scheduled task (which can be set to fire as often as you like). In order for "affection" to seem more natural, the frequency of the SMS text messages needs to seem more random. The NextMessage date/time value will allow the scheduled task to be run repeatedly without launching so many message prompts.
The PrevMessages array is a way for the system to not prompt you for the same message too often. That would sound far to mechanical. This helps take the burden off of you, the ColdFusion programming enthusiast, from having to keep all the outgoing SMS text messages straight in your head.
Now that we have the application defined, we are going to employ a two part process for sending out the text messages. We cannot just blindly send them out - what happens if we left our phones at home? What happens if we are at lunch with our targeted loved one? Getting a text message at those times would obviously be bad and ruin the party. Therefore, the system will email you with a confirmation prompt. Here is the code that selects the message (notice the dynamic criteria ColdFusion query of queries):
<!--- Get the current time. --->
<cfset dtNow = Now() />
<!---
Check to see if this is a weekday. We only care about
doing this during the week when we are more than likely
NOT with our special someones. Additionally, we only want
this to be for work hours - 9AM - 6PM.
Additionally, we only want to send a message if we are
at or after the time of the next message slot.
--->
<cfif (
(DayOfWeek( dtNow ) GTE 2) AND
(DayOfWeek( dtNow ) LTE 6) AND
(Hour( dtNow ) GTE 9) AND
(Hour( dtNow ) LTE 18) AND
(Now() GTE APPLICATION.Settings.NextMessage)
)>
<!---
Based on the current time, create a time-only
data value. This will be used in our min/max
query criteria.
--->
<cfset dtTime = CreateTime(
Hour( dtNow ),
Minute( dtNow ),
Second( dtNow )
) />
<!---
Query for valid messages based on the date
and time criteria.
--->
<cfquery name="qMessage" dbtype="query">
SELECT
id,
message
FROM
APPLICATION.Messages
WHERE
(
min_time IS NULL
OR
min_time <= <cfqueryparam value="#dtTime#" cfsqltype="CF_SQL_FLOAT" />
)
AND
(
max_time IS NULL
OR
max_time >= <cfqueryparam value="#dtTime#" cfsqltype="CF_SQL_FLOAT" />
)
AND
(
days IS NULL
OR
days LIKE <cfqueryparam value="%#LCase( DateFormat( dtNow, 'ddd' ) )#%" cfsqltype="CF_SQL_VARCHAR" />
)
<!---
Check to make sure we are not selecting a
recently used message.
--->
AND
id NOT IN (
<cfqueryparam value="#ArrayToList( APPLICATION.Settings.PrevMessages )#,0" cfsqltype="CF_SQL_INTEGER" list="yes" />
)
ORDER BY
id ASC
</cfquery>
<!---
Check to make sure that we have at least
one message to choose from.
--->
<cfif qMessage.RecordCount>
<!--- Select a random row for the query. --->
<cfset intMessage = RandRange(
1,
qMessage.RecordCount
) />
<!---
Send email for confirmation. We don't want to just
fire off these text messages without confirmation
or we might text our ladies at highly inappropriate
times (such as when we are currently with them).
--->
<cfmail
to="xyz@xxxxxxxxxx.com"
from="xyz@xxxxxxxxxx.com"
subject="Romantic SMS Text Message Confirmation"
type="HTML">
<div style="font-size: 18px ; line-height: 27px ;">
<p>
Hey Romeo, do you want to send out the
following text message:
</p>
<blockquote style="font-style: italic ;">
#qMessage[ "message" ][ intMessage ]#
</blockquote>
<p>
<br />
<br />
</p>
<p>
<a
href="http://#CGI.server_name##GetDirectoryFromPath( CGI.script_name )#confirm_message.cfm?id=#qMessage[ "id" ][ intMessage ]#"
>SEND TEXT MESSAGE</a>
</p>
</div>
</cfmail>
</cfif>
</cfif>
Notice that the bulk of the select message ColdFusion template is only run during the week and between the hours of 9am to 6pm and only if the current time is greater than or equal to the time of NextMessage. This is how we prevent too many text message email prompts from going out. Running that ColdFusion template will result in an email like this:
Hey Romeo, do you want to send out the following text message:
Wish I was at home with you.
SEND TEXT MESSAGE
If you read the text message and want to send it out, you simply click on the link "Send Text Message" and the text is sent out. The ColdFusion template responsible for sending out the SMS text message is confirm_message.cfm:
<!--- Param the URL variables. --->
<cftry>
<cfparam
name="URL.id"
type="numeric"
default="0"
/>
<cfcatch>
<cfset URL.id = 0 />
</cfcatch>
</cftry>
<!--- Query for the given ID. --->
<cfquery name="qMessage" dbtype="query">
SELECT
message
FROM
APPLICATION.Messages
WHERE
id = <cfqueryparam value="#URL.id#" cfsqltype="CF_SQL_INTEGER" />
</cfquery>
<!---
Check to see if the passed ID matches a message
that we have defined in our application.
--->
<cfif qMessage.RecordCount>
<!---
Use the ColdFusion CFMail tag to send the
text message using the celluar carrier's
mobile phone email address.
--->
<cfmail
to="0123456789@vtext.com"
from="9876543210@vtext.com"
subject="">
<!--- Send the text part. --->
<cfmailpart
type="text"
>#qMessage.message#</cfmailpart>
</cfmail>
<!---
Now that the message has been sent, we want to select
a random interval of time only after which we can then
send out another text message. We don't want to send
them out too often, or she will suspect something.
Let's add between 2 and 8 hours. We are going to create
the time span with minutes (not hours) to get a more
random distribution.
--->
<cfset APPLICATION.Settings.NextMessage = (
Now() +
<!--- Randomly pick time interval. --->
CreateTimeSpan(
0,
0,
RandRange( (2 * 60), (8 * 60) ),
0
)
) />
<!---
Add this message ID to the array of previous messages
so that we don't have to mentally keep track of which
messages have already been sent.
--->
<cfset ArrayAppend(
APPLICATION.Settings.PrevMessages,
URL.id
) />
<!---
We only want to keep track of the last three
message. If we have more than that, we need to pop
one off the top.
--->
<cfif (APPLICATION.Settings.PrevMessages.Size() GT 3)>
<!--- Pop the first one off. --->
<cfset ArrayDeleteAt(
APPLICATION.Settings.PrevMessages,
1
) />
</cfif>
<!---
At this point, our application settings have
been updated. Now, we just need to update our
data file in case our application crashes or
has to be reinitialized. Convert the settings
to WDDX.
--->
<cfwddx
action="CFML2WDDX"
input="#APPLICATION.Settings#"
output="REQUEST.SettingsData"
/>
<!--- Write the app settings to disk. --->
<cffile
action="WRITE"
file="#APPLICATION.Settings.FilePath#"
output="#REQUEST.SettingsData#"
/>
</cfif>
<cfoutput>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Text Message Confirmation</title>
</head>
<body>
<!--- Check to see if a message was found. --->
<cfif qMessage.RecordCount>
<p>
The following message has been sent:
</p>
<blockquote>
#qMessage.message#
</blockquote>
<cfelse>
<p>
<em>The given message could not be found</em>.
</p>
</cfif>
</body>
</html>
</cfoutput>
Notice that the SMS text message is sent using the Verizon Wireless cellular phone email account and a CFMail tag. This is the easiest way to accomplish this task. Once the message does get sent out, we update the system - the next available message time is updated and the current message ID is appended to the previous messages. We are storing the application setting data as XML via WDDX. WDDX, while I have some issues with it, provides a really easy and fast way for us to serialize and deserialize simple data for simple uses.
The To/From vtext numbers are hard coded, but these could easily be part of the application settings structure. Frankly, I just didn't think of it till the application demo was already done.
Please note that this post is just in good fun and I am NOT really that sad :)
Want to use code from this post? Check out the license.
Reader Comments
If I did that I would be bound to be found out. I would be off sick one day when the message "I wish I was at home with you" would pop up on her phone. Then she would think I really meant to send the message to someone else......
Maybe the plot for my next sitcom!
Hurrah ! I'm *so* using this :-)
@Kevin: The confirmation link should stop 'you' sending if you are ill.
Ben,
you are not sad at all. This is brilliant!! He he, I'm absolutely going to make use of this.
I am glad that some people can appreciate the humor in this :)
Btw, none but the first snippet download links work. Just FYI.
@Boyan,
Yeah the color coding I implemented a while back killed the code download :( I am working on fixing that. Sorry.
The code pop-up and download code features are back in effect.
@Ben
You are the man as usual. Thanks!
Doh! I forgot...just a simple idea - it would be really could if you could provide a way to download all the files/snippets in one zip file. You can use something like CFC Zipper - http://www.intersuite.com/client/index.cfm/2007/4/5/CFC-Zipper-15
@Boyan,
I think that is a tremendous idea. Consider it "in the works."
@Ben
Great! You got a wish list somewhere? Or something of that sort? I want to get you something for all your help.
@Boyan,
I appreciate that, but your most excellent suggestions are payment enough. Check it out:
www.bennadel.com/index.cfm?dax=blog:722.view
Not quite there yet, but just about. You're like an idea machine; I love it!
@Ben,
yeah, I saw that. Thanks! You are awsome! Thanks for the compliment but still - make a wishlist. I'm sure people will be happy to buy you a thing or two. I can't beleive you don't have one yet with all the help/code you provide to the CF community.
Just something I noticed on your front page - under "Recent Snippets", the last "recent" entry is from Sep-28-2006. Not sure if that's by design but thought I'd mention it.
Yeah the Snippets have basically fallen from grace. When I first started the blog I had this vision that I would blog about concepts and then provide examples via the Snippets section... but, once I really started going, what I realized was that I put the concepts AND snippets directly into the blog content. I tried duplicating the examples into the snippets as well, but that just because too much effort with too little time.
What I am planning to do is create a "Projects" section in the coldfusion section ( www.bennadel.com/coldfusion/ ) under which I will put documentation and snippets for bigger things like the POIUtility and SkinSpider and what not.
I will keep the snippets section linkable, but will probably remove it from the primary nav and the homepage. We shall see... its all up in the air at this point.
Great,this is really informative.
Great piece of writing, I really liked the way you highlighted so me really important and significant points on ending-Random-SMS-Text-Messages-With-ColdFusion-To-Make-Her-Feel-Loved. Thanks so much, I appreciate your work.
SMS Reseller
So informative things are provided here, I really happy to read this post about Sending Random SMS Text Messages With ColdFusion To Make Her Feel Loved, I was just imagine about it and you provided me the correct information I really bookmark it, for further reading, So thanks for sharing the information.
http://www.experttexting.com/sms-reseller/