You know how to receive and reply to incoming SMS messages. What if you receive an MMS message containing an image you'd like to download? Let's learn how we can grab that image and any other incoming MMS media using Java.
When Twilio receives a message for your phone number, it can make an HTTP call to a webhook that you create. The easiest way to handle HTTP requests in Java is to use Spark web framework.
Twilio expects, at the very least, for your webhook to return a 200 OK
response if everything is peachy. Often, however, you will return some TwiML in your response as well. TwiML is just a set of XML commands telling Twilio how you'd like it to respond to your message. Rather than manually generating the XML, we'll use the twilio
helper library to facilitate generating TwiML and the rest of the webhook plumbing.
When Twilio calls your webhook, it sends a number of parameters about the message you just received.
Most of these, such as the To phone number, the From phone number, and the Body of the message are available as properties of the request parameter to the Spark views.
We may receive more than one media per message, this parameter informs us how many we received. We used a custom class parseBody
to get the value and cast it to an Integer, to be used in a following loop.
1Map<String, String> parameters = parseBody(req.body());2String numMediaStr = parameters.get("NumMedia");3int numMedia = Integer.parseInt(numMediaStr);
Since an MMS message can have multiple attachments, Twilio will send us form variables named MediaUrlX
, where X is a zero-based index. So, for example, the URL for the first media attachment will be in the MediaUrl0
parameter, the second in MediaUrl1
, and so on.
In order to handle a dynamic number of attachments, we loop through all the available URLs:
1while (numMedia > 0) {2numMedia = numMedia - 1;3String mediaUrl = parameters.get(String.format("MediaUrl%d", numMedia));4}
Attachments to MMS messages can be of many different file types. JPG and GIF images as well as MP4 and 3GP files are all common. Twilio handles the determination of the file type for you and you can get the standard mime type from the MediaContentTypeX
parameter. If you are expecting photos, then you will likely see a lot of attachments with the mime type of image/jpeg
.
1while (numMedia > 0) {2numMedia = numMedia - 1;3String mediaUrl = parameters.get(String.format("MediaUrl%d", numMedia));4String contentType = parameters.get(String.format("MediaContentType%d", numMedia));5}
Depending on your use case, storing the URLs to the images (or videos or whatever) may be all you need. There's two key features to these URLs that make them very pliable for your use in your apps:
For example, if you are building a browser-based app that needs to display the images, all you need to do is drop an <img src="twilio url to your image">
tag into the page. If this works for you, then perhaps all you need is to store the URL in a database character field.
If you want to save the media attachments to a file, then you will need to make an HTTP request to the media URL and write the response stream to a file. If you need a unique filename, you can use the last part of the media URL. For example, suppose your media URL is the following:
https://api.twilio.com/2010-04-01/Accounts/ACxxxx/Messages/MMxxxx/Media/ME27be8a708784242c0daee207ff73db67
You can use that last part of the URL as a unique filename. Figuring out a good extension to use is a little tricker. If you are only expecting images, you could just assume a ".jpg" extension. For a little more flexibility, you can lookup the mime type and determine a good extension to use based on that.
Here's the complete code for our controller that saves each MMS attachment to the App_Data folder:
1package com.twilio.app;23import static spark.Spark.*;45import java.util.Map;6import java.util.HashMap;7import java.io.File;8import java.io.InputStream;9import java.io.UnsupportedEncodingException;10import java.net.URL;11import java.net.URLDecoder;1213import org.apache.tika.mime.MimeType;14import org.apache.tika.mime.MimeTypes;1516import org.apache.commons.io.FileUtils;17import org.apache.http.HttpResponse;18import org.apache.http.client.methods.HttpGet;19import org.apache.http.impl.client.CloseableHttpClient;20import org.apache.http.impl.client.HttpClients;21import org.apache.http.impl.client.LaxRedirectStrategy;2223import com.twilio.twiml.MessagingResponse;24import com.twilio.twiml.messaging.Body;25import com.twilio.twiml.messaging.Message;2627public class App {28public static void main(String[] args) {29post("/sms", (req, res) -> {30Map<String, String> parameters = parseBody(req.body());31String numMediaStr = parameters.get("NumMedia");32int numMedia = Integer.parseInt(numMediaStr);3334if (numMedia > 0) {35while (numMedia > 0) {36numMedia = numMedia - 1;3738// Get all info39String mediaUrl = parameters.get(String.format("MediaUrl%d", numMedia));40String contentType = parameters.get(String.format("MediaContentType%d", numMedia));41String fileName = mediaUrl.substring(mediaUrl.lastIndexOf("/") + 1);42MimeTypes allTypes = MimeTypes.getDefaultMimeTypes();43MimeType mimeType = allTypes.forName(contentType);44String fileExtension = mimeType.getExtension();45File file = new File(fileName + fileExtension);4647// Download file48URL url = new URL(mediaUrl);49CloseableHttpClient httpclient = HttpClients.custom()50.setRedirectStrategy(new LaxRedirectStrategy())51.build();52HttpGet get = new HttpGet(url.toURI());53HttpResponse response = httpclient.execute(get);54InputStream source = response.getEntity().getContent();55FileUtils.copyInputStreamToFile(source, file);56}57}5859// Send message back60String message = (numMedia > 0) ? String.format("Thanks for sending us %s file(s)!", numMedia) : "Send us an image!";61res.type("application/xml");62Body body = new Body63.Builder(message)64.build();65Message sms = new Message66.Builder()67.body(body)68.build();69MessagingResponse twiml = new MessagingResponse70.Builder()71.message(sms)72.build();73return twiml.toXml();7475});76}7778// Body parser help79public static Map<String, String> parseBody(String body) throws UnsupportedEncodingException {80String[] unparsedParams = body.split("&");81Map<String, String> parsedParams = new HashMap<String, String>();82for (int i = 0; i < unparsedParams.length; i++) {83String[] param = unparsedParams[i].split("=");84if (param.length == 2) {85parsedParams.put(urlDecode(param[0]), urlDecode(param[1]));86} else if (param.length == 1) {87parsedParams.put(urlDecode(param[0]), "");88}89}90return parsedParams;91}9293public static String urlDecode(String s) throws UnsupportedEncodingException {94return URLDecoder.decode(s, "utf-8");95}96}
Another idea for these image files could be uploading them to a cloud storage service like Azure Blob Storage or Amazon S3. You could also save them to a database, if necessary. They're just regular files at this point. Go crazy.
If you are downloading the attachments and no longer need them to be stored by Twilio, you can delete them by sending an HTTP DELETE
request to the media URL. You will need to be authenticated to do this. The code sample below demonstrates how to make this request.
1// Install the Java helper library from twilio.com/docs/java/install23import com.twilio.Twilio;4import com.twilio.rest.api.v2010.account.message.Media;56public class Example {7// Find your Account SID and Auth Token at twilio.com/console8// and set the environment variables. See http://twil.io/secure9public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");10public static final String AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");1112public static void main(String[] args) {13Twilio.init(ACCOUNT_SID, AUTH_TOKEN);14Media.deleter("MM800f449d0399ed014aae2bcc0cc2f2ec", "ME557ce644e5ab84fa21cc21112e22c485").delete();15}16}
Twilio supports HTTP Basic and Digest Authentication. Authentication allows you to password protect your TwiML URLs on your web server so that only you and Twilio can access them. Learn more about HTTP authentication and validating incoming requests here.
All the code, in a complete working project, is available on GitHub. If you need to dig a bit deeper, you can head over to our API Reference and learn more about the Twilio webhook request and the Media resource. Also, you will want to be aware of the pricing for storage of all the media files that you keep on Twilio's servers.
We'd love to hear what you build with this.