SMS and MMS Marketing Notifications with Java and Servlets

Download the Code

Ready to implement SMS and MMS marketing notifications with Java and Servlets?

Here's how it'll work at a high level:

  1. A possible customer sends an SMS to a Twilio phone number you advertise somewhere.
  2. Your application confirms that the user wants to receive SMS and MMS notifications from your company.
  3. An administrator or marketing campaign manager uses a web form to craft a message to push to all subscribers via SMS/MMS message.

Learn how Walmart sent daily deals to customers as part of their "Value of the day" marketing campaign.

Building Blocks

To get this done, you'll be working with the following tools:

  • TwiML and the <Message> verb: We'll use TwiML to manage interactions initiated by the user via SMS.
  • Messages Resource: We will use the REST API to broadcast messages out to all subscribers.

Let's get started! Click the button below to move on to the next step of the tutorial.

The Subscriber Model

Before send out marketing notifications to a subscriber, we need to start with the perfect model:

  • phoneNumber stores where to send the notifications.
  • subscribed stores if a subscriber is active (only an active subscriber will receive notifications).
Loading Code Samples...
Language
package org.twilio.smsmarketing.models;

import javax.persistence.*;

@SuppressWarnings("unused")
@Entity
@Table(name = "subscribers")
public class Subscriber {

    public Subscriber() {
    }

    public Subscriber(String phoneNumber){
        this.phoneNumber = phoneNumber;
        this.subscribed = false;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "phone_number")
    private String phoneNumber;

    @Column(name = "suscribed")
    private boolean subscribed;


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public boolean isSubscribed() {
        return subscribed;
    }

    public void setSubscribed(boolean subscribed) {
        this.subscribed = subscribed;
    }
}
Subscriber Model
The Subscriber model

Subscriber Model

Let's see how to handle incoming messages.

Handle Incoming Messages

This is the endpoint that will be called every time our application receives a message.

We begin by getting the user's phone number from the incoming Twilio request. Next, we try to find a Subscriber model with that phone number.

If there's no subscriber with this phone number, we create one, save it, and respond with a friendly message asking our newest user to send an SMS with "add" as a command. This is done to confirm that they want to receive messages from us.

Loading Code Samples...
Language
package org.twilio.smsmarketing;

import com.twilio.twiml.Body;
import com.twilio.twiml.Message;
import com.twilio.twiml.MessagingResponse;
import com.twilio.twiml.TwiMLException;
import org.twilio.smsmarketing.models.Subscriber;
import org.twilio.smsmarketing.repositories.SubscribersRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TwilioServlet extends HttpServlet {

    private final String SUBSCRIBE_COMMAND = "add";
    private final String UNSUBSCRIBE_COMMAND = "remove";

    private final SubscribersRepository repository;

    @SuppressWarnings("unused")
    public TwilioServlet() {
        this(new org.twilio.smsmarketing.repositories.SubscribersRepository());
    }

    public TwilioServlet(org.twilio.smsmarketing.repositories.SubscribersRepository repository) {
        this.repository = repository;
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        String phone = request.getParameter("From");
        String body = request.getParameter("Body");

        String output;

        try {
            Subscriber subscriber = repository.findByPhoneNumber(phone);
            if (subscriber == null) {
                repository.create(new Subscriber(phone));
                output = "Thanks for contacting TWBC! Text 'add' if you would like to receive updates via text message.";
            } else {
                output = processMessage(body, subscriber);
            }
        } catch (Exception e) {
            output = "Something went wrong. Try again.";
        }

        try {
            MessagingResponse messagingResponse = new MessagingResponse.Builder()
                    .message(new Message.Builder().body(new Body(output)).build())
                    .build();

            response.setContentType("text/xml");

            response.getWriter().write(messagingResponse.toXml());
        } catch (TwiMLException e) {
            throw new RuntimeException(e);
        }
    }

    private String processMessage(String message, Subscriber subscriber) {
        String output = "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";

        if (message.startsWith(SUBSCRIBE_COMMAND) || message.startsWith(UNSUBSCRIBE_COMMAND)) {
            subscriber.setSubscribed(message.startsWith(SUBSCRIBE_COMMAND));
            repository.update(subscriber);
            if (!subscriber.isSubscribed())
                output = "You have unsubscribed from notifications. Text 'add' to start receiving updates again";
            else
                output = "You are now subscribed for updates.";
        }
        return output;
    }

}
src/main/java/org/twilio/smsmarketing/TwilioServlet.java
Register a new subscriber in the database or apply a user command

src/main/java/org/twilio/smsmarketing/TwilioServlet.java

We've created a Subscriber model to keep track of the people that want our messages.  Now, let's look at the options we provide our users for opting in or out.

Managing User Subscriptions

We want to provide the user with two SMS commands to manage their subscription status: add and remove.

These commands will toggle a boolean flag for their Subscriber record in the database and will determine whether they get our marketing messages.  Because we want to respect our user's preferences, we don't opt them in automatically - rather, we have them confirm that they want to receive SMSes or MMSes from us.

To make this happen, we will need to update the controller logic which handles the incoming text message to do a couple things:

  • If it is a add or remove command, create/update a Subscription with the right status in the database.
  • If it is a command we don't recognize, send a message explaining the two available commands.
Loading Code Samples...
Language
package org.twilio.smsmarketing;

import com.twilio.twiml.Body;
import com.twilio.twiml.Message;
import com.twilio.twiml.MessagingResponse;
import com.twilio.twiml.TwiMLException;
import org.twilio.smsmarketing.models.Subscriber;
import org.twilio.smsmarketing.repositories.SubscribersRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TwilioServlet extends HttpServlet {

    private final String SUBSCRIBE_COMMAND = "add";
    private final String UNSUBSCRIBE_COMMAND = "remove";

    private final SubscribersRepository repository;

    @SuppressWarnings("unused")
    public TwilioServlet() {
        this(new org.twilio.smsmarketing.repositories.SubscribersRepository());
    }

    public TwilioServlet(org.twilio.smsmarketing.repositories.SubscribersRepository repository) {
        this.repository = repository;
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        String phone = request.getParameter("From");
        String body = request.getParameter("Body");

        String output;

        try {
            Subscriber subscriber = repository.findByPhoneNumber(phone);
            if (subscriber == null) {
                repository.create(new Subscriber(phone));
                output = "Thanks for contacting TWBC! Text 'add' if you would like to receive updates via text message.";
            } else {
                output = processMessage(body, subscriber);
            }
        } catch (Exception e) {
            output = "Something went wrong. Try again.";
        }

        try {
            MessagingResponse messagingResponse = new MessagingResponse.Builder()
                    .message(new Message.Builder().body(new Body(output)).build())
                    .build();

            response.setContentType("text/xml");

            response.getWriter().write(messagingResponse.toXml());
        } catch (TwiMLException e) {
            throw new RuntimeException(e);
        }
    }

    private String processMessage(String message, Subscriber subscriber) {
        String output = "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";

        if (message.startsWith(SUBSCRIBE_COMMAND) || message.startsWith(UNSUBSCRIBE_COMMAND)) {
            subscriber.setSubscribed(message.startsWith(SUBSCRIBE_COMMAND));
            repository.update(subscriber);
            if (!subscriber.isSubscribed())
                output = "You have unsubscribed from notifications. Text 'add' to start receiving updates again";
            else
                output = "You are now subscribed for updates.";
        }
        return output;
    }

}
src/main/java/org/twilio/smsmarketing/TwilioServlet.java
Validate and execute a subscriber's command

src/main/java/org/twilio/smsmarketing/TwilioServlet.java

Let's now take a look at how to send notifications.

Send Notifications Out

When a form is submitted, we first grab the message text and/or image URL.  Second, we loop through all Subscribers and call the method send on our Sender domain object.

When the messages are on their way, render the index page with a success message and delight the marketing team.

Loading Code Samples...
Language
package org.twilio.smsmarketing;

import org.twilio.smsmarketing.lib.RequestParametersValidator;
import org.twilio.smsmarketing.lib.Sender;
import org.twilio.smsmarketing.models.Subscriber;
import org.twilio.smsmarketing.repositories.SubscribersRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class NotificationsServlet extends HttpServlet {

    private final SubscribersRepository repository;

    @SuppressWarnings("unused")
    public NotificationsServlet() {
        this(new SubscribersRepository());
    }

    public NotificationsServlet(org.twilio.smsmarketing.repositories.SubscribersRepository repository) {
        this.repository = repository;
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getRequestDispatcher("/notifications.jsp").forward(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        String message = request.getParameter("message");
        String imageUri = request.getParameter("imageUrl");

        if (validateRequest(request)) {
            try {
                Sender sender = new Sender();
                Iterable<Subscriber> subscribers = repository.findAllSubscribed();
                for (Subscriber s : subscribers) {
                    sender.send(s.getPhoneNumber(), message, imageUri);
                }
            } catch (Exception e) {
                request.setAttribute("flash", "Something when wrong.");
            }

            request.setAttribute("flash", "Messages on their way!");
            request.getRequestDispatcher("/notifications.jsp").forward(request, response);
        } else {
            request.getRequestDispatcher("/notifications.jsp").forward(request, response);
        }
    }

    private boolean validateRequest(HttpServletRequest request) {
        RequestParametersValidator validator = new RequestParametersValidator(request);

        return validator.validatePresence("message", "imageUrl");
    }
}
src/main/java/org/twilio/smsmarketing/NotificationsServlet.java
Send a message to all active subscribers

src/main/java/org/twilio/smsmarketing/NotificationsServlet.java

Let's take a closer look at how to send SMS or MMS notifications.

Sending SMS or MMS Notifications

When the model object is loaded, it creates a Twilio REST API client that can be used to send SMS and MMS messages. The client requires your Twilio account credentials (an account SID and auth token), which can be found in the console:

console credentials

Next, all we need to do is create a message using the MessageCreator class. The Twilio Message API call creator requires To, From and Body parameters.  MediaUrl is an optional parameter.

Loading Code Samples...
Language
package org.twilio.smsmarketing.lib;

import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.MessageCreator;
import com.twilio.type.PhoneNumber;

public class Sender {

    private final TwilioRestClient client;

    public Sender() {
        client = new TwilioRestClient.Builder(Config.getAccountSid(), Config.getAuthToken()).build();
    }

    public Sender(TwilioRestClient client) {
        this.client = client;
    }

    public void send(String to, String message, String imageUri) {
        new MessageCreator(
                new PhoneNumber(to),
                new PhoneNumber(Config.getPhoneNumber()),
                message
        ).setMediaUrl(imageUri).create(client);
    }

}
src/main/java/org/twilio/smsmarketing/lib/Sender.java
Use Twilio Java client to send a message

src/main/java/org/twilio/smsmarketing/lib/Sender.java

That's it! We've just implemented a an opt-in process and an administrative interface to run an SMS and MMS marketing campaign. Now all you need is killer content to share with your users via text or MMS.

Where to Next?

Love Java?  So do we at Twilio!  Here are two other excellent tutorials you should check out:

SMS and MMS Notifications

SMS notifications are a great way to alert humans quickly when important events happen. In this example, we'll show you how to send SMS notifications to a list of people when an error happens in a web application.

Automated Surveys

Don't let valuable insights slip away - survey your customers with Voice and SMS.

Did this help?

Thanks for checking out this tutorial... tweet @twilio to let us know how you did and what you're building!

Kevin Whinnery
Paul Kamp
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
package org.twilio.smsmarketing.models;

import javax.persistence.*;

@SuppressWarnings("unused")
@Entity
@Table(name = "subscribers")
public class Subscriber {

    public Subscriber() {
    }

    public Subscriber(String phoneNumber){
        this.phoneNumber = phoneNumber;
        this.subscribed = false;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "phone_number")
    private String phoneNumber;

    @Column(name = "suscribed")
    private boolean subscribed;


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public boolean isSubscribed() {
        return subscribed;
    }

    public void setSubscribed(boolean subscribed) {
        this.subscribed = subscribed;
    }
}
package org.twilio.smsmarketing;

import com.twilio.twiml.Body;
import com.twilio.twiml.Message;
import com.twilio.twiml.MessagingResponse;
import com.twilio.twiml.TwiMLException;
import org.twilio.smsmarketing.models.Subscriber;
import org.twilio.smsmarketing.repositories.SubscribersRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TwilioServlet extends HttpServlet {

    private final String SUBSCRIBE_COMMAND = "add";
    private final String UNSUBSCRIBE_COMMAND = "remove";

    private final SubscribersRepository repository;

    @SuppressWarnings("unused")
    public TwilioServlet() {
        this(new org.twilio.smsmarketing.repositories.SubscribersRepository());
    }

    public TwilioServlet(org.twilio.smsmarketing.repositories.SubscribersRepository repository) {
        this.repository = repository;
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        String phone = request.getParameter("From");
        String body = request.getParameter("Body");

        String output;

        try {
            Subscriber subscriber = repository.findByPhoneNumber(phone);
            if (subscriber == null) {
                repository.create(new Subscriber(phone));
                output = "Thanks for contacting TWBC! Text 'add' if you would like to receive updates via text message.";
            } else {
                output = processMessage(body, subscriber);
            }
        } catch (Exception e) {
            output = "Something went wrong. Try again.";
        }

        try {
            MessagingResponse messagingResponse = new MessagingResponse.Builder()
                    .message(new Message.Builder().body(new Body(output)).build())
                    .build();

            response.setContentType("text/xml");

            response.getWriter().write(messagingResponse.toXml());
        } catch (TwiMLException e) {
            throw new RuntimeException(e);
        }
    }

    private String processMessage(String message, Subscriber subscriber) {
        String output = "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";

        if (message.startsWith(SUBSCRIBE_COMMAND) || message.startsWith(UNSUBSCRIBE_COMMAND)) {
            subscriber.setSubscribed(message.startsWith(SUBSCRIBE_COMMAND));
            repository.update(subscriber);
            if (!subscriber.isSubscribed())
                output = "You have unsubscribed from notifications. Text 'add' to start receiving updates again";
            else
                output = "You are now subscribed for updates.";
        }
        return output;
    }

}
package org.twilio.smsmarketing;

import com.twilio.twiml.Body;
import com.twilio.twiml.Message;
import com.twilio.twiml.MessagingResponse;
import com.twilio.twiml.TwiMLException;
import org.twilio.smsmarketing.models.Subscriber;
import org.twilio.smsmarketing.repositories.SubscribersRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TwilioServlet extends HttpServlet {

    private final String SUBSCRIBE_COMMAND = "add";
    private final String UNSUBSCRIBE_COMMAND = "remove";

    private final SubscribersRepository repository;

    @SuppressWarnings("unused")
    public TwilioServlet() {
        this(new org.twilio.smsmarketing.repositories.SubscribersRepository());
    }

    public TwilioServlet(org.twilio.smsmarketing.repositories.SubscribersRepository repository) {
        this.repository = repository;
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        String phone = request.getParameter("From");
        String body = request.getParameter("Body");

        String output;

        try {
            Subscriber subscriber = repository.findByPhoneNumber(phone);
            if (subscriber == null) {
                repository.create(new Subscriber(phone));
                output = "Thanks for contacting TWBC! Text 'add' if you would like to receive updates via text message.";
            } else {
                output = processMessage(body, subscriber);
            }
        } catch (Exception e) {
            output = "Something went wrong. Try again.";
        }

        try {
            MessagingResponse messagingResponse = new MessagingResponse.Builder()
                    .message(new Message.Builder().body(new Body(output)).build())
                    .build();

            response.setContentType("text/xml");

            response.getWriter().write(messagingResponse.toXml());
        } catch (TwiMLException e) {
            throw new RuntimeException(e);
        }
    }

    private String processMessage(String message, Subscriber subscriber) {
        String output = "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";

        if (message.startsWith(SUBSCRIBE_COMMAND) || message.startsWith(UNSUBSCRIBE_COMMAND)) {
            subscriber.setSubscribed(message.startsWith(SUBSCRIBE_COMMAND));
            repository.update(subscriber);
            if (!subscriber.isSubscribed())
                output = "You have unsubscribed from notifications. Text 'add' to start receiving updates again";
            else
                output = "You are now subscribed for updates.";
        }
        return output;
    }

}
package org.twilio.smsmarketing;

import org.twilio.smsmarketing.lib.RequestParametersValidator;
import org.twilio.smsmarketing.lib.Sender;
import org.twilio.smsmarketing.models.Subscriber;
import org.twilio.smsmarketing.repositories.SubscribersRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class NotificationsServlet extends HttpServlet {

    private final SubscribersRepository repository;

    @SuppressWarnings("unused")
    public NotificationsServlet() {
        this(new SubscribersRepository());
    }

    public NotificationsServlet(org.twilio.smsmarketing.repositories.SubscribersRepository repository) {
        this.repository = repository;
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getRequestDispatcher("/notifications.jsp").forward(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        String message = request.getParameter("message");
        String imageUri = request.getParameter("imageUrl");

        if (validateRequest(request)) {
            try {
                Sender sender = new Sender();
                Iterable<Subscriber> subscribers = repository.findAllSubscribed();
                for (Subscriber s : subscribers) {
                    sender.send(s.getPhoneNumber(), message, imageUri);
                }
            } catch (Exception e) {
                request.setAttribute("flash", "Something when wrong.");
            }

            request.setAttribute("flash", "Messages on their way!");
            request.getRequestDispatcher("/notifications.jsp").forward(request, response);
        } else {
            request.getRequestDispatcher("/notifications.jsp").forward(request, response);
        }
    }

    private boolean validateRequest(HttpServletRequest request) {
        RequestParametersValidator validator = new RequestParametersValidator(request);

        return validator.validatePresence("message", "imageUrl");
    }
}
package org.twilio.smsmarketing.lib;

import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.MessageCreator;
import com.twilio.type.PhoneNumber;

public class Sender {

    private final TwilioRestClient client;

    public Sender() {
        client = new TwilioRestClient.Builder(Config.getAccountSid(), Config.getAuthToken()).build();
    }

    public Sender(TwilioRestClient client) {
        this.client = client;
    }

    public void send(String to, String message, String imageUri) {
        new MessageCreator(
                new PhoneNumber(to),
                new PhoneNumber(Config.getPhoneNumber()),
                message
        ).setMediaUrl(imageUri).create(client);
    }

}