Introduction
In this tutorial we are going to learn how to use Twilio’s API to send a SMS from the ESP32, using the Arduino core.
Twilio is a Cloud communications Platform as a Service company that, among many other features, offers an API that allows to programmatically send SMS [1]. Although being a paid service, Twilio offers a trial account that we can use to do some testing of the features its platform offers. We are assuming the usage of the trial account for this tutorial.
You can check the SMS API documentation here.
Although the code section details most of the steps we will be doing to be able to reach Twilio’s API and send the SMS, you may consult these previous ESP32 posts for more details about some parts of the code:
- Sending HTTP POST Requests: Explains the basics on how to perform HTTP POST requests from the ESP32, such as initializing the request, adding headers and doing the actual request.
- Sending HTTPS Requests: Covers how to perform HTTPS requests from the ESP32 and how to provide a CA certificate. Although, for simplicity, we are not going to use any certificate in our code, it is a good ideia to provide one in a real scenario, due to security reasons.
- Adding Basic Authentication to a HTTP request: Explains how to add basic authentication when performing a HTTP request from the ESP32. In the mentioned tutorial we build the header ourselves, which gives a more detailed view of what is done under the hood. Here, for simplicity, we will use a method from the Arduino core that only receives the username and password and takes care of the details.
Please note that we are not going to be using any Twilio library to take care of the HTTP details. This means that we are going to build the HTTP request ourselves (which is very simple, as we will see below). Nonetheless, at the time of writing, I found this library that hides these details from us. Although I didn’t test it and cannot confirm it works as expected, I’ll leave it here as reference for those who may be interested in trying it.
The tests shown below were performed on a ESP32-E FireBeetle board from DFRobot. The Arduino core version used was 2.0.0 and the Arduino IDE version was 1.8.15, working on Windows 8.1. The code was also tested on Platformio and the screenshots from the tests shown below were taken from this platform.
Getting Twilio account credentials
We are not going to be covering here all the details on how to create a trial account or its limitations, as these may change with time. So, please follow the guide from Twilio, as it details all the necessary steps.
At the time of writing, we need a valid email address to register on the platform and we need to validate a personal phone number, as we are only allowed to send test SMS to that validated number while using the Trial account. We will also to setup a Messaging Service, which can be done for free at the time of writing.
In order to be able to send SMS, we will need the following information, which can be obtained from the Twilio account pages:
- Account Number (SID)
- API Auth Token
- Messaging Service SID
To get the account number and the Authentication token, we can navigate on the left side menu (after we log in to Twilio), on the “Messaging” separator, on the “Get Set Up” entry. There, we can obtain both the Account SID and the Auth token, as highlighted in green on figure 1.
To get the Messaging Service ID, we can click the “Manage and view all your Messaging Services” option, also highlighted on figure 1. Once you get to the corresponding page, you should see a table with the Messaging Services you have, and one of the columns contains the corresponding SID, like displayed on figure 2.
If you don’t see any Messaging Service on the table, you must create one first.
After we have obtained these 3 credentials, we have all the information we need from our Twilio account to start sendind SMS programmatically from the ESP32.
Before moving to the ESP32, we can also try the functionality of sending the SMS from the “Send an SMS” menu, as shown in figure 3. It even displays code samples for different programming languages (using Twilio’s SDK) and using curl. It is recommended to test first using this method, to ensure that the cellphone we registered can be reached, before trying to run the ESP32 code.
Please note that all the UIs shown before may have changed by the time you consult this post.
The code
We will start our code by the library includes. We will need the followng libraries:
- WiFi.h: as usual, we need to include the WiFi.h lib to be able to connect the ESP32 to a WiFi network, so it can then reach the Internet.
- HTTPClient.h: exposes an easy to use interface to perform HTTP requests.
- sstream: allows the usage of the stringstream class, which we will use to be able to concatenate strings in a very simple way.
#include <WiFi.h>
#include <HTTPClient.h>
#include <sstream>
We will also need the credentials of the WiFi network (name and password), so we are able to connect the ESP32 to that network. Don’t forget to replace the placeholders I’m using below by the actual credentials of your own network.
const char* ssid = "yourNetworkName";
const char* password = "yourNetworkName";
We will now define 4 additional placeholders for information necessary to send the SMS (3 of them are the ones we obtained on the previous section):
- Messaging service SID: The identifier of the messaging service.
- Destination phone number: The number that we want to send an SMS to. This number should include the country code. For example, in my case the destination is a portuguese phone number, meaning it should start with “+351“. Don’t forget that this number must have been validated before on the Twilio platform.
- Account number (SID): number of the account. It is necessary both for the URL of the API and also for the authentication.
- Password (API key): The API key that is used as password on the authentication.
const char * messagingServiceSid = "yourMessagingServiceId";
const char * to = "destinationPhoneNumber";
const char * accountNr = "yourAccountNr;
const char * twilioPassword = "yourApiKey";
Moving on to the Arduino setup, we will begin by opening a serial connection, so we can later output some info messages.
Serial.begin(115200);
After that, we will connect the ESP32 to the WiFi network, using the previously provided credentials.
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
We will encapsulate the actual SMS sending logic inside a function called sendSMS, which receives as input a string with the body of the message to be sent to the destination number. We will check the implementation of the function below, but we will assume it returns a Boolean indicating if the procedure was successful or not, for error checking.
bool result = sendSMS("Test SMS from ESP32");
if(result){
Serial.println("\nSMS sent");
}else{
Serial.println("\nSMS failed");
}
The complete Arduino setup is available in the code snippet below.
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
bool result = sendSMS("Test SMS from ESP32");
if(result){
Serial.println("\nSMS sent");
}else{
Serial.println("\nSMS failed");
}
}
Since we are using Twilio’s free account, which has a limited balance, we will send the message only once in the Arduino setup, meaning we will leave the Arduino main loop empty.
void loop() { }
To finalize, we will check the implementation of the sendSMS function, which receives a string as input and returns a Boolean indicating if the process was completed with success.
bool sendSMS(const char * body){
// Send SMS using Twilio's API
}
The first thing we will do is building the API URL, which has the account number as route parameter. Naturally, we could have hardcoded it here, but it will be easier to change in the future if we just have this value as a variable at the top of our code.
Basically, the URL is composed by multiple parts we want to concatenate. We will use a stringstream and the << operator to build the final string with the complete URL.
The base URL is “https://api.twilio.com/2010-04-01” [2]. Since sending an SMS will be tied to our account, we have then the Accounts resource, followed by the ID of our account (which we stored in a global variable). Finally, we need to create a Message resource [3]. As such, the full URL is the following [3]:
https://api.twilio.com/2010-04-01/Accounts/{yourAccountSid}/Message
Note that the API only works over HTTPS to ensure data privacy [2]. For our test case, we are not providing any certificate to be validated by our HTTP client, but in a real application you should use it.
In sum, the code to build a string with the final URL from its parts is shown below.
std::stringstream url;
url << "https://api.twilio.com/2010-04-01/Accounts/" << accountNr <<"/Messages";
Similarly, we are going to build the body payload as a string, using again the stringstream class. In our case, we need to specify the following parameters in the body:
- Destination number: The destination phone number, which we have in a global variable. In trial mode, this number must have been verified [3]. In the body, the parameter is called To.
- Messaging Service SID: The identifier of the Messaging Service, which we also have in a global variable. In the body the parameter is called MessagingServiceSid.
- SMS content: the actual content of the SMS, which should be the string we received as input of our sendSMS function. In the body, the parameter is called Body.
The format of this body should be form URL encoded. As such, we will build the body from its parts accordingly to this format.
std::stringstream urlEncodedBody;
urlEncodedBody << "MessagingServiceSid=" << messagingServiceSid << "&To=" << to << "&Body=" << body;
To confirm that we are obtaining the correct format for both the URL and the body, we will print the resulting values to the serial port. Naturally, this is done for debugging purposes and, on a final application, we wouldn’t need to do this.
To obtain the content of a stringstream as a std::string, we simply need to call the str method, which takes no arguments. Since the Serial print methods do not support the std::string class, we need to call the c_str method afterwards to obtain the C style version of the string.
Serial.print("\nURL: ");
Serial.println(url.str().c_str());
Serial.print("Encoded body: ");
Serial.println(urlEncodedBody.str().c_str());
Now that we have both the destination URL and the body, we will start building our HTTP request. We start by creating an object of class HTTPClient.
HTTPClient http;
Then, to initialize the request and specify the destination URL, we call the begin method on our HTTPClient object, passing as input the URL. Once again, we call the str method to obtain the corresponding std::string and then we call the c_str method.
Note that, if you want to add a CA certificate to validate the request, you can use this overloaded version of the begin method. If you want to learn more about HTTPS and certificate validation on the ESP32, please check this previous post.
http.begin(url.str().c_str());
We must also specify what is the content-type of the body by setting the corresponding header, so the server we are reaching knows how to interpret the body data. To add a header to the request, we simply call the addHeader method, passing as first input the name of the header and as second the value of the header.
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
The requests to the API must include Basic Authentication, where the username is the Account Number (SID) and the password is the API key [2]. To add the authorization credentials, we must call the setAuthorization method on our HTTPClient object, passing as input the Account Number and the API key (both stored in global credentials).
http.setAuthorization(accountNr, twilioPassword);
Like already mentioned, we are going to create a Message resource when invoking the API to send the SMS [3]. As such, we will use a HTTP POST method. To do so, we simply need to call the POST method on our HTTPClient object, passing as input the URL encoded body we have created before. For a more detailed tutorial on how to perform HTTP POST requests, please check here.
As output, this method will return the HTTP response code (in case the request is successfuly performed). However, if this value is lesser than 0, then an error in the connection has occurred. We will store this result in a variable for error checking.
int httpCode = http.POST(urlEncodedBody.str().c_str());
In case the response is a standard HTTP code (greater than zero), we will print that code and also the body response. We print the body response for debugging purposes because if, for example, we passed any parameter incorrectly, we will obtain a 400 response code that we must analyze and solve.
In case the code returned is lesser than zero, we will simply print it to the serial port. The list of possible connection related errors can be analyzed here.
if (httpCode > 0) {
String payload = http.getString();
Serial.print("\nHTTP code: ");
Serial.println(httpCode);
Serial.print("\nResponse: ");
Serial.println(payload);
}
else {
Serial.println("Error on HTTP request:");
Serial.println(httpCode);
}
After we end the request, we must call the end method on our HTTPClient object, to free the resources.
http.end();
Finally, to confirm if the process was successful or not, we need to have in consideration that the API returns the HTTP code 201 when the request was correctly received and processed, thus indicating the SMS will be sent. We will compare this value against what we have received as HTTP response from the API call, so we can return a Boolean as output of the sendSMS function.
return httpCode == 201;
The full sendSMS function is shown below.
bool sendSMS(const char * body){
std::stringstream url;
url << "https://api.twilio.com/2010-04-01/Accounts/" << accountNr <<"/Messages";
std::stringstream urlEncodedBody;
urlEncodedBody << "MessagingServiceSid=" << messagingServiceSid << "&To=" << to << "&Body=" << body;
Serial.print("\nURL: ");
Serial.println(url.str().c_str());
Serial.print("Encoded body: ");
Serial.println(urlEncodedBody.str().c_str());
HTTPClient http;
http.begin(url.str().c_str());
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
http.setAuthorization(accountNr, twilioPassword);
int httpCode = http.POST(urlEncodedBody.str().c_str());
if (httpCode > 0) {
String payload = http.getString();
Serial.print("\nHTTP code: ");
Serial.println(httpCode);
Serial.print("\nResponse: ");
Serial.println(payload);
}
else {
Serial.println("Error on HTTP request:");
Serial.println(httpCode);
}
http.end();
return httpCode == 201;
}
The complete code is shown below.
#include <WiFi.h>
#include <HTTPClient.h>
#include <sstream>
const char* ssid = "yourNetworkName";
const char* password = "yourNetworkName";
const char * messagingServiceSid = "yourMessagingServiceId";
const char * to = "destinationPhoneNumber";
const char * accountNr = "yourAccountNr;
const char * twilioPassword = "yourApiKey";
bool sendSMS(const char * body){
std::stringstream url;
url << "https://api.twilio.com/2010-04-01/Accounts/" << accountNr <<"/Messages";
std::stringstream urlEncodedBody;
urlEncodedBody << "MessagingServiceSid=" << messagingServiceSid << "&To=" << to << "&Body=" << body;
Serial.print("\nURL: ");
Serial.println(url.str().c_str());
Serial.print("Encoded body: ");
Serial.println(urlEncodedBody.str().c_str());
HTTPClient http;
http.begin(url.str().c_str());
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
http.setAuthorization(accountNr, twilioPassword);
int httpCode = http.POST(urlEncodedBody.str().c_str());
if (httpCode > 0) {
String payload = http.getString();
Serial.print("\nHTTP code: ");
Serial.println(httpCode);
Serial.print("\nResponse: ");
Serial.println(payload);
}
else {
Serial.println("Error on HTTP request:");
Serial.println(httpCode);
}
http.end();
return httpCode == 201;
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
bool result = sendSMS("Test SMS from ESP32");
if(result){
Serial.println("\nSMS sent");
}else{
Serial.println("\nSMS failed");
}
}
void loop() { }
Testing the code
To test the code, compile it and upload it to your device using a tool of your choice (Arduino IDE, Platformio). Make sure to replace all the placeholders from the code above by the actual values for your account.
When the procedure finishes, open a serial monitoring tool. You should see a result similar to figure 4 below, in case of success. I’ve hidden my personal credentials and also the response (since it returns back some of the content we sent in the body, but what is left should illustrate what is expected).
We can see both the URL and the body where built as expected and that the HTTP response code is 201. Also, we have obtained the payload as a XML. In case of error (ex: 400 due to incorrect parameters), this payload is very useful since it pinpoints the issue. Also, we can see by the “SMS sent” message that we were able to correctly output the Boolean result from the sendSMS function.
In your cellphone, you should receive a message like the one shown in figure 5. As can be seen, it includes the “Sent from your Twilio trial account” text, which is sent when using the trial account. Additionally, we can see the body text we defined in our code: “Test SMS from ESP32”.
References
[1] https://en.wikipedia.org/wiki/Twilio
[2] https://www.twilio.com/docs/sms/api
[3] https://www.twilio.com/docs/sms/api/message-resource#create-a-message-resource
I have here a nodeMCU esp8266, it’s ESP12. Do you think it works with him ? regards