Send SMS in Your Spring Boot App

January 13, 2020
Written by

spring-boot-java-websockets.png

In this article, you’ll learn how to use WebSocket API with Spring Boot and build a simple status delivery application at the end.

WebSocket is a communication protocol that makes it possible to establish a two-way communication channel between a server and its client. Websockets are supported by most of the browsers that are commonly used today. 

Prerequisites

First, you need to set up your Twilio account and a suitable phone number.

Here are the steps to generate a project using Spring Initializr: 

  1. Go to http://start.spring.io/.
  2. Enter Artifact’s value as websocket-callback.
  3. Add Websocket in the dependencies section.
  4. Click Generate Project to download the project.
  5. Extract the downloaded zip file.
  6. Note: You will need Java 8 or later installed and download the JDK from here.

WebSocket Configuration

The first step is to configure the WebSocket endpoint and a message broker. Create a new class called WebSocketConfig inside com.twilio.websocketcallbackpackage with the following contents:

package com.example.websocketcallback;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
 
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
 
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }
 
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }
 
}

@EnableWebSocketMessageBroker is used to enable our WebSocket server. We include the  WebSocketMessageBrokerConfigurer interface and provide an implementation for some of its methods to configure the WebSocket connection.

In the first method configureMessageBroker, we’re configuring a message broker that will be used to route messages from one client to another.

Line #15 defines the messages whose destination starts with the/topic should be routed to the message broker. The message broker broadcasts messages to all the connected clients who are subscribed to a particular topic. 

Line #16 defines the messages whose destination starts with /app. After processing the message, the controller will send it to the broker channel.

In the second method registerStompEndpoints, we register a WebSocket endpoint that clients use to connect to our WebSocket server.

Notice the use of withSockJS() with the endpoint configuration. SockJS is used to enable fallback options for browsers that don’t support WebSocket.

The word STOMP in the method name shows it's a derivation of Spring framework's STOMP implementation. STOMP stands for Simple Text Oriented Messaging Protocol. It is a messaging protocol that defines the format and rules for data exchange.

Here is the pictorial representation of Web Sockets that facilitates the full-duplex communication channel:

 

Diagram of how data will flow in the WebSocket App

Build with Gradle

Create a build.gradle file with the associated Twilio and WebSockets dependency libraries. 


dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation group: "com.twilio.sdk", name: "twilio", version : "7.47.2"
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}
 

Create a resource representation class

To model the Message carrying SMS class, you can create a plain old Java object (POJO) with the to and message properties and the corresponding methods.

package com.twilio.websocketcallback.domain;
 
public class SMS {
    private String to;
    private String message;
 
    public String getTo() {
        return to;
    }
 
    public void setTo(String to) {
        this.to = to;
    }
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
 
    @Override
    public String toString() {
        return "SMS{" +
                "to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

Create a message-handling controller

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the SMSController is mapped to handle messages to destination /sms.

package com.example.websocketcallback;
 
import com.twilio.exception.ApiException;
import com.twilio.websocketcallback.domain.SMS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
 
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
 
@RestController
public class SMSController {
 
    @Autowired
    SMSService service;
 
    @Autowired
    private SimpMessagingTemplate webSocket;
 
    private final String  TOPIC_DESTINATION = "/topic/sms";
 
    @RequestMapping(value = "/sms", method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public void smsSubmit(@RequestBody SMS sms) {
        try{
            service.send(sms);
        }
        catch(ApiException e){
 
            webSocket.convertAndSend(TOPIC_DESTINATION, getTimeStamp() + ": Error sending the SMS: "+e.getMessage());
            throw e;
        }
        webSocket.convertAndSend(TOPIC_DESTINATION, getTimeStamp() + ": SMS has been sent!: "+sms.getTo());
 
    }
 
    @RequestMapping(value = "/smscallback", method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public void smsCallback(@RequestBody MultiValueMap<String, String> map) {
       service.receive(map);
       webSocket.convertAndSend(TOPIC_DESTINATION, getTimeStamp() + ": Twilio has made a callback request! Here are the contents: "+map.toString());
    }
 
    private String getTimeStamp() {
       return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
    }
}

This controller is concise and precise, but there are plenty of processes running internally. Let's review them step-by-step.

The @RequestMapping annotation ensures that if a message is sent to the destination /sms through POST methodThe content_type accepted by the consumer is set in the line consumes = MediaType.APPLICATION_JSON_VALUE.

produces = MediaType.APPLICATION_JSON_VALUE is the value that can be produced by the mapped handler. produces consists of one or more media types, one of which must be chosen via content negotiation against the "acceptable" media types of the request. Typically those are extracted from the "Accept" header but may be derived from query parameters, or somewhere else. 

The payload of the message is bound to a @RequestBody SMS object, which is passed into SMSSubmit().

The broadcast to all subscribers to /topic/sms as specified in the webSocket.convertAndSend() method. This method is used to send messages to connected clients from any part of the application. Any application component can send messages to the"brokerChannel" and is done by SimpMessagingTemplate injection to send messages.

Once the message and to fields are received from the UI, these values are parsed and sent to the SMS service called send method. This will send outgoing SMS messages from your Twilio phone number to the text-capable phone number you input in the UI. Twilio sends SMS on-behalf of the actual wired phone number. With the programmable SMS, we can build logic to send SMS with a message body and the destination phone number and allow Twilio to do its magic.

Similarly, the second method smsCallback is a callback handler method that receives the callback from the Twilio endpoint.  It consumes a request content-type header of type MediaType.APPLICATION_FORM_URLENCODED_VALUE and produces a negotiable mediatype of type MediaType.APPLICATION_JSON_VALUE. This mediatype is then processed by our frontend js engine. Twilio sends a callback in URL_ENCODED form values, hence they need to be formatted into a type that is easily consumable by your application. 

Create a Messaging Service

component is a unit of software that is independently replaceable and upgradeable. 

Microservice architectures will use libraries, but their primary way of componentizing their software is by breaking down into services. We define libraries as components that are linked into a program and called using in-memory function calls, and services as out-of-process components which communicate with a mechanism such as a web service request, or remote procedure call. One main reason for using services as components (rather than libraries) is that services are independently deployable. In the demo application, we have a service to communicate directly with the Twilio endpoints to send or receive callback information.

Twilio suggests you never save your credentials in your development environment or project. Instead, you can save these values and retrieve them through the ./bash_profile(environment) file on your development machine, or another suitable place depending on your operating system. Spring can extract data through externalized configuration

We have a couple of blog posts on how to link the Environment Variables and Storing Credentials.

Below is the code of this implementation:


package com.example. websocketcallback;
import com.twilio. websocketcallback.domain.SMS;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import com.twilio.Twilio;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
 
import java.net.URI;
 
@Component
public class SMSService {
 
    @Value("#{systemEnvironment['TWILIO_ACCOUNT_SID']}")
    private String ACCOUNT_SID;
 
    @Value("#{systemEnvironment['TWILIO_AUTH_TOKEN']}")
    private String AUTH_TOKEN;
 
    @Value("#{systemEnvironment['TWILIO_PHONE_NUMBER']}")
    private String FROM_NUMBER;
 
    public void send(SMS sms) {
        Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
   
        Message message = Message.creator(new PhoneNumber(sms.getTo()), new PhoneNumber(FROM_NUMBER), sms.getMessage())
                .create();
        System.out.println("here is my id:"+message.getSid());// Unique resource ID created to manage this transaction
 
    }
 
    public void receive(MultiValueMap<String, String> smscallback) {
    }
}

Create a browser client

Once all the server components are established and connected, the UI can be implemented with JS client and HTML pages. Recommend placing these files under src/main/resources/static.

Create an index.html file that looks like this:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello WebSocket</title>
 
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
        <div class="col-md-6">
 
            <form class="form-inline" id ="smsForm"  method="post">
                <div class="form-group">
                <p>To Number: <input type="text"  id="to" /></p>
                    <br/><p>Message: <textarea  id="message" > </textarea></p>
                </div>
                <p> <button id="send" class="btn btn-primary" type="submit">Send</button></p>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Message Status dashboard</th>
                </tr>
                </thead>
                <tbody id="messages">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

This HTML file imports the SockJS and STOMP javascript libraries that will be used to communicate with our server using STOMP over WebSocket. We’re also importing here an app.js file which contains the logic of our client application.

Let’s create that file:

var stompClient = null;
 
function setConnected(connected) {
 
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#messages").html("");
}
 
function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/sms', function (sms) {
            showData(sms.body);
        });
    });
}
 
function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}
 
function showData(message) {
    $("#messages").append("<tr><td>" + message + "</td></tr>");
}
 
function processForm(e) {
    if (e.preventDefault) e.preventDefault();
    var form = document.getElementById('smsForm');
    var data = {};
    for (var i = 0, ii = form.length; i < ii -1; ++i) {
        var input = form[i];
        if (input.id) {
            data[input.id] = input.value;
        }
    }
    // construct an HTTP request
    var xhr = new XMLHttpRequest();
    xhr.open("post", "/sms", true);
    xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
 
    // send the collected data as JSON
    xhr.send(JSON.stringify(data));
 
    return false;
}
 
$(function () {
 
    let form = document.getElementById('smsForm');
    if (form.attachEvent) {
        form.attachEvent("submit", processForm);
 
    } else {
        form.addEventListener("submit", processForm);
    }
 
        // collect the form data while iterating over the inputs
 
    });
 
    connect();

Build an executable JAR

If you use Gradle, you can run the application by using ./gradlew bootRun

Test the service

Now that the service is running, point your browser at http://localhost:8080 and click the "Send" button.

Upon opening a connection, you are asked for your to-number and message. Enter your information and click "Send". The data is sent to the server as a JSON message over STOMP. After a 1-second simulated delay, the server sends a message back with the callback status information received from Twilio. 

sequence.png

Web sequence diagram of the complete implementation

Screen Shot 2020-01-08 at 11.42.05 AM.png

End result of the implementation

Twilio with Spring Boot and WebSockets

If all the tests worked, you now have an SMS app built with Spring Boot and WebSockets. If you want to work alongside my code, you can find my repository here.

Let me know if you get it working - we can't wait to see what you build!

Further References:

Pooja Srinath is a Senior Solutions Engineer at Twilio. She's focused on learning new things and building cool apps to help budding developers. She can be found at psrinath [at] twilio.com.