Send an SMS with Rust in 30 Seconds

August 10, 2023
Written by
Reviewed by

SMS is still one of the best ways to reach and engage with people around the world in 2023!

While it may seem like an old — even outdated — technology, nothing could be further from the truth. For example, according to WebTribunal SMS has an open rate of 98% and 23 billion SMS are sent every day.

So, if you want to integrate SMS into your Rust applications, then let me show you how with Twilio's Programmable Messaging API.

Prerequisites

You don't need a lot to follow along with this tutorial, just:

Generate a new Rust project

Start off by running the following commands to generate a new Rust project and change into the new project directory.

cargo new sms-in-30-seconds --bin
cd sms-in-30-seconds

Add the required Crates

Then, open up the new sms-in-30-seconds directory on your preferred IDE and add the five required dependencies by updating the dependencies section of Cargo.toml to match the following.

dependencies]
dotenv = "0.15.0"
reqwest = {version="0.11.13"

Here's what's going on:

  • Reqwest: This package will simplify sending requests to Twilio's Programmable Messaging API
  • rust-dotenv: This package helps keep secure information, such as API keys and tokens, out of code, by setting them as environment variables
  • serde, serde_derive, and serde_json: These packages reduce the complexity of deserialising JSON responses from Twilio, so that they can be more easily used

Set the required environment variables

Now, create a new file called .env in the project's top-level directory, and paste the following code into it.

TWILIO_AUTH_TOKEN="<<TWILIO_AUTH_TOKEN>>"
TWILIO_ACCOUNT_SID="<<TWILIO_ACCOUNT_SID>>"
TWILIO_PHONE_NUMBER="<<TWILIO_PHONE_NUMBER>>"
RECIPIENT_PHONE_NUMBER="<<RECIPIENT_PHONE_NUMBER>>"

After that, retrieve your Twilio Auth Token, Account SID, and phone number from the Twilio Console Dashboard and insert them in place of the first three placeholders, respectively. Then, replace the fourth placeholder with the E.164-formatted phone number of the recipient.

Write the code

Now, update src/main.rs to match the following code.

use dotenv::dotenv;
use reqwest::{blocking::Client, Error, StatusCode};
use serde::{Deserialize, Serialize};
use std::env;

#[derive(Serialize, Deserialize)]
struct SMSResponse {
    account_sid: Option<String>,
    api_version: String,
    body: String,
    date_created: String,
    date_sent: String,
    date_updated: String,
    direction: String,
    error_code: String,
    error_message: String,
    from: String,
    messaging_service_sid: String,
    num_media: String,
    num_segments: String,
    price: String,
    price_unit: String,
    sid: String,
    status: String,
    subresource_uris: SubresourceUris,
    to: String,
    uri: String,
}

#[derive(Serialize, Deserialize)]
struct SubresourceUris {
    all_time: String,
    today: String,
    yesterday: String,
    this_month: String,
    last_month: String,
    daily: String,
    monthly: String,
    yearly: String,
}

#[derive(Serialize, Deserialize)]
struct ErrorResponse {
    code: u16,
    message: String,
    more_info: String,
    status: u16
}

fn handle_error(body: String) {
    let error_response: ErrorResponse = serde_json::from_str(&body).expect("Unable to deserialise JSON error response.");
    println!("SMS was not able to be sent because: {:?}.", error_response.message);
}

fn handle_success(body: String) {
    let sms_response: SMSResponse = serde_json::from_str(&body).expect("Unable to deserialise JSON success response.");
    println!("Your SMS with the body \"{:?}\".", sms_response.body);
}

fn main() -> Result<(), Error> {
    dotenv().ok();

    let twilio_account_sid =
        env::var("TWILIO_ACCOUNT_SID").expect("Twilio Account SID could not be retrieved.");
    let twilio_auth_token =
        env::var("TWILIO_AUTH_TOKEN").expect("Twilio Auth Token could not be retrieved.");
    let twilio_phone_number =
        env::var("TWILIO_PHONE_NUMBER").expect("The Twilio phone number could not be retrieved.");
    let recipient_phone_number = env::var("RECIPIENT_PHONE_NUMBER")
        .expect("The recipient's phone number could not be retrieved.");

    let sms_body = "G'day from Rust and Twilio".to_string();

    let request_url =
        format!("https://api.twilio.com/2010-04-01/Accounts/{twilio_account_sid}/Messages.json");

    let client = Client::new();
    let request_params = [
        ("To", &recipient_phone_number),
        ("From", &twilio_phone_number),
        ("Body", &sms_body),
    ];
    let response = client
        .post(request_url)
        .basic_auth(twilio_account_sid, Some(twilio_auth_token))
        .form(&request_params)
        .send()?;

    let status = response.status();
    let body = match response.text() {
        Ok(result) => result,
        Err(error) => panic!(
            "Problem extracting the JSON body content. Reason: {:?}",
            error
        ),
    };

    match status {
        StatusCode::BAD_REQUEST => handle_error(body),
        StatusCode::OK => handle_success(body),
        _ => println!("Received status code: {}", status),
    }

    Ok(())
}

The code initialises three structs: the first two, collectively, hold successful responses, and the third holds error responses. Then, two functions are defined which deserialise responses into the correct structs.

Then, the main() function loads the variables you defined in .env as environment variables and initiates four variables to hold the respective information. After that, it uses Reqwest to send a POST request to Twilio's Programmable SMS API. The request will include POST data to set the recipient (To), the sender (From), and the SMS' body (Body).

If the request fails the user is notified with a short message written to the terminal. Otherwise, the response's status code is used to determine whether the response was successful or not and deserializes it accordingly.

If the request was successful, a confirmation message, including the SMS body, is written to the terminal. Otherwise, the user is informed that the SMS was not able to be sent.

Test the application

With the code written, it's now time to test it by running the command below.

cargo run

First, the dependencies will be downloaded and compiled. Then, src/main.rs will be compiled and executed. After the code's finished running, you should see an SMS similar to the screenshot below.

An example of the SMS that the application sends, received with iMessage.

That's how to send an SMS in about 30 seconds with Rust

Well, perhaps a little longer than 30 seconds. While Twilio doesn't officially support Rust, yet, it doesn't take a lot of work to send SMS' with Rust, thanks to the simplicity of Twilio's Programmable Messaging API, and a few Rust Crates.

If you had any issues following along, grab a copy of the code from GitHub. Otherwise, I'd love to know how you'd improve the code.

Matthew Setter is a PHP/Go Editor in the Twilio Voices team and a PHP, Go, and Rust developer. He’s also the author of Mezzio Essentials and Deploy With Docker Compose. When he's not writing PHP code, he's editing great PHP articles here at Twilio. You can find him at msetter[at]twilio.com, on LinkedIn, Twitter, and GitHub.