This content originally appeared on Twilio Blog and was authored by Matthew Gilliard
Life's complicated enough without having to forward messages between friends and family members to make sure everyone is up to date with what's going on. In this post I'll share how I set up a Twilio number that I gave as "my" phone number to my kids' schools so that all the messages sent to and from that number are automatically forwarded to both me and my wife, and either of us can reply. I expect you can think of situations in your own life where this could be handy: package deliveries, party planning, appointment reminders, the list is long.
In this post I'll use Java, but the same approach would work in any language which you can build a web app in. If you're comfortable with JavaScript then SMS Forwarding to Multiple Numbers on Code Exchange would be a great starting point.
What are we building?
Everything here is based around a single Twilio phone number, and a group of you and your family members or friends who have cell phones. You can give the Twilio number to others as if it is your own number.
We'll set up the Twilio number so that when someone texts it the message will be forwarded on to everyone in your group. I'll use two group members in this example, but the code is designed so that you can use as many as you want.
When someone from outside your group (the left-hand phone) sends an SMS to the Twilio number, the message will be forwarded to everyone in the group (the phones on the right). The messages will appear to have been sent from the Twilio number, so the real sender's phone number will be added at the start of the message.
When anyone in your group replies to the Twilio number, they should put the real destination number at the start of the message, which will be removed before the message is forwarded on. Everyone in your group will get a copy of the message, too:
Note that if you want to send a message from one group member to all others, you could do this by prefixing the message with your own number.
Prerequisites
To build this you will need:
- a Twilio account, upgraded from a trial account. If you don't already have one, sign up with this link to get an extra $10 when you upgrade.
- a Twilio phone number
- a Java 11 or a newer version. I recommend SDKMAN to install and manage Java on my development machine.
- a Java development environment.
Using Twilio Programmable Messaging
To tell Twilio how to behave in response to incoming SMS we will use webhooks. When a message comes into our phone number, Twilio will make an HTTP request to a URL we provide. We will build and app to send instructions back in the HTTP response to tell Twilio what to do next.
The instructions in the HTTP response are written in TwiML, including multiple Message tags which tell Twilio to send new text messages. The content and destination for those messages depend on who sent the incoming message and what they said; attributes which are part of the HTTP request. Read on to see how to build this app using Java and Spring Boot
Building the app
Spring Boot is the most popular framework for building web apps in Java. I like to start new projects with the Spring Initializr. If you want to follow along with coding, use this link which sets up the same options as I used, or find the finished project on GitHub.
Download, unzip and open the generated project in your IDE. There will be a single class in src/main/java, in the com.example.smsgroupbroadcast
package, called SmsGroupBroadcastApplication
. You won't need to edit that class but it has a main
method which can be used to run the application.
In the same package, create a new class called SmsHandler
, in a new file called SmsHandler.java
. To keep things from getting too complicated we'll put all our code in that class. By the time we're done it will be about 100 lines long.
Start with the code which we need to run at startup:
@RestController
public class SmsHandler {
private final Set<String> groupPhoneNumbers;
public SmsHandler() {
groupPhoneNumbers = Set.of(System.getenv("GROUP_PHONE_NUMBERS").split(","));
}
// more code will go in here
}
[this code including imports on GitHub]
The @RestController
annotation tells Spring that this class should be scanned for methods that can handle HTTP requests. We'll be writing one soon.
The Set<String> groupPhoneNumbers
on line 4 is initialized in the constructor by reading an environment variable whose value is a comma-separated string of phone numbers in E.164 format. These numbers should be your cell phone and the phones of everyone else in your group (everyone on the right side of the diagrams above). You can set environment variables directly in your IDE:
IntelliJ IDEA Environment Variable configuration
A method for handling HTTP requests
To make it easier to write the correct TwiML we will use the Twilio Java helper library. Add the following snippet into the <dependencies>
section of pom.xml
, the Maven config file which is at the top level of your project:
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio</artifactId>
<version>8.18.0</version>
</dependency>
We always recommend using the latest version of the Twilio Helper Libraries. At the time of writing the latest version is 8.18.0
and you can check for newer versions at mvnrepository.com.
You may have to tell your IDE to reload Maven changes at this point. Then, add this code to your SmsHandler
class:
@RequestMapping(
value = "/sms",
method = {RequestMethod.GET, RequestMethod.POST},
produces = "application/xml")
@ResponseBody
public String handleSmsWebhook(
@RequestParam("From") String fromNumber,
@RequestParam("To") String twilioNumber,
@RequestParam("Body") String messageBody) {
List<Message> outgoingMessages;
if (groupPhoneNumbers.contains(fromNumber)) {
outgoingMessages = messagesSentFromGroup(fromNumber, twilioNumber, messageBody);
} else {
outgoingMessages = messagesSentToGroup(fromNumber, twilioNumber, messageBody);
}
MessagingResponse.Builder responseBuilder = new MessagingResponse.Builder();
outgoingMessages.forEach(responseBuilder::message);
return responseBuilder.build().toXml();
}
[this code with imports on GitHub]
This method starts with a lot of annotations, which are recognised by Spring:
@RequestMapping
tells Spring that this method should be call forGET
andPOST
requests to/sms
, and that theContent-type
on the response isapplication/xml
.@ResponseBody
tells Spring that the return value from this method should be used as the body of the HTTP response.- The
@RequestParam
annotations tell Spring to extract the named parameters from the HTTP request and pass them as arguments to the method. This works for bothGET
andPOST
even though the parameters are in different parts of the HTTP request.
The body of the method creates a list of Message
objects which is populated differently depending on whether the incoming SMS that triggered this webhook is from a group member or not (line 12). We'll define the messagesSentFromGroup
and messagesSentToGroup
methods in a moment, but first notice how the list of Messages is added to a MessagingResponse
using forEach
on lines 19-21.
Dealing with messages from non group members
If the groupPhoneNumbers.contains(fromNumber)
check in the method above returns false
then we know that the message has come from someone outside of our group. In this case the messagesSentToGroup
method is called to get a list of Message
objects representing a copy of the incoming message to be forwarded to each group member:
private List<Message> messagesSentToGroup(String fromNumber, String twilioNumber, String messageBody) {
List<Message> messages = new ArrayList<>();
String finalMessage = "From " + fromNumber + " " + messageBody;
groupPhoneNumbers.forEach(groupMemberNumber ->
messages.add(createMessageTwiml(groupMemberNumber, twilioNumber, finalMessage))
);
return messages;
}
[this code with imports on GitHub]
We build up the finalMessage
and add a Message to the list for each group member. I created a small helper method called createMessageTwiml
to turn the Twilio helper library's builder-pattern code into a one-liner. I felt that was worth doing as we will build Message objects a few times in this class. That method looks like this:
private Message createMessageTwiml(String to, String from, String body) {
return new Message.Builder()
.to(to)
.from(from)
.body(new Body.Builder(body).build())
.build();
}
[this code with imports on GitHub]
Messages from group members
When a group member sends a message to the Twilio number, they should preface it with the real destination number:
The "Thank you!" part of the message will be sent on from the Twilio number, to the number at the start of the message. Everyone in the group will get a copy of it, too. To do this, the messagesSentFromGroup
method has to split up the message body, check if it starts with a phone number (sending a helpful reminder back if it doesn't), and build a list of outbound messages, like this:
private List<Message> messagesSentFromGroup(String fromNumber, String twilioNumber, String messageBody) {
List<Message> messages = new ArrayList<>();
String[] messageParts = messageBody.split("\\s+", 2);
String e164Regex = "\\+[0-9]+";
if (messageParts.length != 2 || !messageParts[0].matches(e164Regex)) {
return List.of(createHowToMessage(fromNumber, twilioNumber));
}
String realToNumber = messageParts[0];
String realMessageBody = messageParts[1];
// add the message to the non-group recipient
messages.add(
createMessageTwiml(realToNumber, twilioNumber, realMessageBody)
);
// send a copy of the message to everyone in the group except the sender
String groupCopyMessage = "To " + realToNumber + " " + realMessageBody;
groupPhoneNumbers.forEach(groupMemberNumber -> {
if (!groupMemberNumber.equals(fromNumber)) {
messages.add(
createMessageTwiml(groupMemberNumber, twilioNumber, groupCopyMessage));
}
});
return messages;
}
private Message createHowToMessage(String fromNumber, String twilioNumber){
return createMessageTwiml(fromNumber, twilioNumber,
"To send a message to someone outside your group, " +
"don't forget to include the destination phone number at the start, " +
"eg '+44xxxx Ahoy!'");
}
[this code with imports on GitHub]
The messagesSentFromGroup
method might seem like a lot of code, but it splits roughly in half. Lines 4-9 deal with splitting the input and checking to see if it starts with a phone number. The e164Regex
tests for a +
followed by numbers, which corresponds to E.164 formatting for phone numbers.
The rest of messagesSentFromGroup
builds up a list of all the messages we need to send and returns it.
Finally there's a separate method for createHowToMessage
, which I thought was worth breaking out to keep the longer method more readable.
Code Complete
The SmsHandler
class is complete, so the code is finished. For reference, the full class is on GitHub.
Running your code locally
The easiest way to start the app is by using your IDE to run the main
method in the SmsGroupBroadcasterApplication
class that we saw earlier. If you prefer using a command line terminal then run ./mvnw spring-boot:run
from the top level of the project. Either way remember to set the GROUP_PHONE_NUMBERS
environment variable.
Once the app has started up, you can browse to http://localhost:8080/sms?From=__from__&To=__to__&Body=__body__ and you should see a response like:
<Response>
<Message from="__to__" to="GROUP_MEMBER_1">
<Body>From __from__ __body__</Body>
</Message>
<Message from="__to__" to="GROUP_MEMBER_2">
<Body>From __from__ __body__</Body>
</Message>
</Response>
GROUP_MEMBER_1
and GROUP_MEMBER_2
will be the numbers in your GROUP_PHONE_NUMBERS
environment variable.
Using your code with Twilio
For Twilio to be able to use your app for its webhooks, it will need a public URL. There are a lot of ways to deploy Java code online, but for simplicity while you're working on it I recommend using ngrok.
After installing ngrok you can run ngrok http 8080
and you will see an https
Forwarding URL which you will need to set for when "a message comes in" on your phone number config page. Don't forget to add the path of /sms
onto the URL:
You can also set the webhook URL for a phone number using the Twilio CLI:
twilio phone-numbers:update <PHONE_NUMBER> --sms-url=<URL>
if the URL is a localhost
address, Twilio CLI will create an ngrok tunnel for you.
If you want to test how this works before giving out the number for real, either recruit some friends with phones or use other Twilio numbers to try it out. Once you're happy with it, move the app to an always-on public cloud so that you don't have to keep your dev machine running 24/7. That's outside of the scope of this post but Spring's documentation on packaging and deployment has a lot of options. You only need to serve one HTTP request per SMS sent to your Twilio number so the requirements are very low.
Further credit
There are a lot of enhancements you could make to this app, for example:
- For large groups, it might be useful to include the "From" number in the copy messages.
- Include an "address book" in your app so you can put the name instead of number when sending to non-group recipients.
- There are no checks to make sure that HTTP requests actually came from Twilio. It's not a huge problem for this app because if anyone else calls your app they'll get some TwiML back, but it won't cost you anything. If you want to add this check, I wrote Securing your Twilio webhooks in Java which shows how.
Whatever you're building with Twilio, I'd love to hear about it. Get in touch with me @MaximumGilliard on Twitter or mgilliard@twilio.com. Happy coding!
This content originally appeared on Twilio Blog and was authored by Matthew Gilliard
Matthew Gilliard | Sciencx (2021-08-20T12:46:17+00:00) Group SMS with Twilio and Java. Retrieved from https://www.scien.cx/2021/08/20/group-sms-with-twilio-and-java/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.