Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: David Epler
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: David Epler@dcepler )

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

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments