Send SMS messages to many phone numbers using the Twilio API and Spring

Introduction

Sending SMS is an excellent way to get your customers to know of any promotions or sales in our businesses.

Suppose that you're a top-seller t-shirt retailer. A good way of letting a customer know of any discount in your store is by sending an SMS message to that customer. Twilio SMS API lets you send a single SMS very easily.

Now, let's imagine that your customers are very big fans of basketball. Or that a famous NBA player just scored 46 points in a single game, then all of sudden all your customers want to buy his t-shirts. You'll want all your customers to know about a discount on that t-shirt as soon as possible.

In this post, I'll show you how to set up a service to send SMS messages to many customers using Twilio SMS API, Twilio Messaging Service, and Spring.

Pre Requisites

You'll need a couple of things to follow along with this tutorial:

The Implementation

Set up The Twilio Messaging Service

To set up a Twilio Messaging Service go to the console, and click on the Create Messaging Service button, under Messaging->Services. Pick any name to it and choose the option Market my services to its purpose and go to the next step. On the Step 2, add your Twilio phone number as a sender and go to the next step. The Integration part at step 3 contains some useful configurations:

  • Incoming messages define how you want to handle incoming SMS messages. You can mark Drop the message and ignore incoming SMS messages. Or, you can mark Defer to sender's webhook if you want to invoke the SMS sender webhook. Finally, you can respond to all incoming messages using Send a webhook. I marked mine as Drop the message since I'm not very interested in the customer's response to the discount I'm making. I'm just letting them know.

  • You can also set the Callback URL to be the URL that Twilio will send the delivery status of SMS messages sent. I marked mine as empty since I don't have any dedicated URL to handle that.

  • Set the Validity Period if you want to not send messages that took a long time to send. The maximum wait time for a message is 4 hours. I left mine as the default of 4 hours.

You can set some information about your business at step 4 to unlock an A2P 10DLC phone number to send a higher volume of SMS messages. You can unlock up to 1 million messages per day, and up to 2 thousand messages per second. That's a huge volume for SMS messages.

In this tutorial, I'll not reach that volume of messages. But keep in mind that as soon as your application grows to that volume, you'll need a dedicated 10DLC phone number to handle scalability.

After completing all steps, your Twilio Messaging Service is set up and ready to be used.

Set up your Spring Application

I'll use a Spring Maven Project with Spring Web dependency to create our API. You can generate your project here and use the same configurations shown in the image below if you're using JDK17. If not, set the correct Java version accordingly to the one installed in your machine. You can change the artifact, name, and description fields as per your desire. I choose shirt-retailer for this example.

Sample Spring Project.png

Then, press the generate button and save it somewhere.

Now, let's add the Twilio Java Helper Library into our project to make the API easier to work with. Open the project in IntelliJ IDEA and add the following lines to your pom.xml file:

<dependency>
  <groupId>com.twilio.sdk</groupId>
  <artifactId>twilio</artifactId>
  <version>8.27.0</version>
</dependency>

Also, add the Lombok dependency to pull some useful annotations:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.22</version>
  <scope>provided</scope>
</dependency>

You'll also need to set some secret values for Account SID, Auth Token, and the Twilio Messaging Service. It is a good practice to set those values as environment variables. I'll use the application.properties file to store the variables, and the @Value Spring annotation to get them. Pick your account values at the Twilio console and replace them in the application. properties file:

TWILIO_ACCOUNT_SID=<your_account_sid>
TWILIO_AUTH_TOKEN=<your_auth_token>
TWILIO_MESSAGING_SERVICE_ID=<your_twilio_messaging_service_id>

With that set, we should be good to write some code.

Create the REST API to send SMS messages

Let's start by defining our REST API class. Create a package named controller and a file named SmsController inside of it with the following content:

package com.example.shirtretailer.controller;

import com.example.shirtretailer.model.SmsRequest;
import com.example.shirtretailer.service.SmsSenderService;
import com.twilio.rest.api.v2010.account.Message;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/sms")
@RequiredArgsConstructor
public class SmsController {

    private final SmsSenderService smsSenderService;

    @PostMapping("/send")
    public ResponseEntity<List<Message>> send(@RequestBody SmsRequest request) {

        return new ResponseEntity<>(smsSenderService.send(request), HttpStatus.OK);
    }
}

This code snippet is defining a POST API endpoint called /send that works under the resource /sms. The API response is a list of Twilio results of sending an SMS. Forget about the SmsSenderService for now, we'll create that in a moment.

Create a class named SmsRequest to wrap our API request, and add to it the code below:

package com.example.shirtretailer.model;

import lombok.Data;

import java.util.List;

@Getter
public class SmsRequest {

    private List<String> numbers;

    private String message;
}

The request object contains only two fields: one to store the list of target numbers, and another to store the SMS content. The @Getter annotation from Lombok dependency will generate a getter method for each field.

So far, we have defined an API that does nothing special. Now, let's give some functionality to our application. Create another package named service. Add to it a class named SmsSenderService with the following content:

package com.example.shirtretailer.service;

import com.example.shirtretailer.model.SmsRequest;
import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;

import static java.util.stream.Collectors.toList;

@Service
public class SmsSenderService {

    private final String TWILIO_ACCOUNT_SID;

    private final String TWILIO_AUTH_TOKEN;

    private final String TWILIO_MESSAGING_SERVICE_ID;

    public SmsSenderService(@Value("${TWILIO_ACCOUNT_SID}") String TWILIO_ACCOUNT_SID,
                            @Value("${TWILIO_AUTH_TOKEN}") String TWILIO_AUTH_TOKEN,
                            @Value("${TWILIO_MESSAGING_SERVICE_ID}") String TWILIO_MESSAGING_SERVICE_ID) {
        this.TWILIO_ACCOUNT_SID = TWILIO_ACCOUNT_SID;
        this.TWILIO_AUTH_TOKEN = TWILIO_AUTH_TOKEN;
        this.TWILIO_MESSAGING_SERVICE_ID = TWILIO_MESSAGING_SERVICE_ID;
    }

    public List<Message> send(SmsRequest request) {
        Twilio.init(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);

        return request.getNumbers()
                .stream()
                .map(number -> Message.creator(new PhoneNumber(number),
                                TWILIO_MESSAGING_SERVICE_ID,
                                request.getMessage())
                        .create())
                .collect(toList());
    }
}

This service class will do the actual job. First, I've initialized the Twilio client the environment variables defined in the application.properties file with the help of the @Value Spring annotation. Then, I've applied map operation over the phone numbers contained in the request to create a new Twilio SMS Message object for each phone number in the list.

To create a message, I've used the creator method that contains three arguments. The first argument is the target number. The second one is the unique number of the Messaging Service we've created before. And the third is the SMS content.

Finally, we return the collected results of all SMS messages. The results contain useful information about the SMS delivery like timestamp, errors (if any), version of Twilio API used, and others.

Testing our code

Let's test our API at localhost using the Run IntelliJ configuration that we've created before, and cURL. Click on the play button in the top right corner in IntelliJ to start the application. If the application started properly (and most likely will), open up a terminal and paste the cURL below. Pick some phone numbers that you can see the incoming SMS notification.

curl --location --request POST 'http://localhost:8080/sms/send' \
--header 'Content-Type: application/json' \
--data-raw '{
    "numbers": [
        "<phone_number_1>",
        "<phone_number_2>"
    ],
    "message": "Stephen Currys T-Shirt 50% off only today"
}'

After that, you should be able to see the SMS notification on your smartphone and get your Steph Curry's T-Shirt piece before it runs out of stock! Also, the response of this API lets us know of any failures that happened in SMS delivery and some other useful information. The response that I've got from the cURL using my phone number was:

[
    {
        "body": "Stephen Currys T-Shirt 50% off only today",
        "direction": "OUTBOUND_API",
        "from": null,
        "to": "<masked>",
        "price": null,
        "uri": "<masked>",
        "status": "ACCEPTED",
        "sid": "<masked>",
        "numSegments": "0",
        "dateUpdated": "2022-03-05T00:39:10Z",
        "errorMessage": null,
        "accountSid": "<masked>",
        "numMedia": "0",
        "messagingServiceSid": "<masked>",
        "dateSent": null,
        "dateCreated": "2022-03-05T00:39:10Z",
        "errorCode": null,
        "priceUnit": null,
        "apiVersion": "2010-04-01",
        "subresourceUris": {
            "media": "<masked>
        }
    }
]

For security purposes, I've masked some personal information from the response body.

Troubleshooting

Since we're testing with a Twilio Trial phone number, you need to verify the numbers that you're trying to send an SMS to. If you do not verify, you'll get an error when sending that request. To verify a number, go to your console, add the desired number and approve it using the two-factor authentication sent to that phone number.

Conclusion

As retailers or sellers of any kind of product, we need a way to send notifications to our customers of discounts. In this tutorial, you learned how to achieve that with an API to send SMS messages to various phone numbers at once using Spring Boot, Twilio SMS API and Twilio Messaging Service. If you have any questions please reach out to me in the comments. Hope you enjoyed it and see you at the next one!

Pedro Lopes is a backend engineer at a Brazilian digital bank. He's a specialist in cloud computing, distributed systems, and Java coding. He likes to write and read in his spare time. He's also a big fan of sports, especially basketball.