Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Clark Valberg
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Clark Valberg ( @clarkvalberg )

Sending Random SMS Text Messages With ColdFusion To Make Her Feel Loved

By on
Tags:

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

6 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!

95 Comments

@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.

15,640 Comments

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.

1 Comments

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.

1 Comments

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/

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel