How To Send Faxes Via Email Using SparkPost, Twilio & Cloudinary

January 12, 20186 min read

No Fax Machine? No Problem! SparkPost, Twilio, and Cloudinary Are Here To Save The Day!

I don’t know about you, but I don’t have a fax machine. Once in a blue moon, I have to send or receive a fax, so I was excited when Twilio shared an awesome way to receive faxes straight to your email using SparkPost. But that is only half the battle. I decided to build the ability to send faxes from an email so I would never need a fax machine again!

We’ll build a function so we can send a PDF attachment to FAX_NUMBER@YOUR_DOMAIN, which will automatically fax the PDF to the phone number before the at sign (a.k.a. the local part, for all you email geeks out there). To do this, we’ll use SparkPost’s inbound functionality, Twilio’s Fax API, and Cloudinary to glue them together. We’ll receive an email to a Twilio function, pull off the attached PDF, save it to Cloudinary, and send it as a fax.

Sign Up and Configure Your SparkPost Account

The first thing you’ll need is a SparkPost account and a domain you want to use to receive mail (a.k.a. an inbound domain). You’ll also need to create an API key with permissions to read and writeinbound domains and relay webhooks

Next, add the SparkPost MX records for your inbound domain. Once you verify that they are set up correctly, run the following cURL to add your inbound domain.

curl -XPOST \
  https://api.sparkpost.com/api/v1/inbound-domains \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "YOUR_DOMAIN" }'

Sign Up For Cloudinary

Next, we’ll need a Cloudinary account to store the PDF we’ll fax. If you don’t know, Cloudinary is a powerful solution for storing, manipulating, and delivering all your media. Grab your cloud name, API key, and API secret and put them somewhere safe for later.

Create & Configure Twilio

We’ll use Twilio to consume the incoming emails and send the faxes. To get started, sign up for an account, and buy a phone number that can send and receive faxes.

Twilio’s serverless functions are perfect for this project. This isn’t a stateful application and it doesn’t need to be running all the time. Using one, we can quickly get set up and run our application.


We’ll need the following NPM modules for our function:

Let’s also add in our environment variables for Cloudinary and enable Twilio’s option to pass through our ACCOUNT_SID and AUTH_TOKEN so we can use their client library without worrying about our credentials:

The Code

First things first, let’s write our basic Twilio function and pull in our dependencies:

const cloudinary = require('cloudinary')
const { MailParser } = require('mailparser')
const { toArray } = require('lodash')
exports.handler = function(context, messages, callback) {
  const twilio = context.getTwilioClient()
  cloudinary.config({
    cloud_name: context.CLOUDINARY_NAME,
    api_key: context.CLOUDINARY_KEY,
    api_secret: context.CLOUDINARY_SECRET
  })
}

The messages variable is an object full of the messages SparkPost handed off to us. We’ll need to loop through the messages, pull off the attachment, save it to Cloudinary, and send off the fax.

// after the cloudinary config
const promises = toArray(messages).map((message) => {
    const toNumber = pickPhoneNumber(message)
    return pickPdfAttachment(message)
      .then((attachment) => {
        if (!attachment) { return false }
        return uploadAttachment(attachment)
      })
      .then((mediaUrl) => {
        return sendFax(twilio, toNumber, mediaUrl)
      })
  })
  Promise.all(promises)
    .then(() => callback(null, 'success'))
    .catch((error) => callback(error))

Parsing The Email

You can see an example the payload SparkPost sends in the API documentation. The important parts for us are the rcpt_toand the content which will contain the raw email. These live inside message.msys.relay_message.

We can pull out the phone number by splitting off the local part from the rcpt_to value – everything before the @.

function pickPhoneNumber(message) {
  return message.msys.relay_message.rcpt_to.split('@')[0]
}

We’ll also need to pull the PDF off of the message. To do this, we’ll parse the RFC 822 value using the mailparser library and return the content of the first PDF attached.

function pickPdfAttachment(message) {
  return new Promise((resolve, reject) => {
    const content = message.msys.relay_message.content
    const isBase64 = content.email_rfc822_is_base64
    const body = isBase64 ? Buffer.from(content.email_rfc822, 'base64') : content.email_rfc822
    const parser = new MailParser({ streamAttachments: true })
    let attachment
    parser.on('data', (data) => {
      if(!attachment &&
          data.type === 'attachment' &&
          data.contentType === 'application/pdf') {
        attachment = data.content
      }
    })
    parser.on('error', reject)
    parser.write(body)
    parser.end(() => resolve(attachment))
  })
}

Saving The Attachment

Assuming we get an attachment, we’ll need to put it in an accessible spot for Twilio to pull it from. Enter Cloudinary. Using their Node library we can easily pipe the PDF and get the publicly accessible URL.

function uploadAttachment(attachment) {
  return new Promise((resolve, reject) => {
    const stream = cloudinary.v2.uploader.upload_stream((error, result) => {
      if (error) {
        reject(error)
      }
      else {
        resolve(result.url)
      }
    })
    attachment.pipe(stream)
  })
}

Sending The Fax

The last step for our code is to send the fax! Twilio makes this really easy. We need three values: the number we are sending to, the number we are sending from, and the media url. We’ll use the number we bought earlier to send for the “from” number. And we should have a phone number from the local part of the email and the media url from Cloudinary!

function sendFax(twilio, toNumber, mediaUrl) {
  return twilio.fax.v1.faxes.create({
      to: toNumber,
      from: YOUR_PHONE_NUMBER,
      mediaUrl: mediaUrl,
    })
    .then((res) => {
      console.log('sent', mediaUrl, 'to', toNumber)
    })
}

Create A Relay Webhook

The last piece of the puzzle it to tie our inbound domain from SparkPost to our Twilio function. Let’s create a relay webhook to pass all mail sent to our inbound domain onto our Twilio function. Copy your function path and pass it into this cURL request to create the relay webhook. Make sure that “Check for valid Twilio signature” is unchecked so that SparkPost can access it!

curl -XPOST   https://api.sparkpost.com/api/v1/relay-webhooks \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target": "YOUR_TWILIO_PATH", "match": { "domain": "YOUR_INBOUND_DOMAIN", "protocol": "SMTP" }, "name": "Email-to-Fax" }'

Send The Fax!! 🎉

Send an email over to FAX_NUMBER@YOUR_DOMAIN with your fax attached as a PDF and it should go through!

You can set up receiving faxes for a fully working email fax machine with some guidance from our friend Patrick at Twilio! Feel free to reach out if you’ve got any questions and have fun faxing!

-Avi

P.S. Before you launch this to the world, you’ll probably want to validate the phone numbers you’re sending to and add some security to who can send. I’d suggest a token in the email that you verify in the Twilio function.

Avi Goldman

I'm a Developer Advocate over at SparkPost. I spend my time thinking about email and how to improve it. Reach me on Twitter and lets get coffee.