Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
Writing our own Haskell code using only simple libraries is fun. But we canât do everything from scratch. There are all kinds of cools services out there to use so we donât have to. We can interface with a lot of these by using APIs. Often, the most well supported APIs use languages like Python and Javascript. But adventurous Haskell developers have also developed bindings for these systems! So in the next few weeks, weâll be exploring a couple of these. Weâll also see what we can do when there isnât an out-of-the-box library for us to use.
This week, weâll focus on the Twilio API. Weâll see how we can send SMS messages from our Haskell code using the twilio library. Weâll also write a simple server to use Twilioâs callback system to receive text messages and process them programmatically. You can follow along with the code here on the Github repository for this series.
Of course, none of this is useful if youâve never written any Haskell before! If you want to get started with the language basics, download our Beginners Checklist. To learn more about advanced techniques and libraries, grab our Production Checklist!
Setting Up Our Account
Naturally, youâll need a Twilio account to use the Twilio API. Once you have this set up, you need to add your first Twilio number. This will be the number youâll send text messages to. Youâll also see it as the sender for other messages in your system. You should also go through the process of verifying your own phone number. This will allow you to send and receive messages on that phone without âpublishingâ your app.
You also need a couple other pieces of information from your account. Thereâs the account SID, and the authentication token. You can find these on the dashboard for your project on the Twilio page. Youâll need these values in your code. But since you donât want to put them into version control, you should save them as environment variables on your machine. Then when you need to, you can fetch them like so:
fetchSid :: IO StringfetchSid = getEnv âTWILIO_ACCOUT_SIDâ
fetchToken :: IO StringfetchToken = getEnv âTWILIO_AUTH_TOKENâ
Sending a Message
The first thing weâll want to do is use the API to actually send a text message. We perform Twilio actions within the Twilio monad. Itâs rather straightforward to access this monad from IO. All we need is the runTwilioâ function:
runTwilioâ :: IO String -> IO String -> Twilio a -> IO a
The first two parameters to this function are IO actions to fetch the account SID and auth token. We've already written those. Then the final parameter of course is our Twilio action.
sendMessage :: IO ()sendMessage = runTwilioâ fetchSid fetchToken $ do ...
To compose a message, weâll use the PostMessage constructor. This takes three parameters. First, the âtoâ number of our message. Fill this in with the number to your physical phone. Then the second parameter is the âfromâ number, which has to be our Twilio accountâs phone number. Then the third parameter is the message itself. To send the message, all we have to do is use the postfunction! Thatâs all there is to it!
sendMessage :: IO ()sendMessage = runTwilioâ fetchSid fetchToken $ do let msg = PostMessage â+15551231234â â+15559879876â âHello Twilio!â _ <- post msg return ()
And just like that, youâve sent your first Twilio message! Note that it does cost a small amount of money to send messages over Twilio. But a trial account should give you enough free credit to experiment a little bit.
Receiving Messages
Now, itâs a little more complicated to deal with incoming messages. The first thing we need to do is create a webhook on our Twilio account. To do this, go to âManage Numbersâ from your project dashboard page. Then select your Twilio number. Youâll now want to scroll to the section called âMessagingâ and then within that, find âA Message Comes Inâ. You want to select âWebhookâ in the dropdown. Then youâll need to specify a URL where your server is located, and select âHTTP Postâ. For setting up a quick server, I use Heroku combined with this nifty build pack that works with Stack. Iâll go into that in more depth in a later article. But the main thing to see is that our endpoint is /api/sms.
With this webhook set up, Twilio will send a post request to the endpoint every time a user texts our number. The request will contain the message and the number of the sender. So letâs set up a server using Servant to pick up that request.
Weâll start by specifying a simple type to encode the message weâll receive from Twilio:
data IncomingMessage = IncomingMessage { fromNumber :: Text , body :: Text }
Twilio encodes its post request body as FormURLEncoded. In order for Servant to deserialize this, weâll need to define an instance of the FromForm class for our type. This function takes in a hash map from keys to lists of values. It will return either an error string or our desired value.
instance FromForm IncomingMessage where fromForm :: Form -> Either Text IncomingMessage fromForm (From form) = ...
So form is a hash map, and we want to look up the âFromâ number of the message as well as its body. Then as long as we find at least one result for each of these, weâll return the message. Otherwise, we return an error.
instance FromForm IncomingMessage where fromForm :: Form -> Either Text IncomingMessage fromForm (From form) = case lookupResults of Just ((fromNumber : _), (body : _)) -> Right $ IncomingMessage fromNumber body Just _ -> Left âFound the keys but no valuesâ Nothing -> Left âDidnât find keysâ where lookupResults = do fromNumber <- HashMap.lookup âFromâ form body <- HashMap.lookup âBodyâ form return (fromNumber, body)
Now that we have this instance, we can finally define our API endpoint! All it needs are the simple path components and the request body. For now, we wonât actually post any response.
type TwilioServerAPI = "api" :> "sms" :> ReqBody '[FormUrlEncoded] IncomingMessage :> Post '[JSON] ()
Writing our Handler
Now letâs we want to write a handler for our endpoint. First though, weâll write a natural transformation so we can write our handler in the Twilio monad.
transformToHandler :: Twilio :~> HandlertransformToHandler = NT $ \action -> liftIO $ runTwilio' fetchSid fetchToken action
Now weâll write a simple handler that will echo the userâs message back to them.
twilioNum :: TexttwilioNum â+15559879876â
smsHandler :: IncomingMessage -> Twilio ()smsHandler msg = do let newMessage = PostMessage (fromNumber msg) twilioNum (body msg) _ <- post newMessage return ()
And now we wrap up with some of the Servant mechanics to run our server.
twilioAPI :: Proxy TwilioServerAPItwilioAPI = Proxy :: Proxy TwilioServerAPI
twilioServer :: Server TwilioServerAPItwilioServer = enter transformToHandler smsHandler
runServer :: IO ()runServer = do port <- read <$> getEnv âPORTâ run port (serve twilioAPI twilioServer)
And now if we send a text message to our Twilio number, weâll see that same message back as a reply!
Conclusion
In this article, we saw how we could use just a few simple lines of Haskell to send and receive text messages. There was a fair amount of effort required in using the Twilio tools themselves, but most of that is easy once you know where to look! Come back next week and weâll explore how we can send emails with the Mailgun API. Weâll see how we can combine text and email for some pretty cool functionality.
An important thing making these apps easy is knowing the right tools to use! One of the tools we used in this part was the Servant web API library. To learn more about this, be sure to check out our Haskell Web Skills Series. For more ideas of web libraries to use, download our Production Checklist.
And if youâve never written Haskell before, hopefully Iâve convinced you that it IS possible to do some cool things with the language! Download our Beginners Checklist to get stated!
Sending Texts with Twilio + Haskell! was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.