Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Uploading And Sending MMS (Multimedia Messaging Service) Messages With Twilio And ColdFusion

By Ben Nadel on
Tags: ColdFusion

Last year, I blogged about sending MMS (Multimedia Messaging Service) messages using the Twilio API. However, at the time, Twilio only supported MMS messages on Canadian phone numbers and US short-codes (ie, those 5-digit phone numbers). Yesterday, however, Twilio finally announced full MMS support on all US phone numbers. So, naturally, I had to make sure my previous blog post worked.

My first blog post on Twilio and MMS messages was written about a year ago, so I wasn't sure if it was still relevant. Much to my supreme happiness, however, the demo worked perfectly, sending out an MMS message, without having to change a single line of code. Now, I could have just said, "Hey, this just works." But, what would be the fun in that? We're programmers, dangit - we have to "build" stuff. So, instead, I wanted to re-tool the previous example to use an upload form.

In the following code, I'm taking my prior demo and working it into an upload form where the user can select a target phone number, enter a message, and upload a photo. For the sake of simplicity, I'm hard-coding the requirement for JPEG images. This way, I don't have to worry about checking mime-types.

When the user selects the photo, I first upload it to my server. But, since I'm actually on a local development environment, the upload can't be publicly available. As such, I then need to push the upload to Amazon S3 where it can be accessed by the Twilio servers.

<cfscript>

	// Include the credentials for both Amazon S3 as well as Twilio.
	include "./credentials.cfm";


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// Param the form inputs.
	param name="form.submitted" type="boolean" default="false";
	param name="form.phone" type="string" default="";
	param name="form.message" type="string" default="";
	param name="form.upload" type="string" default="";

	errorMessage = "";

	// Check to see if the user has submitted the form.
	if ( form.submitted ) {

		try {

			// Sanitize the phone number.
			form.phone = reReplace( form.phone, "[^\d]+", "", "all" );

			// Validate phone number.
			if ( len( form.phone ) neq 10 ) {

				throw(
					type = "InvalidField",
					message = "Please provide a valid 10 digit phone number."
				);

			}

			// Validate message.
			if ( ! len( form.message ) ) {

				throw(
					type = "InvalidField",
					message = "Don't be rude, say something."
				);

			}

			// Validate file selection (for existence only).
			if ( ! len( form.upload ) ) {

				throw(
					type = "InvalidField",
					message = "Please select a photo to upload."
				);

			}

			// If we made it this far, the form data is valid, as best we can determine
			// before processing the upload.

			// Upload the file to our server.
			// --
			// NOTE: For this demo, we're going to hard-code the requirement of the
			// uploaded asset to be a JPEG image.
			upload = fileUpload(
				expandPath( "./uploads" ),
				"upload",
				"image/jpeg",
				"makeUnique"
			);

			// Twilio can't handle direct file uploads (as far as I know), so we have to
			// provide a "media URL". This means that our MMS media has to be publicly
			// accessible for some period of time. To accomplish this, we'll upload it
			// Amazon S3 as a public-read object for the Twilio interaction.

			// -- Step 1: Push Asset to Amazon S3. -- //

			// Read in the content of the uploaded file.
			media = fileReadBinary( expandPath( "./uploads/#upload.serverFile#" ) );

			// Define the Amazon S3 resource.
			// --
			// NOTE: This demo does not take into account url-encoding special characters.
			// I'm just keeping it simple for the demo (since I know nothing will break).
			resource = "/#aws.bucket#/twilio/#upload.serverFile#";

			// S3 has a limited window for when a request is valid. We need to tell it
			// when this request was prepared.
			currentTime = getHttpTimeString( now() );

			// The mime-type will be stored as Meta-data on the S3 resource; Amazon will
			// also provide this as the Content-Type header when serving the file.
			// --
			// NOTE: I'm hard-coding the mime-type for the demo.
			contentType = "image/jpeg";

			// Set up the S3 signature assets.
			// --
			// NOTE: I am setting the Amazon S3 resource as "PUBLIC-READ"; this way, we
			// do NOT have to generate a pre-signed URL for Twilio - we can provide a URL
			// directly to this resource.
			stringToSignParts = [
				"PUT",
				"",
				contentType,
				currentTime,
				"x-amz-acl:public-read",
				resource
			];

			stringToSign = arrayToList( stringToSignParts, chr( 10 ) );

			// Generate the Hmac-SHA1 of the signature.
			signature = new Crypto().hmacSha1(
				aws.secretKey,
				stringToSign,
				"base64"
			);

			// Upload the image asset to Amazon S3.
			s3Request = new Http(
				method = "put",
				url = "https://s3.amazonaws.com#resource#"
			);

			s3Request.addParam(
				type = "header",
				name = "Authorization",
				value = "AWS #aws.accessID#:#signature#"
			);

			s3Request.addParam(
				type = "header",
				name = "Content-Length",
				value = arrayLen( media )
			);

			s3Request.addParam(
				type = "header",
				name = "Content-Type",
				value = contentType
			);

			s3Request.addParam(
				type = "header",
				name = "Date",
				value = currentTime
			);

			s3Request.addParam(
				type = "header",
				name = "x-amz-acl",
				value = "public-read"
			);

			s3Request.addParam(
				type = "body",
				value = media
			);

			result = s3Request.send();

			// Make sure the upload to Amazon S3 was successful.
			if ( ! reFind( "2\d\d", result.getPrefix().statusCode ) ) {

				throw(
					type = "S3",
					message = "The Amazon S3 upload API request failed.",
					extendedInfo = duplicate( result.getPrefix() )
				);

			}


			// ------------------------------------------------------ //
			// ------------------------------------------------------ //


			// -- Step 2: Send The MMS Message. -- //

			// Now that we've uploaded the asset to Amazon S3 with *PUBLIC* access, we
			// can send the MMS message via Twilio.

			// Get the full Amazon S3 resource url using the resource from above.
			mediaUrl = "https://s3.amazonaws.com#resource#";

			// Send the MMS message via Twilio's REST API.
			twilioRequest = new Http(
				method = "post",
				url = "https://api.twilio.com/2010-04-01/Accounts/#twilio.accountSID#/Messages",
				username = twilio.accountSID,
				password = twilio.authToken
			);

			twilioRequest.addParam(
				type = "formfield",
				name = "From",
				value = twilio.phone
			);

			twilioRequest.addParam(
				type = "formfield",
				name = "To",
				value = "+1#form.phone#"
			);

			twilioRequest.addParam(
				type = "formfield",
				name = "MediaUrl",
				value = mediaUrl
			);

			twilioRequest.addParam(
				type = "formfield",
				name = "Body",
				value = form.message
			);

			result = twilioRequest.send();

			// Make sure that the Twilio request was initiated.
			if ( ! reFind( "2\d\d", result.getPrefix().statusCode ) ) {

				throw(
					type = "Twilio",
					message = "The Twilio MMS API request failed.",
					extendedInfo = duplicate( result.getPrefix() )
				);

			}


			// If we made it this far, everything worked like a champ! The file was
			// uploaded to Amazon S3, then the public URL for said object was used to
			// send the Twilio MMS message. Send user to the confirmation page for much
			// deserved celebration and high-fiving!
			location( url = "./confirmation.cfm", addToken = false );


		// Catch any form-validation error.
		} catch ( InvalidField error ) {

			errorMessage = error.message;

		// Catch any unexpected error.
		} catch ( any error ) {

			errorMessage = "For some reason, we couldn't send your MMS message.";

		}

	} // END: if submitted.

</cfscript>


<cfoutput>

	<!doctype html>
	<html>
	<head>
		<meta charset="utf-8" />
		<title>
			Sending MMS (Multimedia Messaging Service) Messages With Twilio And ColdFusion
		</title>
	</head>
	<body>

		<h1>
			Sending MMS (Multimedia Messaging Service) Messages With Twilio And ColdFusion
		</h1>

		<!--- If we have an error message, show it. --->
		<cfif len( errorMessage )>

			<p>
				<strong>Oops!</strong> #htmlEditFormat( errorMessage )#
			</p>

		</cfif>

		<form
			method="post"
			action="#cgi.script_name#"
			enctype="multipart/form-data">

			<input type="hidden" name="submitted" value="true" />

			<p>
				Phone Number:<br />
				<input type="text" name="phone" size="20" />
			</p>

			<p>
				Message:<br />
				<input type="text" name="message" size="40" />
			</p>

			<p>
				Photo:<br />
				<input type="file" name="upload" size="20" />
			</p>

			<p>
				<input type="submit" value="Send MMS Message" />
			</p>

		</form>

	</body>
	</html>

</cfoutput>

There's not a whole lot going on here - so I won't go into any further detail. Most of it is just the noise of generating and signing HTTP requests. But, when I upload a photo, I am able to get the MMS message on my iPhone:

Sening MMS messages using Twilio and ColdFusion.

Woot! And double-woot! Can you think of a better way to start your Friday off than by getting MMS-enabled?

Twilio's approach to telephony revolutionized the way we developers can think about our application interactions. And, now, they've freakin' done it again. Twilio is definitely one of my favorite 3rd-party services and a goto integration point for just about every application I build.



Reader Comments