Sending MMS (Multimedia Messaging Service) Messages With Twilio And ColdFusion
I'm a huge fan of Twilio's telephony services! After learning about them a few years ago, I've integrated Twilio into almost every new application that I've built. As such, I was delighted to hear that Twilio had finally announced that they would be adding the ability to send MMS (Multimedia Messaging Service) messages via their API. Unlike the normal SMS (Short Messaging Service) messages, MMS messages can contain embedded assets, such as images, that can be displayed on the end user's mobile device. Naturally, I had to give this a try!
Now, at the time of this writing, MMS messages are only available via US short-codes and Canadian phone numbers. I thought that I could get around this limitation by renting a Canadian number and then using it to send an MMS message to my US mobile phone. Unfortunately, this approach is a no-go. The sample code below results in the following "400 BAD REQUEST" response from Twilio's API:
The 'To' phone number: +1**********, is not currently reachable using the 'From' phone number: +1********** via MMS.
That said, some day, the following code should work (I think)!
When you send an MMS message through Twilio, you don't actually post the binary content of the multimedia asset to Twilio; instead, you post a URL to a web-accessible multimedia asset. In the following code, I'm uploading an image to Amazon S3; then, I'm using the S3 resource as my "MediaUrl" form field.
NOTE: In this demo, I'm using an Amazon S3 resource with "public-read" permissions. You could have also provided a private S3 resource using a pre-signed, query-string authentication URL.
<cfscript>
// Include the credentials for both Amazon S3 as well as Twilio.
include "./credentials.cfm";
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// -- Step 1: Push Asset to Amazon S3. -- //
// When sending MMS (Multimedia Messaging Service) messages, we
// need to provide a URL for the multimedia asset. As such, we will
// need to upload it to Amazon S3 first, before we can send the
// message via Twilio.
content = fileReadBinary( expandPath( "./monkey.jpg" ) );
// Define the Amazon S3 resource.
resource = "/#aws.bucket#/twilio/monkey.jpg";
// 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.
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( content )
);
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 = content
);
result = s3Request.send();
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// -- 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.
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**********"
);
twilioRequest.addParam(
type = "formfield",
name = "MediaUrl",
value = mediaUrl
);
twilioRequest.addParam(
type = "formfield",
name = "Body",
value = "Check out this awesome monkey!!!"
);
result = twilioRequest.send();
</cfscript>
NOTE: Since I am on ColdFusion 9 on my current development machine, I'm using the Crypto.cfc library for Hmac generation instead of ColdFusion 10's native hmac() function.
Like I said above, the Twilio portion of this code returns an HTTP status code of 400 since MMS messaging is not yet supported on US mobile phones (from Canadian phone numbers). That said, if I remove the "MediaUrl" form-field from the POST, I do get the SMS message on my phone. This makes me believe that the code above is "ready" and will start to work once Twilio supports MMS in the US.
Want to use code from this post? Check out the license.
Reader Comments
@Ben: Do you have any idea how one might receive MMS via ColdFusion? I'd like people to be able to send me an MMS with images attached, download those images and save them to the server. As always, thank you for sharing.
Hey Ben, I've always been meaning to ask you this question, but I notice the vast majority of your code samples seem to use CFSCRIPT vs the tag-based syntax.
Do you simply prefer the script syntax over tags? Have you found there to be any critical level of functionality that is not supported via CFSCRIPT?
On a side note, I LOVE how clean your code is and how well you comment. A sign of an experienced developer!
@Chris,
I wish I knew! Hopefully Twilio will introduce this feature to the US phone numbers (and beyond) sooner than later. Now that it works in Canada AND in the US (if you have a short-code), I assume that the roadblock can't be *technical*. It must either be legal reason, contract reasons, or perhaps both :(
@Aaron,
I've really only fully embraced CFScript for the last year or so. That said, I DO keep my "gateway" components in full-tags because I CANNOT stand to write SQL statements in CFScript. Sure, I'll do it from time-to-time, especially when I have to perform a quick query-of-queries; but, for the vast majority of any component that does Database querying, I keep it in tags.
Actually, in a way, this divergence of script and tags and SQL has, in some way, helped me start to better separate the aspects of my application - the data-access is in queries and in "gateways." Then, the service layers above that, in CFScript, depend on the abstraction of the data-access.
That said, I do find myself making "helper" components that make up for the tags that are not yet well supported in CFScript... things like ZipHelper and CookieHelper, that basically just turn around and do something like:
<cfcookie attributeCollection="#arguments#">
And, thanks for the kind words! I try to really communicate my intention and thinking in the code!
I'm actually working on a custom-built framework right now and JUST got to the point where I'm developing my data-access component, however I feel that having it simply load specified files full of SQL seems a bit...archaic.
I'm looking into ORM, but WOW have I found so little structured information on it!
As for my framework. It has given me worlds of experience, as well as respect, for those who've built them. Such an amazing amount of coding: requests, responses, authentication, authorization, data access, error handling... but it's been quite the journey!
In my opinion, ORM is cool until you have to do complex joins. As soon as I got to that point (which doesn't take long on some projects), I decided to stick with standard SQL.
Since ORM is a deviation from standard SQL, it means that if you pickup new people on a project, they can't just 'jump right in'. It has a lot of cool ideas, but I haven't felt comfortable using it all that much. I like my projects to be such that pretty much any ColdFusion developer can sit down and go to work with as little effort as possible.
There are lots of Java oriented ORM sites. For the most part, you can learn more about it there.
@Kevin
So what solution are you using? Do you just have CFQUERY tags where you need them or do you have some kind of Data-Access component which you fire off arguments to and it builds the query and returns the data?
@Aaron Martone
I have played with it a couple different ways. One was with ColdBox and the other was just using the native ColdFusion tags for ORM.
That was a couple years ago, so things could easily have changed since then.
The non-ColdBox testing I was doing was using sample code such as what Adobe has here: http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSA59BCAA1-7773-49a9-977C-F8755BC2E0B8.html
@Aaron, @Kevin,
Complex joining is definitely one of my biggest mental hurdles when it comes to transition to any other kind of technology. Actually, this question is very timely considering that I am just now getting into MongoDB / NoSQL.
MongoDB doesn't allow for joins. Period (from what I understand so far). If you want complex reports, you have to create them as documents and then maintain them over time (as one option); or, have scripts that assemble them in the application logic.
As I'm reading about this stuff, I've also started to think more deeply about the concept of "Repositories" and data-access and about how we need to get data for "mutation" vs. something like complex reporting which needs to be highly optimized / tuned.
The more I go over it in my head, the more I am convinced that there has to be something like the Command-Query-Segregation-Principle where "business logic" and "reporting" are different work-horses in the app:
www.bennadel.com/blog/2470-What-If-All-User-Interface-UI-Data-Came-In-Reports-.htm
... anyway, right now, I have soooo many more questions than I have answers :(
As bad of an idea as it might be, I would probably be half tempted to max out all of my servers RAM capacity (i.e. add as much as possible) and dump some of my tables into queries in ColdFusion memory so I could do my joins to my hearts content and then cache the results for the ones I need.
I couldn't stand not having the ability to do complex joins as much as I hate writing them.
@Kevin,
Maxing our RAM is not such a crazy idea. I think that's kind of how some of these NoSQL databases work - they try to keep the entire "working set" in memory so they never have to read from disk once the app is "hot."
That said, performing a query-of-queries is probably going to be much slower on huge data-sets that just going directly to the database - databases are insanely fast!
@All,
Most excellent news! Twilio now supports MMS messages on all US phone numbers. I double-checked this demo and it works as-is!
But, of course, what would be the fun in that - I re-tooled the demo to use an upload form just so I could spend some time basking in the awesome goodness that is Twilio:
www.bennadel.com/blog/2685-uploading-and-sending-mms-multimedia-messaging-service-messages-with-twilio-and-coldfusion.htm
Keep calm and MMS on.