There I was, in a desolate cabin with nothing except my maladaptive daydreams, 2G coverage, and ingredients for the only dish I had ever managed to write down splayed out on the countertops. There was just one problem. After desperately rummaging through the rental’s kitchen cabinets, I found only a single tablespoon hanging lonesomely on an otherwise empty ring.
The proportions of ingredients were important in this dish, but I hadn’t actually sat down and memorized all the conversion rates before. Without internet service, not even Google could save me, but I did my best to eyeball it and make due.
It turned out terrible, and I vowed to never be in such a predicament again.
Before we start the tutorial, make sure you have the following things:
Write and export a JSON file
So, to simplify things, we will use our IDE to create a JSON file that will store all our conversion rates so we can cut down on the amount of code we need to put directly into a Twilio Function.
First, navigate to a place on your computer you want to store your project files and enter these commands in your terminal to make a project directory:
mkdir json-exporter && cd json-exporter
Open up your IDE to the directory you just made. Create a new
.js file, and paste the following code:
const fs = require('fs'); const conversionRates = "tsp" : "tsp": "1", "tbsp": "1/3", "oz": "1/6", "c": "1/48.692", "q" : "1/192", "pt" : "1/96", "gal": "1/768", "ml" : "4.929", "l" : "1/202.9", , "tbsp" : "tsp" : "3", "tbsp" : "1", "oz": "1/2", "c": "1/16.231", "q" : "1/64", "pt" : "1/32", "gal": "1/256", "ml" : "14.787", "l" : "1/67.628", , "oz" : "tsp": "6", "tbsp": "2", "oz": "1", "c": "1/8", "q" : "1/32", "pt" : "1/16", "gal": "1/128", "ml" : "29.574", "l" : "1/33.814", , "c" : "tsp": "48", "tbsp": "16", "oz": "8", "c": "1", "q" : "1/4", "pt" : "1/2", "gal": "1/16", "ml" : "236.6", "l" : "1/4.227", , "q" : "tsp": "192", "tbsp": "64", "oz": "32", "c": "4", "q" : "1", "pt" : "2", "gal": "1/4", "ml" : "946.4", "l" : "1/1.057", , "pt" : "tsp": "96", "tbsp": "32", "oz": "16", "c": "2", "q" : "1/2", "pt" : "1", "gal": "1/8", "ml" : "473.2", "l" : "1/2.113", , "gal" : "tsp": "768", "tbsp": "256", "oz": "128", "c": "16", "q" : "4", "pt" : "8", "gal": "1", "ml" : "3785", "l" : "3.785", , "ml" : "tsp": "1/4.929", "tbsp": "1/14.787", "oz": "1/29.574", "c": "1/236.6", "q" : "1/946.4", "pt" : "1/473.2", "gal": "1/3785", "ml" : "1", "l" : "1/1000", , "l" : "tsp": "202.9", "tbsp": "67.628", "oz": "33.814", "c": "4.167", "q" : "1.057", "pt" : "2.113", "gal": "1/3.785", "ml" : "1000", "l" : "1", ; const jsonContent = JSON.stringify(conversionRates); fs.writeFile("output.json", jsonContent, 'utf8', function (err) if (err) console.log("An error occurred while writing JSON Object to File."); return console.log(err); console.log("JSON file has been saved."); );
Run this code. You will notice that a new file called
output.json shows up in the same directory as your
.js file. Keep note of this file, because we will be uploading it to a remote directory called Assets when we create a Function in the next section.
If you already have a solid understanding of how the above code works, feel free to skip to the next section where we proceed to Twilio Functions. If not, read on and I will explain what the code above is doing.
JSON exporter code explanation
conversionRates, which stores all the conversion rates between two different measurements in the form of
conversionRates[ ‘measurement converting from’ ][ ‘measurement converting to’ ]. The object’s use will be evident when we use the JSON file in a Twilio Service later on.
Below the object definition, a JSON function called stringify receives the
conversionRates object as an argument. This function converts the object into a JSON string and assigns it to the variable
jsonContent. At this point,
Finally, the fs writeFile function sets the file name to
output.json, writes the content of the file
jsonContent, encodes the file as
utf8 (the encoding system for Unicode), and uses a callback function
function(err) in case there were any errors in writing the file.
If successful, the console will show “JSON file has been saved”. If not, it will show an error message.
Create a service in the Twilio Console
Now that the JSON file is ready, let’s proceed with the setup of a Twilio Service where we upload the file and write a function!
Navigate to the Twilio Console and in the left-hand sidebar, click “Explore Products”. Then, under Developer tools, click “Functions and Assets” and then click on the blue “Create Service” button. Name the new service “converter”, and then click “Next”.
You should be redirected to a new page where you can see your empty service like the following:
The two areas to pay the most attention to are Functions and Assets.
Create an Asset
Click the blue “Add +” button directly above the Functions panel, and click “Upload file”. Then, navigate to where you saved the
output.json file. A new panel will appear in the editor with your file, along with radio buttons indicating whether you want this file to be Private, Protected, or Public. Choose “Public”.
Before clicking the blue “Upload” button, save the below image to your project directory because it will be uploaded as well:
Click the “Add Additional Files” button and upload the above image. This will be a nice visual aid for anyone who texts our Twilio number but does not provide a format that our converter recognizes. This image will be sent if the sender has cellular data available, in addition to a normal SMS message – just in case they find themselves in a desolate cabin.
At this point, this is what the files will look like.
Ensure both are set to Public, and then click “Upload”. This will upload two files under Assets, which are saved but not yet visible because we haven’t deployed the service yet.
Add dependencies and environment variables
In the Module text box, enter “node-fetch” and in the Version box enter “2.x”, like so:
Click the “Add” button and watch the dependency appear in the table of dependencies.
Next, navigate to Environment Variables which is located right above Dependencies. Click it to open up its own tab that looks very similar. Here you will create an environment variable to store your Twilio phone number.
Under “Key”, enter
TWILIO_PHONE_NUM and under “Value” enter the number in E.164 format. Click the white “Add” button and it should appear below.
Also ensure that the checkbox to add your Twilio account credentials is checked.
You are now all set to start coding the Function!
Write the conversion functionality
Click on the same blue “Add +” button you used to upload your assets, but now choose “Add Function”. This will create a new function path which defaults to the name
/path_1. Go ahead and rename this function to
/convert. Make sure the function is set to Protected and press “Enter”.
The editor has some pre-written code in it already. Go ahead and delete everything. Then, paste the following code in the editor for your
const fetch = require('node-fetch'); function convertMeasurement(conversionRates, qty, from, to)
Skip to the next section if you understand this code.
Explanation of convertMeasurement function
Remember when I mentioned we will find out how we are going to use the
conversionRates object in our function? Well, here it is! So, what’s happening here?
convertMeasurement accepts four parameters (
qty stores the measurement amount we are converting from. For example, if I wanted to convert 5 tbsp into tsp,
qty would be set to
from is the measurement being converted from (in the previous example, this would be tbsp),
to is the measurement being converted to (tsp).
The first thing the function does is check to make sure it can find the given
to measurements in the
conversionRates object using the hasOwnProperty function, which tells us whether a value is present in the object. If the object does not contain the given measurements, then it cannot convert them. It makes a final check to make sure that the
qty argument is of
number type because we need to do some math with it.
The eval function is notoriously dangerous if used to accept input from outside sources because it will willingly run malicious code. We use it here because it is the only function that can interpret fractions from strings (and strings with decimals within fractions). However, it is highly discouraged to use it when not in complete control of your input.
For our purposes, it is safe to use because our function is in an isolated environment and it only executes after the text message has been parsed and compared to the JSON file that we created ourselves.
The last two lines of the function are where the magic happens.
newQty calculates the amount needed in order to convert
to. We use
eval on both the
conversionRates[from][to] so we can interpret division and multiplication in one line. The returned string puts it all together by using a template literal, which is returned and eventually sent back in the reply. We also use the toFixed method to round our
newQty variable to two decimal places.
Write the exports handler function
The only way Twilio can run our function is if it has exports.handler method with a callback function as a way to signal the end of the function’s execution. In ours, we will call
exports.handler and craft two different responses based on whether or not the sender sent a parsable text message.
convertMeasurement function, paste the following in the function editor:
exports.handler = async function(context, event, callback) // Setting up a twiml MessagingResponse for a simple SMS reply const twiml = new Twilio.twiml.MessagingResponse(); // Accessing the body of the sender's message and parsing it const body = event.Body ? event.Body.toLowerCase() : null; const tokens = body.split(' '); const [quantity, fromMeasure,, toMeasure] = tokens; // Using the node-fetch module to access the JSON file we uploaded let obj = ; try const response = await fetch("YOUR OUTPUT JSON LINK"); obj = await response.json(); const s = JSON.stringify(obj); obj = JSON.parse(s); catch(e) console.log("An error occurred while fetching JSON: ", e); // Calling convertMeasurement, and if conversion is successful, will send an SMS reply const result = convertMeasurement(obj,quantity,fromMeasure,toMeasure); if (result !== 'Unknown') twiml.message(result); return callback(null, twiml); // If convertMeasurement returns 'Unknown', we will send our chart and a different message const msg = 'Hi there! Please make sure your message is formatted correctly. ' + 'Accepted measurements include: tsp, tbsp, oz, c, q, pt, gal, ml, and l.' + '\n\nSee the chart for reference and note accepted measurements are ' + 'in the right column.\n\nExample usage: "5 c to tbsp" or ".5 gal to l"'; const twilioClient = context.getTwilioClient(); // Configured in "Environment Variables" tab twilioClient.messages .create( from: context.TWILIO_PHONE_NUM, to: event.From, mediaUrl: 'YOUR CONVERSION CHART JPG LINK', body: '' ) .then((message) => console.log('MMS successfully sent'); return callback(null, msg); ) .catch((error) => console.error(error); return callback(error); ); ;
exports.handler function receives the text message from the sender (accessed via
event.Body), and parses it for the
to arguments to use in the
The first block after parsing is a try/catch block where we attempt to parse the JSON file that we uploaded. If you turn your attention to the highlighted portion of the code in this block, you will see a placeholder in the
’YOUR OUTPUT JSON LINK’. We need to gather the link to our JSON file asset and paste it here. This link can be found by clicking the three stacked dots next to the asset and clicking “Copy URL”.
If the try/catch block was successful, the
convertMeasurement function returns a string that either contains the conversion or
Unknown. We make sure that the returned string does not equal ‘Unknown’ in an if statement, and if
true, will use a twiml.message to store the string. The callback function then sends the twiml message back to the sender, which concludes the execution of the function.
However, if the returned string does equal
Unknown, the code constructs a response indicating that the sender hadn’t sent a properly formatted text and sends our
conversion-chart.jpg with the message. Twilio Functions automatically connect your account credentials through a REST client with the line
const twilioClient = context.getTwilioClient();.
Similar to the JSON file asset, we will need to copy the URL in the same way and paste it into the
mediaUrl: 'YOUR CONVERSION CHART JPG LINK'.
Once these two lines are changed, click the blue “Save” button below the editor window and then click “Deploy All”.
Not all Twilio numbers are capable of sending MMS messages. Ensure your number is MMS enabled before continuing to the next section.
Connect your Twilio phone number
Go back to the Twilio Console dashboard and navigate to your Active Numbers in the left sidebar under “Phone Numbers > Manage > Active Numbers”. Choose the number that you added as an environment variable earlier in the tutorial and scroll down to Messaging.
In the boxes under “A MESSAGE COMES IN” choose “Function”, under SERVICE choose “converter’”, under ENVIRONMENT choose –“ui”, and in the last box select our
Click the blue “Save” button at the bottom of the page, and let’s test this out!
Test the converter
At this point, our number should be ready to receive text messages. Send a few and see what happens!
Notice in my testing when I sent an SMS that the converter didn’t recognize, it forwarded me the JPG, but when I did send an SMS in a recognized format, it sent me back some pretty accurate conversions!
If your text messages don’t appear to be working, see this article on how to debug your Twilio Functions. Error logs contain a wealth of information that can tell you what might be going on.
Now you are familiar with Twilio Functions
Congratulations on building a cooking measurement converter that even Gordon Ramsay would be proud of! It doesn’t have to end here though, as there are literally millions of things you can build now that you know your way around Twilio Functions. Be sure to check out our other Twilio blog posts if you still want more practice or inspiration for your next project. Whatever it is you decide to make in the future, I hope you have fun and make it tasty!
Hayden Powers is a Developer Voices intern on Twilio’s Developer Network team. She enjoys enthusing over creative ideas and endeavors, and hopes you will reach out to her with yours at hpowers[at]twilio.com or on LinkedIn.