Using Twilio Functions with ClojureScript
Time to read: 5 minutes
Twilio offers several APIs for programmable communication which rely on webhooks. Twilio Functions is a serverless runtime which can handle these webhooks by running your code on Twilio’s platform to generate customized responses to phone calls, text or WhatsApp messages, emails and more - without having to manage a server yourself. Functions supports JavaScript, and therefore any of the compile-to-js languages can be used too, such as TypeScript.
Here I’ll show how you can use Shadow CLJS, the Twilio CLI and Serverless Toolkit to create a Twilio Function in ClojureScript. If you want to jump straight to the final code, you can find the project on GitHub.
Prereqs
- Node.js 8.10 or newer
- Java 8 or higher (we won’t use this directly, but the ClojureScript compiler is a Java application)
- The Twilio CLI
Howto
Project Setup
Create a new Shadow CLJS project using npx
which is part of the Node.js distribution:
This creates a project using Shadow CLJS, in a directory called cljs-twilio-function
.
Creating a Function
Twilio Functions in JavaScript look like this:
So first create a ClojureScript namespace with a function which takes those three arguments and generates a response, then configure shadow-cljs to set up the export correctly.
Create a file in src/main
called my_twilio_function.cljs
, with the following content:
(Note “my twilio function” has underscores the the filename and dashes in the code - this is a quirk of the Clojure compiler).
That’s all the code needed for now, so move on to configuring Shadow to build this into the appropriate JavaScript. The shadow config file is shadow-cljs.edn
. Edn is Clojure’s Extensible Data Notation, which is a subset of Clojure syntax. Change the empty map {}
under the :builds
key to define how the build should work:
This configuration:
- defines a build called
function
which builds the code as a Node.js library - exports
my-function
asexports.handler
- sets the output file to
functions/my-function.js
Using :optimizations :advanced
enables a host of optimizations that shrink non-public names, inline functions, remove unused code and produce minified JavaScript in a single file. You can check that your shadow-cljs.edn
matches mine on GitHub.
Building and Running our Function
Compile the function with npx shadow-cljs release function
. The first time this runs it might cause some dependencies to be downloaded but this won’t happen in future builds:
This creates the file functions/my-function.js
which contains the function as highly-minified (and therefore quite unreadable) JavaScript.
Before deploying to the Twilio runtime, it’s possible to test the function locally using the Twilio CLI with the Serverless Toolkit. If these aren’t installed already, do so with:
Then run the function locally with twilio serverless:start
. The output will show that the function is available at http://localhost:3000/my-function. The name of the function is determined by the name of the js file, so can be customized in shadow-cljs.edn
.
If the twilio serverless:start
process is left running it will pick up any changes to existing functions when the JavaScript is recompiled. For now, I’d say that looks good, so move on to deploying the function to Twilio’s Serverless runtime.
Doing this needs a Twilio account. If you don’t have one you can sign up for free at twilio.com/try-twilio. From your account console you can find your Account SID and Auth Token which you will need next:
The serverless:deploy
command will create a Twilio Function, upload our code and create a public URL which we can call to invoke our code:
🎉🎉 You did it! 🎉🎉 As shown near the end of the output from twilio serverless:deploy
, the function is available publicly at https://cljs-twilio-function-XXXX-dev.twil.io/my-function
.
Using shadow-cljs we have compiled a ClojureScript function to JavaScript, set up the exports correctly, tested locally and deployed to the Twilio Serverless Runtime. Congratulations, it's time for a well-earned cup of tea ☕.
A Better Function
Lets modify the function so that it can respond to an SMS with a random Rich Hickey quote. To prevent abuse we should also configure it so that the function can only be invoked by Twilio webhook requests.
Responding to SMS
Webhooks called by Twilio are expected to respond using TwiML, which is a dialect of XML. Unfortunately the built-in XML tools in ClojureScript rely on the DOM APIs, which are only available in browser runtimes. No matter, we can use hiccups instead. You may be familiar with hiccups as a tool for creating html, but it can be easily (ab)used to generate any kind of XML. First, add it to the :dependencies section of shadow-cljs.edn
:
Then change the code in the my-twilio-function
namespace to use hiccups to generate TwiML (or copy-paste from the version on my GitHub). Some JavaScript interop is needed to set the content-type
header correctly. If you’re not sure about the interop, Rafael Spacjer’s blog post is my go-to resource, along with this cheatsheet.
As before, run npx shadow-cljs release function
and twilio serverless:start
(or perhaps you left that running earlier?) then load http://localhost:3000/my-function to see that the correct TwiML is being generated:
Let’s move on to some more interesting messages.
Adding the Random Hickey Quotes
I have created a quotes
namespace on my GitHub project which contains a few of my favourite Hickey quotes from Azel4321’s Clojure Quotes repo. You can copy mine straight from GitHub or write your own in src/main/quotes.cljs
, as you prefer.
Requiring the quotes
namespace and using rand-nth to select one, the function now looks like this:
Protecting our Function
To prevent Twilio Functions from being accessible outside of the context of a Twilio webhook call, we can require that the requests be signed. To enable this we only need to change the name of the generated JavaScript file to end .protected.js
instead of just .js
. In shadow-cljs.edn
:
The function can still be accessed as before when running on localhost but when deployed the platform will check for a valid signature before executing your code.
Building and Deploying
Again, build with npx shadow-cljs release function
. The path for the function will still be /my-function
so delete functions/my-function.js
to prevent the error “Duplicate. Path /my-function already exists”.
Deploy with twilio serverless:deploy
again, and the existing function will be replaced with this new one. Because this function now requires the webhook signature, attempting to load the public URL in a browser will result in an “HTTP 403 Forbidden” error.
To buy a phone number and configure Twilio to use this function to respond to SMS you can use the Twilio CLI:
Now you can hear from Rich Hickey whenever you like:
What Next?
- Read about how Shadow CLJS can mix-and-match npm dependencies with ClojureScript
- Check out Reagent for building powerful browser apps with ClojureScript
- Send SMS with Clojure
- Learn more Clojure with Clojure for the Brave and True and Elements of Clojure
- Learn about adding assets to your functions to host audio files for use with Programmable Voice, or images for MMS and WhatsApp.
I can’t wait to see what you build. Let me know by email mgilliard@twilio.com or on Twitter @MaximumGilliard
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.