Create Your Own Phone Backup Service using PHP, Twilio SMS, DigitalOcean Spaces, and Laravel

August 14, 2019
Written by

Create a Phone Backup Service using Twilio SMS.png

Have you ever had a voicemail from a loved one that you wanted to keep forever? Maybe a contact that you wanted to save but didn’t want to add to your phone? Whatever the use case, it’s almost impossible to backup some types of media, like voicemail and contacts, without an app.

We're going to learn how to create a phone backup service that allows us to manually transfer data via SMS to cloud storage, just by using our phone number.

We will accomplish this by creating a RESTful API in Laravel, connecting it to Twilio SMS, and adding cloud storage via Digital Ocean Spaces.

In order to complete this tutorial you will need the following:

At the time of writing, DigitalOcean does not provide an official PHP API for connecting to its object storage. So we will also utilize the Spaces API, an open-source wrapper that assists us in easily connecting to the Digital Ocean API in PHP. The Spaces API will be added to our project later via Composer. Special thanks to Devang Srivastava for maintaining this repo.

Create a Backup Service Using a New Laravel Project

Begin this project by creating a RESTful API using the Laravel PHP framework. This API will communicate with the Twilio SMS API and deliver our media. If you haven’t already installed Laravel, you can do so by following the official documentation and installation guide.

After installation, run the following command to generate a new Laravel project:

$ laravel new phonebackup 
$ cd phonebackup

Add the Spaces API Dependency to Your Laravel Project

In preparation for the next step, the app we’re building will require the Spaces API in order to easily connect to our DigitalOcean Space. Before we set up the Spaces API, we need to add the Spaces API dependency to our Laravel project.

Now that you’re inside the phonebackup directory, run the following command inside of terminal using Composer:

$ composer require sociallydev/spaces-api:dev-master

This command added the Spaces API to our project as a dependency. Now, Laravel will use it to communicate with the DigitalOcean API and handle uploading of media received from the API endpoint.

Create a Twilio Account & Number

Our backup service will use a Twilio number as a data entry point. The user will forward media from their phone to a specified number. Once  the designated Twilio number receives the SMS, it will be parsed in order to extract the attached media and upload the media into our Space.

If you don’t have a Twilio account, you can sign up for one now and receive a trial account to test your app. Once your account is created, purchase a phone number or use an existing one.

Create a DigitalOcean Space

This tutorial will work with any cloud storage system, including the local file system. However; we are using DigitalOcean’s Spaces, an S3-compatible object storage system able to store and serve large amounts of data with free CDN support. The following steps will illustrate how to create a DigitalOcean Space and connect it to your project.

Create a New Space

Login to your DigitalOcean account or create a new one (with a $50 credit). Create a new container by navigating to the Spaces dashboard and follow these five easy steps:

DigitalOcean Spaces dashboard to create a new space
  1. Select the datacenter that is closest to you for better deliverability.
  2. “Enable CDN” to increase the speed of content delivery
  3. For security, leave the “Restrict File Listing” option selected
  4. Choose a unique subdomain such as “phonebackup” + a unique identifier
  5. After clicking on “Create a Space” you will have successfully created a new container

Generate a DigitalOcean Access Key

Now that the Space has been created, navigate to the Applications & API page to generate a key/secret pair for secure access and transmittance of data to the Space.

Under “Spaces access keys” click on the “Generate New Key” button and fill in the required fields.

DigitalOcean create new API key

After your key is generated, the secret will be temporarily displayed. Navigate to the .env file and add the following credentials:

DO_KEY="Insert ACCESS KEY here"
DO_SPACE_REGION="Generally sfo2 or nyc3"

Note: Your DO_SPACE_NAME is the unique name you previously created in Step 4. The DO_SPACE_REGION is available on the settings page of your Space in the “Endpoint” field.

Add Credentials to Laravel Config

Although the .env is being utilized, for better portability and support, we will exploit Laravel’s global configuration object to inject the credentials safely and consistently throughout the application. Laravel comes with configurations for various development environments automatically created for you.

For this project, our credentials will be injected into the config/services.php file which has already been predefined for third-party services.

   'digitalocean' => [
       'key'    => env('DO_KEY'),
       'secret' => env('DO_SECRET'),
       'space' => [
           'name'     => env('DO_SPACE_NAME'),
           'region' => env('DO_SPACE_REGION'),

Create the Endpoint to Upload Media

Now that setup of all services is complete, it’s time to write the scripts so we can capture and transfer our media.

Create the MediaController

Laravel by definition is a modern MVC framework and uses its built-in routing to create dynamic and fully extensible API routes. As an important next step, create the controller to support processing of incoming requests from our API and transferring the captured data to our Space.

To generate the MediaController, run the following command:

$ php artisan make:controller API/MediaController --api

You will notice that this command has created a file at app/Http/Controllers/API/MediaController.php.

Open this file.

Five methods have been created from this command, index() for listing all resources, store() for saving a single resource, show() for displaying a single resource, update() for updating a single resource, and destroy() for deleting a single resource. Essentially, CRUD operations are generated from the --api flag.

We are only focusing on the store() method because we are technically creating a new resource in our Space.

Modify the method as follows:

    * Store a newly created resource in storage.
    * @param  \Illuminate\Http\Request  $request
    * @return \Illuminate\Http\Response
   public function store( Request $request )
       // Capture the sender's mobile number
       $mobile_number_formatted = str_ireplace( '+', '', $request->From );

       if ( ! $mobile_number_formatted ) {
           return [
               'error' => 'Cannot detect mobile number'

       // Capture the media type of the attachment
       $media_type = $request->MediaContentType0;

       if ( ! $media_type || ! $request->MediaUrl0 ) {
           return [
               'error' => 'No media present in the SMS'

       // Create the filename for the media sent
       $media_type_parts = explode( '/', $media_type );
       $filename_ext     = str_replace( [ 'jpeg', 'amr', 'x-vcard' ], [ 'jpg', 'mp3', 'vcf' ], $media_type_parts[1] );
       $filename         = $media_type_parts[0] . '-' . date( 'Y-m-d-His' ) . '.' . $filename_ext;

       // Set the folder name based on content type
       switch ( $filename_ext ) {
           case "mp3" :
               $folder = 'audio/';
           case 'vcf' :
               $folder = 'contacts/';
           case 'jpg' :
               $folder = 'images/';
           default :
               $folder = '';

       // Download the media locally
       $download = file_get_contents( $request->MediaUrl0 );

       // Assign the data to the specified $filename
       file_put_contents( $filename, $download );

       // Upload file to DigitalOcean Space
       $do_space = new \SpacesConnect( config('services.digitalocean.key'), config('services.digitalocean.secret'), config(''), config('') );
       $media_url = $do_space->UploadFile( $filename, 'public', $mobile_number . '/' . $folder . $filename );

       // Delete the file locally whether or not upload is successful
       unlink( $filename );

       // Send public link back to $mobile_number
       if ( isset( $media_url[ 'ObjectURL' ] ) ) {

               <?xml version="1.0" encoding="UTF-8"?>
                   <Message><Body>Your file was successfully backed up! Here's a shareable link <?php echo $media_url[ 'ObjectURL' ]; ?></Body></Message>

       return [
           'status' => 'error',
           'error' => 'media failed to upload'

In the code above we captured the sender's mobile number. The + prefix is removed since this value will create a subdirectory in our Space. After detecting the media type of the attachment, the filename is auto-generated based on the type, timestamp, and extension. Additionally, a folder relative to the media type is created.

Note: The switch statement can be modified to include any type of file you desire.

The contents of our attachment are downloaded to the local file system and assigned the generated filename. Once that process is complete, the contents are uploaded to your Space, the local file is deleted, and a success message is returned in TwiML with a request to send a response back to the sender’s mobile number.

Connect the MediaController to Routing

By default, when routing is mentioned, it assumes the public-facing, view-controller relationship for a Laravel application. However; for the purpose of what we’re creating, we’ll be focusing on the API routing as defined in routes/api.php which seamlessly generates RESTful endpoints.

Since the logic for MediaController is complete, we need to expose and connect the controller to the routing system. 

The recommended endpoint that we’ll POST our request to is /media. Our endpoint will then transfer the request and all of its data to the store() method. 

Simply add the following line of code to the routes/api.php file as seen below:

Route::post( 'media', 'API\MediaController@store' );

Connect the Endpoint to your Twilio Phone Number

In order to complete this project, we need to connect the Twilio phone number and the endpoint. Once this step is complete, we will be able to forward an attachment from our phone, where it will automatically upload to our DigitalOcean Space, and we will receive an automated SMS receipt with a public link to the file.

Our application lives within our local environment. Therefore it has no direct access from the Internet. Dynamically assign DNS to the localhost and provide a tunnel to communicate with our application using ngrok. 

In your terminal and within the project folder, run the following command to serve your website:

$ php artisan serve

This command when successfully executed will serve your Laravel application at

Laravel default landing

In another terminal window, run the following ngrok command to create a publicly accessible, temporary domain:

$ ngrok http

Within this command, we’re telling ngrok to tunnel all traffic to our localhost at port 8000. After this runs successfully, a screen similar to the following will display:

ngrok terminal

You’ll see above that my “Forwarding” address is for unsecured traffic and for https. Do not close this window until you are ready to terminate traffic to your local environment.

Within your Twilio console, navigate to your phone numbers and click on the preferred phone number. On the details page for that number, navigate to the “Messaging” section and under “Configure with” select “Webhooks, TwiML Bins, Functions, Studio, or Proxy. A new dropdown “A message comes in” will appear. 

Make sure to select “Webhooks” as the value. 

In the adjacent field, input the https ngrok address + /api/media. For example, my full URL would be Save the updated settings.

Twilio phone number setup

Ready to Test

Now that our Twilio phone number is connected to our Laravel Application, we are ready to test the phone backup service. I will demonstrate how to do this with an iPhone, but the principle applies to all capable smartphones.

From the Voicemail app, expand the details of an existing voicemail.

Click on the Share button in the top right-hand corner of the card.

  1. Click on the “Message” icon
  2. Input your Twilio phone number in the “To:” field, and
  3. Click send.

If there were no errors, you will receive an SMS response similar to the screenshot provided below.


I made this app because I have very important voicemails from loved ones that I want to keep “forever”. I also have an iPhone and the backup services are somewhat hit-and-miss. I’ve even heard of people who have loved ones that have passed who hold on to their phones just to replay the messages.

With this tutorial, you’ve learned how to ensure that special digital memories can be transferred from your phone in the event you no longer have access to it.

I’d love to see what you build with this and the modifications you make. If you need any assistance, don’t hesitate to hit me up on Twitter or shoot me an email.

Marcus Battle is Twilio’s PHP Developer of Technical Content. You can learn more about him on the blog or access additional PHP content at Twilio's new PHP Voices.