Receive and Download Images on Incoming Media Messages with PHP and Laravel
This tutorial shows how to receive and download images and other media on incoming MMS messages using PHP and Laravel. If you also need to handle plain text replies, see how to receive and reply to incoming SMS messages.
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 with PHP is to use Laravel or a similar framework.
At a minimum, your webhook must return a 200 OK response. Often you will also return some TwiML, a set of XML commands that tell Twilio how to respond to the message. Rather than generating the XML manually, use the Twilio\Twiml module in the SDK to generate TwiML and handle the rest of the webhook plumbing.
To install the library, run:
composer require twilio/sdk
Add a new controller called MessagingController that handles an incoming SMS request.
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\MMSMedia;7use App\Services\MediaMessageService\IMediaMessageService;8use Illuminate\Foundation\Auth\AuthenticatesUsers;9use Illuminate\Http\Request;10use Illuminate\Http\Response;11use Magyarjeti\MimeTypes\MimeTypeConverter;12use Twilio\Rest\Client;13use Twilio\Twiml;1415class MessagingController extends Controller16{17/*18|--------------------------------------------------------------------------19| Messaging Controller20|--------------------------------------------------------------------------21|22| This controller receives messages from Twilio and makes the media available23| via the /images url.24*/2526protected $twilio;27protected $accountSid;28protected $twilioNumber;29/**30* Create a new controller instance.31*32* @return void33*/34public function __construct()35{36$this->accountSid = env('TWILIO_ACCOUNT_SID');37$this->twilioNumber = env('TWILIO_NUMBER');38$authToken = env('TWILIO_AUTH_TOKEN');3940$this->twilio = new Client($this->accountSid, $authToken);41}4243public function handleIncomingSMS(Request $request, IMediaMessageService $mediaService)44{45$converter = new MimeTypeConverter;46$NumMedia = (int)$request->input('NumMedia');47$FromNumber = $request->input('From');48$MessageSid = $request->input('MessageSid');4950for ($i=0; $i < $NumMedia; $i++) {51$mediaUrl = $request->input("MediaUrl$i");52$MIMEType = $request->input("MediaContentType$i");53$fileExtension = $converter->toExtension($MIMEType);54$mediaSid = basename($mediaUrl);5556$media = $mediaService->getMediaContent($mediaUrl);5758$filename = "$mediaSid.$fileExtension";5960$mediaData = compact('mediaSid', 'MessageSid', 'mediaUrl', 'media', 'filename', 'MIMEType');61$mmsMedia = new MMSMedia($mediaData);62$mmsMedia->save();63}6465$response = new Twiml();66$messageBody = $NumMedia == 0 ? 'Send us an image!' : "Thanks for the $NumMedia images.";67$message = $response->message([68'from' => $request->input('To'),69'to' => $FromNumber70]);71$message->body($messageBody);7273return (string)$response;74}7576public function deleteMediaFromTwilio($mediaItem)77{78return $this->twilio->api->accounts($this->accountSid)79->messages($mediaItem['MessageSid'])80->media($mediaItem['mediaSid'])81->delete();82}8384public function allMedia()85{86$mediaItems = MMSMedia::all();87return $mediaItems;88}8990public function getMediaFile($filename, Response $response)91{92$media = MMSMedia::where('filename', $filename)->firstOrFail();93$fileContents = $media['media'];94$MessageSid = $media['MessageSid'];95$mediaSid = $media['mediaSid'];96$MIMEType = $media['MIMEType'];9798$media->delete();99$this->deleteMediaFromTwilio(compact('mediaSid', 'MessageSid'));100101return response($fileContents, 200)102->header('Content-Type', $MIMEType);103}104105public function config()106{107return ['twilioNumber' => $this->twilioNumber];108}109}
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 body.
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 pull the URLs out of the body request like this:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\MMSMedia;7use App\Services\MediaMessageService\IMediaMessageService;8use Illuminate\Foundation\Auth\AuthenticatesUsers;9use Illuminate\Http\Request;10use Illuminate\Http\Response;11use Magyarjeti\MimeTypes\MimeTypeConverter;12use Twilio\Rest\Client;13use Twilio\Twiml;1415class MessagingController extends Controller16{17/*18|--------------------------------------------------------------------------19| Messaging Controller20|--------------------------------------------------------------------------21|22| This controller receives messages from Twilio and makes the media available23| via the /images url.24*/2526protected $twilio;27protected $accountSid;28protected $twilioNumber;29/**30* Create a new controller instance.31*32* @return void33*/34public function __construct()35{36$this->accountSid = env('TWILIO_ACCOUNT_SID');37$this->twilioNumber = env('TWILIO_NUMBER');38$authToken = env('TWILIO_AUTH_TOKEN');3940$this->twilio = new Client($this->accountSid, $authToken);41}4243public function handleIncomingSMS(Request $request, IMediaMessageService $mediaService)44{45$converter = new MimeTypeConverter;46$NumMedia = (int)$request->input('NumMedia');47$FromNumber = $request->input('From');48$MessageSid = $request->input('MessageSid');4950for ($i=0; $i < $NumMedia; $i++) {51$mediaUrl = $request->input("MediaUrl$i");52$MIMEType = $request->input("MediaContentType$i");53$fileExtension = $converter->toExtension($MIMEType);54$mediaSid = basename($mediaUrl);5556$media = $mediaService->getMediaContent($mediaUrl);5758$filename = "$mediaSid.$fileExtension";5960$mediaData = compact('mediaSid', 'MessageSid', 'mediaUrl', 'media', 'filename', 'MIMEType');61$mmsMedia = new MMSMedia($mediaData);62$mmsMedia->save();63}6465$response = new Twiml();66$messageBody = $NumMedia == 0 ? 'Send us an image!' : "Thanks for the $NumMedia images.";67$message = $response->message([68'from' => $request->input('To'),69'to' => $FromNumber70]);71$message->body($messageBody);7273return (string)$response;74}7576public function deleteMediaFromTwilio($mediaItem)77{78return $this->twilio->api->accounts($this->accountSid)79->messages($mediaItem['MessageSid'])80->media($mediaItem['mediaSid'])81->delete();82}8384public function allMedia()85{86$mediaItems = MMSMedia::all();87return $mediaItems;88}8990public function getMediaFile($filename, Response $response)91{92$media = MMSMedia::where('filename', $filename)->firstOrFail();93$fileContents = $media['media'];94$MessageSid = $media['MessageSid'];95$mediaSid = $media['mediaSid'];96$MIMEType = $media['MIMEType'];9798$media->delete();99$this->deleteMediaFromTwilio(compact('mediaSid', 'MessageSid'));100101return response($fileContents, 200)102->header('Content-Type', $MIMEType);103}104105public function config()106{107return ['twilioNumber' => $this->twilioNumber];108}109}
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 image/jpeg.
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\MMSMedia;7use App\Services\MediaMessageService\IMediaMessageService;8use Illuminate\Foundation\Auth\AuthenticatesUsers;9use Illuminate\Http\Request;10use Illuminate\Http\Response;11use Magyarjeti\MimeTypes\MimeTypeConverter;12use Twilio\Rest\Client;13use Twilio\Twiml;1415class MessagingController extends Controller16{17/*18|--------------------------------------------------------------------------19| Messaging Controller20|--------------------------------------------------------------------------21|22| This controller receives messages from Twilio and makes the media available23| via the /images url.24*/2526protected $twilio;27protected $accountSid;28protected $twilioNumber;29/**30* Create a new controller instance.31*32* @return void33*/34public function __construct()35{36$this->accountSid = env('TWILIO_ACCOUNT_SID');37$this->twilioNumber = env('TWILIO_NUMBER');38$authToken = env('TWILIO_AUTH_TOKEN');3940$this->twilio = new Client($this->accountSid, $authToken);41}4243public function handleIncomingSMS(Request $request, IMediaMessageService $mediaService)44{45$converter = new MimeTypeConverter;46$NumMedia = (int)$request->input('NumMedia');47$FromNumber = $request->input('From');48$MessageSid = $request->input('MessageSid');4950for ($i=0; $i < $NumMedia; $i++) {51$mediaUrl = $request->input("MediaUrl$i");52$MIMEType = $request->input("MediaContentType$i");53$fileExtension = $converter->toExtension($MIMEType);54$mediaSid = basename($mediaUrl);5556$media = $mediaService->getMediaContent($mediaUrl);5758$filename = "$mediaSid.$fileExtension";5960$mediaData = compact('mediaSid', 'MessageSid', 'mediaUrl', 'media', 'filename', 'MIMEType');61$mmsMedia = new MMSMedia($mediaData);62$mmsMedia->save();63}6465$response = new Twiml();66$messageBody = $NumMedia == 0 ? 'Send us an image!' : "Thanks for the $NumMedia images.";67$message = $response->message([68'from' => $request->input('To'),69'to' => $FromNumber70]);71$message->body($messageBody);7273return (string)$response;74}7576public function deleteMediaFromTwilio($mediaItem)77{78return $this->twilio->api->accounts($this->accountSid)79->messages($mediaItem['MessageSid'])80->media($mediaItem['mediaSid'])81->delete();82}8384public function allMedia()85{86$mediaItems = MMSMedia::all();87return $mediaItems;88}8990public function getMediaFile($filename, Response $response)91{92$media = MMSMedia::where('filename', $filename)->firstOrFail();93$fileContents = $media['media'];94$MessageSid = $media['MessageSid'];95$mediaSid = $media['mediaSid'];96$MIMEType = $media['MIMEType'];9798$media->delete();99$this->deleteMediaFromTwilio(compact('mediaSid', 'MessageSid'));100101return response($fileContents, 200)102->header('Content-Type', $MIMEType);103}104105public function config()106{107return ['twilioNumber' => $this->twilioNumber];108}109}
Depending on your use case, storing the URLs of the images (or videos or whatever) may be all you need. Accessing these media files requires HTTP Basic Authentication. There are two key features to these URLs:
- They require HTTP Basic Authentication to access.
- They are permanent (unless you explicitly delete the media).
Authentication required
Twilio enforces HTTP Basic Authentication for all media URLs. Authenticate using an API key as the username and an API key secret as the password. You can also use your Account SID and Auth Token when testing locally.
If you need to display images in a browser without authentication, you should download the media files and serve them from your own server or a cloud storage service.
Info
For PHP implementations: If you're using a media service abstraction like IMediaMessageService, make sure to update your getMediaContent() method to include HTTP Basic Authentication when making requests to media URLs.
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 and look up the corresponding file extension for the mime type.
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\MMSMedia;7use App\Services\MediaMessageService\IMediaMessageService;8use Illuminate\Foundation\Auth\AuthenticatesUsers;9use Illuminate\Http\Request;10use Illuminate\Http\Response;11use Magyarjeti\MimeTypes\MimeTypeConverter;12use Twilio\Rest\Client;13use Twilio\Twiml;1415class MessagingController extends Controller16{17/*18|--------------------------------------------------------------------------19| Messaging Controller20|--------------------------------------------------------------------------21|22| This controller receives messages from Twilio and makes the media available23| via the /images url.24*/2526protected $twilio;27protected $accountSid;28protected $twilioNumber;29/**30* Create a new controller instance.31*32* @return void33*/34public function __construct()35{36$this->accountSid = env('TWILIO_ACCOUNT_SID');37$this->twilioNumber = env('TWILIO_NUMBER');38$authToken = env('TWILIO_AUTH_TOKEN');3940$this->twilio = new Client($this->accountSid, $authToken);41}4243public function handleIncomingSMS(Request $request, IMediaMessageService $mediaService)44{45$converter = new MimeTypeConverter;46$NumMedia = (int)$request->input('NumMedia');47$FromNumber = $request->input('From');48$MessageSid = $request->input('MessageSid');4950for ($i=0; $i < $NumMedia; $i++) {51$mediaUrl = $request->input("MediaUrl$i");52$MIMEType = $request->input("MediaContentType$i");53$fileExtension = $converter->toExtension($MIMEType);54$mediaSid = basename($mediaUrl);5556$media = $mediaService->getMediaContent($mediaUrl);5758$filename = "$mediaSid.$fileExtension";5960$mediaData = compact('mediaSid', 'MessageSid', 'mediaUrl', 'media', 'filename', 'MIMEType');61$mmsMedia = new MMSMedia($mediaData);62$mmsMedia->save();63}6465$response = new Twiml();66$messageBody = $NumMedia == 0 ? 'Send us an image!' : "Thanks for the $NumMedia images.";67$message = $response->message([68'from' => $request->input('To'),69'to' => $FromNumber70]);71$message->body($messageBody);7273return (string)$response;74}7576public function deleteMediaFromTwilio($mediaItem)77{78return $this->twilio->api->accounts($this->accountSid)79->messages($mediaItem['MessageSid'])80->media($mediaItem['mediaSid'])81->delete();82}8384public function allMedia()85{86$mediaItems = MMSMedia::all();87return $mediaItems;88}8990public function getMediaFile($filename, Response $response)91{92$media = MMSMedia::where('filename', $filename)->firstOrFail();93$fileContents = $media['media'];94$MessageSid = $media['MessageSid'];95$mediaSid = $media['mediaSid'];96$MIMEType = $media['MIMEType'];9798$media->delete();99$this->deleteMediaFromTwilio(compact('mediaSid', 'MessageSid'));100101return response($fileContents, 200)102->header('Content-Type', $MIMEType);103}104105public function config()106{107return ['twilioNumber' => $this->twilioNumber];108}109}
You can also upload these image files to a cloud storage service like Azure Blob Storage or Amazon S3. They're regular files at this point. This example saves them to the database in order to retrieve them later.
If you are downloading the attachments and no longer need them to be stored by Twilio, you can delete them. You can send an HTTP DELETE request to the media URL, and it will be deleted, but you will need to be authenticated to do this. The Twilio PHP SDK can help with this operation, as shown here:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\MMSMedia;7use App\Services\MediaMessageService\IMediaMessageService;8use Illuminate\Foundation\Auth\AuthenticatesUsers;9use Illuminate\Http\Request;10use Illuminate\Http\Response;11use Magyarjeti\MimeTypes\MimeTypeConverter;12use Twilio\Rest\Client;13use Twilio\Twiml;1415class MessagingController extends Controller16{17/*18|--------------------------------------------------------------------------19| Messaging Controller20|--------------------------------------------------------------------------21|22| This controller receives messages from Twilio and makes the media available23| via the /images url.24*/2526protected $twilio;27protected $accountSid;28protected $twilioNumber;29/**30* Create a new controller instance.31*32* @return void33*/34public function __construct()35{36$this->accountSid = env('TWILIO_ACCOUNT_SID');37$this->twilioNumber = env('TWILIO_NUMBER');38$authToken = env('TWILIO_AUTH_TOKEN');3940$this->twilio = new Client($this->accountSid, $authToken);41}4243public function handleIncomingSMS(Request $request, IMediaMessageService $mediaService)44{45$converter = new MimeTypeConverter;46$NumMedia = (int)$request->input('NumMedia');47$FromNumber = $request->input('From');48$MessageSid = $request->input('MessageSid');4950for ($i=0; $i < $NumMedia; $i++) {51$mediaUrl = $request->input("MediaUrl$i");52$MIMEType = $request->input("MediaContentType$i");53$fileExtension = $converter->toExtension($MIMEType);54$mediaSid = basename($mediaUrl);5556$media = $mediaService->getMediaContent($mediaUrl);5758$filename = "$mediaSid.$fileExtension";5960$mediaData = compact('mediaSid', 'MessageSid', 'mediaUrl', 'media', 'filename', 'MIMEType');61$mmsMedia = new MMSMedia($mediaData);62$mmsMedia->save();63}6465$response = new Twiml();66$messageBody = $NumMedia == 0 ? 'Send us an image!' : "Thanks for the $NumMedia images.";67$message = $response->message([68'from' => $request->input('To'),69'to' => $FromNumber70]);71$message->body($messageBody);7273return (string)$response;74}7576public function deleteMediaFromTwilio($mediaItem)77{78return $this->twilio->api->accounts($this->accountSid)79->messages($mediaItem['MessageSid'])80->media($mediaItem['mediaSid'])81->delete();82}8384public function allMedia()85{86$mediaItems = MMSMedia::all();87return $mediaItems;88}8990public function getMediaFile($filename, Response $response)91{92$media = MMSMedia::where('filename', $filename)->firstOrFail();93$fileContents = $media['media'];94$MessageSid = $media['MessageSid'];95$mediaSid = $media['mediaSid'];96$MIMEType = $media['MIMEType'];9798$media->delete();99$this->deleteMediaFromTwilio(compact('mediaSid', 'MessageSid'));100101return response($fileContents, 200)102->header('Content-Type', $MIMEType);103}104105public function config()106{107return ['twilioNumber' => $this->twilioNumber];108}109}
Protect your webhooks
Twilio supports HTTP Basic and Digest Authentication. Authentication lets you 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.
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 REST API 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. For MMS media files, Storage, Unlimited free media storage.
We'd love to hear what you build with this.