How to Send SMS with Twilio and Micronaut

November 04, 2022
Written by
Reviewed by
Diane Phan
Twilion

How to Send SMS with Twilio and Micronaut title card

Micronaut is a framework for building JVM web and serverless apps, first released in 2018. It aims for fast startup and low memory overhead for your web applications, and supports Java, Kotlin, and Groovy, with an eye on Scala support in the future.

You might be familiar with Spring Boot as a popular framework in this space, and Baeldung has an overview article comparing it with Micronaut.

In this post I'll show how to set up a web app with Micronaut to handle Twilio webhooks for incoming SMS so your app can reply to them automatically. If you're interested in starting your Micronaut journey or looking to learn enough to start building your next Twilio app with it, then read on.

You'll need:

  • A recent version of Java. I used Java 18 but anything from 11 onward will work
  • A Twilio account. Sign up here if you don't have one, a trial account is fine
  • ngrok or a similar tool to create public URLs for localhost servers

Project Setup

You can set up and download a starter for your project from https://micronaut.io/launch/ or by using the mn command line tool. If you use the website:

  • Use "Maven" as your build tool
  • Select "JUnit" as the Test Framework
  • Set the project name to "twilio-micronaut"

Click "Generate Project" then "Download ZIP" and unzip the file once it's downloaded.

If you prefer to use a CLI tool, a convenient way to install mn is with SDKMAN, which I recommend for managing installations of Java and other useful tools.

Install the Micronaut tool and generate a new project with the following commands:

sdk install micronaut

mn create-app --build=maven --jdk=11 --lang=java --test=junit com.example.twilio-micronaut

Either way, you will now have the same project files in a folder called twilio-micronaut, so import the project into your favourite Java IDE. I'm a fan of the JetBrains IDEs so I use IntelliJ IDEA for Java.

You will need to enable annotation processing to enable Micronaut to do a lot of work when your app is compiled, rather than every time it starts up. This is one of the ways that Micronaut keeps its promise of fast startup and low memory usage.

In IntelliJ it is done in the Preferences like this:

Enabling annotation processing in Intellij IDEA"s preferences window

Now that you've generated the project, loaded it into your IDE, and configured the annotation processing, it's time to get coding.

Build your first Micronaut web app

First, let's look at what's already in the project:

  • A README.md file with helpful links to documentation and guides for Micronaut
  • A pom.xml file with some dependencies and plugins already configured
  • An Application class with a main() method

You won't need to edit any of these for this project, but it's good to see what's there. The main() method is how you will start the application. You can run it from your IDE or on the CLI with ./mvnw mn:run.

You can run it now, but you haven't added any code yet so you will have an app running at http://localhost:8080 which returns a 404 for any URL. Let's fix that.

Add a new Java class called HelloController in the same package as the Application class, with this code:

package com.example;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;

import java.util.Optional;

@Controller("/hello")
public class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN)
    public String index(@QueryValue("name") Optional<String> name) {
        return "Hello "
                + name.map(n -> n + ", ").orElse("")
                + "from Micronaut";
    }
}

By adding the @Controller("/hello") annotation you're telling Micronaut that this class will handle HTTP requests for that path, and the @Get annotation says to use the index method for GET requests. Micronaut defaults to application/json for the response's content type but because this function is returning a String use produces = MediaType.TEXT_PLAIN with the @Get annotation.

Specifying an Optional<String> for the @QueryValue("name") means that the name parameter in the URL is, well, optional. If you used String it wouldn't be.

Run the app as before, and you can see the code in action by browsing to http://localhost:8080/hello, or with the name parameter at http://localhost:8080/hello?name=Matthew

Handle SMS from Twilio with Micronaut

Now that you know how to create a web app with Micronaut, it's only a few more steps to be able to receive and reply to SMS messages using a Twilio phone number:

  1. Modify your app to handle Twilio's SMS webhooks
  2. Buy and configure a phone number from Twilio to use your app

Let's look at those in order.

Modify your app to handle Twilio's SMS webhooks

Webhooks are a flexible way to handle incoming SMS with Twilio. You have to create a web app to handle HTTP requests. The incoming SMS is converted into an HTTP request which Twilio makes to a URL you provide, and your app's response is used as the reply.

Twilio diagram demonstrating how SMS and webhooks work.

If your app returns plain text then this is sent straight back as a reply to the original SMS. For more complex behaviours you can also return TwiML, but for a direct SMS response returning text is enough.

The HTTP request from Twilio will include all the relevant data about the incoming message, including the message body and who it's from, etc. You can configure Twilio to use either HTTP GET or POST. As we've already looked at handling GET with Micronaut, let's use POST this time.

Create a new class alongside your HelloController and call it SmsController. Here's the content:

package com.example;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;

import java.util.Map;

@Controller("/sms")
public class SmsController {

    @Post(consumes = MediaType.APPLICATION_FORM_URLENCODED,
          produces = MediaType.TEXT_PLAIN)
    public String index(@Body Map<String, String> requestParams) {

        String from = requestParams.get("From");
        String body = requestParams.get("Body");

        return "Hello 👋 You sent your message from " + from + ". "
             + "You said: " + body;
    }
}

There are a few new things in this method:

  • Using @Post means you are expecting the details of the incoming SMS to be in the request body. You need to tell Micronaut what format to expect this data to be in using consumes = MediaType.APPLICATION_FORM_URLENCODED.
  • Using the @Body annotation you can get access to all the request data in a single Map<String, String>. The full list of these parameters is rather extensive, but for this app you're only using the From phone number and the Body of the message.

Because this new endpoint takes POST requests and expects data in the request body it's not trivial to test directly from the browser. However, if you're comfortable with a tool like curl you can test it on the command line.

Restart your app, then paste the following command:

$ curl http://localhost:8080/sms -d "From=MY_PHONE_NUMBER&Body=Hello"

You should see the following response printed to the CLI.

Hello 👋 You sent your message from MY_PHONE_NUMBER. You said: Hello

Configure a Twilio phone number to use your Micronaut app

Start by getting a new phone number from Twilio at twilio.com/console and selecting Phone Numbers > Manage > Buy a number. After buying it you can head to the configuration page for that number. You will want to set a URL that points to your Micronaut app for "When a message comes in". But what URL should you use?

Questioning which URL to set for the "When a message comes in" webhook.

Twilio needs a publicly accessible URL, so localhost isn't going to be good enough. Luckily, there are several tools to create temporary public URLs that forward to localhost - my favourite is ngrok.

Running ngrok http 8080 in the terminal gives a response like this:

Response of running ngrok in the terminal

The Forwarding URL is what you need, not forgetting that you need to add /sms to the end:

Setting the URL to use for the "When a message comes in" webhook.

Save this setting with the Save button at the bottom of the phone number configuration page, and you're good to go!

See it in action

Text your Twilio number from your cell phone and see the response from your app - that's all! If you can't use your cell phone (or don't want to) have a look at Twilio Dev Phone which works like a softphone that runs in your browser and is backed by your Twilio account.

Example interaction with the application using a Twilio-backed soft phone.

Deploy your Micronaut app

If you want to leave your app up and running so that your SMS responder can handle messages without having to keep it running on your laptop, you can package it for distribution with ./mvnw clean package in your project directory. This creates a self-contained jar (Java ARchive) in the target/ directory. You can run the app on a cloud instance with java -jar twilio-micronaut-0.1.jar, configure your network and add the public URL to the phone number configuration.

Wrap up

Congratulations 🎉 You've successfully built your first Micronaut Twilio integration! Where you go next is up to you. Check out the API Reference and Guides for more inspiration about Micronaut, and Twilio's documentation and other posts on this blog for ideas about what to build. Almost every Twilio API can be enhanced with webhook calls to your own apps, so let your imagination flow and tell me about what you're building: