Send and track faxes with the Twilio Fax API using Sinatra and Ruby
Time to read: 6 minutes
It happened! I've been waiting for the moment I needed to send a fax since Twilio launched the Programmable Fax API back in 2017 and this week it finally happened! I won't go into detail about what I needed to send, but it's safe to say the medical profession could consider their communication choices for the future.
I could have sent the fax by uploading a PDF to Twilio Assets and using the API explorer, but that wouldn't have been as fun as over-engineering an entire application to send and track the fax to make sure it arrived and be prepared for any future fax situations.
In this post I'll share how to build an application for sending and tracking faxes, but if you have faxes to send and want to jump straight into using it, you can find all the source code on GitHub.
Weapons of choice
When a fax is sent it is more similar to making a phone call than sending a message. For this reason it can fail the same way as a phone call, for example if it receives a busy tone. So, when building this app I wanted it to be simple enough to hack together quickly, but powerful enough to support sending and receiving status updates for faxes.
I decided on Ruby, with Sinatra. To get started with this you'll need:
- Ruby installed, I recommend the latest version, 2.6.5
- Bundler for installing dependencies
- ngrok so we can expose webhook endpoints in style
- A Twilio account with a fax capable number
That ought to be enough to get this app built, so let's get started.
The application shell
Let's get the application set up and make sure it's working before we implement the actual sending of faxes. Create a new directory to work in and change into that directory on the command line.
Initialise a new application by calling:
Add the gems we will use to build this application:
- Sinatra, a simple web framework
- The twilio-ruby gem to interact with the Twilio Fax API
- shotgun so that the server reloads as we make changes
- Envyable to manage environment variables in the application
Create the application structure:
config/env.yml
will hold our application config. Open it up and add the following:
Fill in the Account SID and Auth Token from your Twilio console. For the from number, add a Fax capable number from your Twilio account. We'll fill in the URL_BASE
later.
config.ru
is the file that will start up our application. We'll need to require the application dependencies, load environment variables from config/env.yml
, load the app and then run it. Add the following code to do all of that:
To make sure things are working so far, we'll build a "Hello World!" endpoint as the starting point for our app. Open app.rb and add the following:
This creates an app that returns the text "Hello World!" in response to loading the root path. Run the application with:
Open up localhost:3000, it should say "Hello World!" if so, then we're on the right track.
Next up, let's build the interface for the app.
Building the interface
In Sinatra the default is to render views with ERB, embedded Ruby. By default Sinatra looks for a layout in views/layout.erb
. We've already created that file, let's add the following HTML structure:
The important part here is the <%= yield %>
right in the middle. This is where individual view template will be inserted.
Let's add some style so that the app will look nice too. Open up public/style.css
and copy in the CSS available in this file on GitHub.
Open views/index.erb
. Now we need to build a form that will collect the number we are going to send the fax to and a PDF file that the Twilio API will turn into the fax. Add the following into views/index.erb
:
In this form we've set the method to POST
and the enctype
to multipart/form-data
so that we can use it to upload a file to the server. We've set the action
to /faxes
which is an endpoint we will build soon. We've also used a bit of HTML form validation to make sure the values we enter are correct, both input fields are required, the fax number field is of type tel
and the file input only accepts PDF files.
Open up app.rb
again. We now want to change our "Hello World!" endpoint to render views/index.erb
instead. We do that with the erb
helper method.
If the app is still running, check it out again at localhost:3000. It should look like this:
That's the interface complete, now let's build the back-end and actually send some faxes!
Sending the fax
As we mentioned, we need to create the /faxes
endpoint. It needs to do a few things:
- Respond to POST requests
- Store the PDF file we are uploading
- Make the request to the Twilio Fax API to create a fax
- Finally redirect back to the home page
To respond to POST requests, we use the Sinatra post
method. In app.rb
add this to the application class:
We can get the file and the other parameters submitted to the endpoint using the params
hash.
If we have a file that has been uploaded, we'll write it into the files
directory within the app:
Next we'll create a Twilio API client, authorize it with our credentials and make the API call to send the Fax. We'll use the FROM_NUMBER
we set in config/env.yml
as the from number for the fax, the to number comes from the form parameters and we need to send a media_url
which points to the fax.
When Twilio connects to the fax machine we are sending to, it makes a webhook request to retrieve the PDF file that we want to send as a fax. So we need to provide a URL to that PDF file. We haven't defined a way to serve the uploaded file yet, but that's next on our to do list. For now, use the following as the media_url
: media_url: "#{ENV["URL_BASE"]}/faxes/files/#{ERB::Util.url_encode(filename)}"
. Finish up the endpoint with a redirect back to the root path.
Now, we need to build the /faxes/files/:filename
endpoint to return the uploaded file. It is good practice to protect this webhook endpoint to make sure it only responds to requests that originate from Twilio. We can do this with the rack middleware supplied by the twilio-ruby gem that checks the signature in the header from Twilio.
Sinatra gives us a really easy way to send a file, the send_file
method. So let's create a get endpoint to return the file. We'll pass the filename as the final part of the path (the path will look like /faxes/files/nameOfFile.pdf
) so we can read it as a parameter by defining it in the route with a colon. Then we'll use the filename to find the file on the server and return it with send_file
.
To protect this endpoint, add the Rack::TwilioWebhookAuthentication
middleware. We pass two arguments to the middleware, your Twilio auth token so that it can sign and compare requests and a regular expression for a path that it will work on. Add this line to the top of the class.
Receiving status callbacks
We're ready to send a fax. But since this fax is important I wanted to know that it was delivered as well. Like with calls and messages, we can register to receive a statusCallback
webhook to track our fax.
This application doesn't use a database or any other store, so logging the status will do for now. Create one more post endpoint to receive the statusCallback webhook and log the parameters that are important, making sure to return a 200 status and empty response body:
We need to add this endpoint as the status_callback
URL in the request to send the fax too.
Now we're ready to send and track our fax!
Tunnelling with ngrok
In order to open up our webhook endpoints to the Internet so that Twilio can reach them, we'll use ngrok. We've been running the application on port 3000 locally, so start ngrok tunnelling HTTP traffic through to port 3000 with the following command:
Once ngrok shows you your tunnel URL, grab it and add it as the URL_BASE
in your config/env.yml
file. It should look like this:
Restart the application, or start it again with:
Send the fax
If you don't have anyone with a fax to test this out on, you can always use another Twilio number, but if you're like me you have your fax number and PDF to hand waiting to go. Open up localhost:3000, enter the fax number and choose your PDF file. Click send and watch the logs as Twilio requests the file, then a few minutes later (we estimate a fax takes 30-60 seconds to send per page) check to see the status logs.
Party like it's 1979
In this blog post you learned that certain industries still require faxes. And, if you're ever in a position where you need to send a fax, you can do so with the Twilio API and this Ruby project.
We've seen how to use Sinatra to upload files and the twilio-ruby library to send a fax. The entire application is available to clone from GitHub here.
If you wanted to extend this, you could add a database and store the faxes, along with their status updates. For a similar idea, you can see how to track emails in Rails with SendGrid. You could also look into storing the fax media in a static file store like AWS's S3 or Google Cloud's Filestore and streaming it to Twilio.
Did you ever get asked to send a fax? What did you do? Share your fax stories with me in the comments below or on Twitter at @philnash.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.