Crystal is a young programming language with a whole load of potential and it's been a lot of fun playing around with it over the past few weeks. I want to share with you how easy it is to send SMS messages with Twilio using Crystal.
The Crystal project started in September 2012 and is still going strong. The language hasn't reached version 1 yet, but there are many things about it to get excited about. The goals of the language are listed front and centre on the Crystal site, but briefly, it is a type-inferred, compiled language with Ruby-like syntax that is blazingly fast. To get started, let's take a look at a simple Crystal program:
def fib(n)
if n <= 1
1
else
fib(n - 1) fib(n - 2)
end
end
time = Time.now
puts fib(42)
puts Time.now - time
If you've used Ruby before, then this will be instantly recognisable as a function to calculate the nth Fibonacci number. It's so recognisable that if you were to run this with Ruby the program would run correctly. Of course you can run it with Crystal too.
Pretty amazing, right? And Crystal is orders of magnitude quicker than Ruby in this test. Of course, the Fibonacci sequence is a terrible benchmark and this isn't exactly a fair test either, but it's interesting to see the similarities. As you start to dig into Crystal, however, the differences between this language and Ruby start to emerge.
Let's do that now and make some calls to the Twilio API using Crystal.
Getting started with Crystal
To use Crystal, you'll first need to install the compiler. For Linux, follow the installation instructions in the Crystal docs. If you're on a Mac you can install Crystal with homebrew with the following:
$ brew update
$ brew install crystal-lang
Sadly, Crystal doesn't currently compile on Windows operating systems.
You can check that Crystal is successfully installed by running:
$ crystal --version
Crystal 0.19.4 (2016-10-21)
We need a Twilio account for the next part, so if you haven't got an account, sign up for free here. We will also need a Twilio number that can send SMS messages.
Time to make some API calls with Crystal!
Sending an SMS message with Crystal
Normally when I write about sending an SMS I would point you towards our official helper libraries. As Crystal is still relatively new, there is no such library, so we're going to have to roll up our sleeves and investigate the HTTP module.
Create a new Crystal file named sms.cr
:
$ touch sms.cr
Open the file and start by requiring the http/client
class from the standard library. Start a new function for sending SMS messages using Ruby's familiar def
syntax. We'll need three arguments; a number to send the message to, the number we're sending from and the body of the message.
require "http/client"
def send_sms(to, from, body)
end
Setting up the HTTP Client
Initialise an HTTP::Client
with the base URL of the API the port number and whether the application uses TLS. We can then pass a block to this initialiser and the client will be closed once we are done with it.
require "http/client"
def send_sms(to, from, body)
HTTP::Client.new("api.twilio.com", 443, true) do |client|
end
end
Now that we have our client, we need to add our authentication header using our Twilio Account SID and Auth Token as the username and password. I'm keeping my credentials as environment variables so that I don't accidentally share them publicly. You can set environment variables on the command line with the export
command.
$ export TWILIO_ACCOUNT_SID=AC123456789abcdef
$ export TWILIO_AUTH_TOKEN=1q2w3e4r5t6y7u8i9op0
Use the basic_auth
method on the HTTP::Client
instance to authenticate the client.
require "http/client"
def send_sms(to, from, body)
HTTP::Client.new("api.twilio.com", 443, true) do |client|
client.basic_auth(ENV["TWILIO_ACCOUNT_SID"], ENV["TWILIO_AUTH_TOKEN"])
end
end
Making the HTTP request
Now we can make our request. To send an SMS message we need to make a POST request to the Messages resource. We build the URL using our Account SID. We need to send the properties of our message as application/x-www-form-urlencoded
parameters, and we can use Crystal's built in post_form
method for this:
require "http/client"
def send_sms(to, from, body)
HTTP::Client.new("api.twilio.com", 443, true) do |client|
client.basic_auth(ENV["TWILIO_ACCOUNT_SID"], ENV["TWILIO_AUTH_TOKEN"])
response = client.post_form("/2010-04-01/Accounts/#{ENV["TWILIO_ACCOUNT_SID"]}/Messages.json", {
"To" => to,
"From" => from,
"Body" => body,
})
end
end
We could now call this function and send our first SMS message from Crystal. But we would only know whether it was a success once we received the message. Let's do a little more work to check whether the API call was successful and if so print out the message SID otherwise print out the error message.
Handling the response
Include the JSON module from the standard library and parse the response body. If it's a success we can look for the SID otherwise the error response will have a message field to tell us what went wrong.
require "http/client"
require "json"
def send_sms(to, from, body)
HTTP::Client.new("api.twilio.com", 443, true) do |client|
client.basic_auth(ENV["TWILIO_ACCOUNT_SID"], ENV["TWILIO_AUTH_TOKEN"])
response = client.post_form("/2010-04-01/Accounts/#{ENV["TWILIO_ACCOUNT_SID"]}/Messages.json", {
"To" => to,
"From" => from,
"Body" => body,
})
result = JSON.parse(response.body)
if response.success?
puts result["sid"]
else
puts result["message"]
end
end
end
Now add one final line to sms.cr
to call the method with your own phone number in the to
position, a Twilio number in the from
position and your SMS message.
# sms.cr
def send_sms(to, from, body)
# function code
end
send_sms(ENV["MY_NUMBER"], ENV["MY_TWILIO_NUMBER"], "Hello from Crystal!")
Compiling and running
Compile and run the function and celebrate your first SMS sent from Crystal!
$ crystal sms.cr
SMadca071a5bab4848892d9f24863e99e6
The crystal command compiles and runs the file you pass to it. You can also build a fully optimised executable that's ready for production with the build
command.
$ crystal build sms.cr --release
$ ./sms
SMadca071a5bab4848892d9f24863e99e6
We've sent our first message, but there's more to learn before we finish this chapter. JSON parsing using the JSON.parse
method is fine, but you'll remember that I described Crystal as type-inferred at the start of the post. Parsing arbitrary data structures like JSON in a typed language can be awkward and require you to cast your properties to the expected type before using them. As it happens, the type of each property parsed using JSON.parse
is JSON::Any
and when we call puts
with the object it is cast under the hood to a string with to_s
.
Improved JSON parsing
Instead of using this JSON::Any
type, we can actually tell Crystal's JSON parser what to look for with a JSON mapping. This will have the effect of being "easy, type-safe and efficient" according to the documentation.
Open up sms.cr
again and add simple mappings for our message and error objects.
# sms.cr
class Message
JSON.mapping(
sid: String,
body: String
)
end
class Error
JSON.mapping(
message: String,
status: Int32
)
end
There are more fields available on each of those objects, but we are only interested in those for now. Now, instead of using JSON.parse
we can use the from_json
constructor for each of our Message
and Error
classes. The mapping creates instance variables, getters and setters for the fields we define so we can use dot notation to access them.
def send_message(to, from, body)
client = HTTP::Client.new("api.twilio.com", 443, true) do |client|
client.basic_auth(ENV["TWILIO_ACCOUNT_SID"], ENV["TWILIO_AUTH_TOKEN"])
response = client.post_form("/2010-04-01/Accounts/#{ENV["TWILIO_ACCOUNT_SID"]}/Messages.json", {
"To" => to,
"From" => from,
"Body" => body,
})
if response.success?
message = Message.from_json(response.body)
puts message.sid, message.body
else
error = Error.from_json(response.body)
puts error.status, error.message
end
end
end
If you run the file with crystal sms.cr
again you will receive your message and be safe in the knowledge that your Message
object is now correctly typed and efficiently parsed from the JSON response.
Crystal is pretty cool
In my early explorations with Crystal I found it as pleasurable to write as Ruby with the addition of type safety and speed. In this post we've seen how easy it is to use Crystal to make HTTP requests against the Twilio API and send SMS messages. If you want to see the full code, check out this repository on GitHub.
If you like the look of Crystal you can learn more about it at the official site and by checking out the documentation. There are a bunch of interesting projects listed on the awesome-crystal repo and if you're coming at this from Ruby, like me, take a read through the Crystal for Rubyists book.
What do you think about Crystal? Does it look like a promising language to you? Let me know your thoughts in the comments below or drop me an email or message on Twitter.
Send SMS messages with Crystal and Twilio was originally published on the Twilio blog on November 3, 2016.