DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Using Kong Ingress Controller with Spring Boot Services
  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Actuator Enhancements: Spring Framework 6.2 and Spring Boot 3.4

Trending

  • Optimizing Integration Workflows With Spark Structured Streaming and Cloud Services
  • Ensuring Configuration Consistency Across Global Data Centers
  • Next-Gen IoT Performance Depends on Advanced Power Management ICs
  • Four Essential Tips for Building a Robust REST API in Java
  1. DZone
  2. Coding
  3. Frameworks
  4. Integrating Twilio Into My SaaS Solution In Heroku

Integrating Twilio Into My SaaS Solution In Heroku

In the 2nd part of his series, a Zone Leader/DZone Core member implements Twilio within Heroku to act as a lightweight client.

By 
John Vester user avatar
John Vester
DZone Core CORE ·
Sep. 09, 20 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
69.6K Views

Join the DZone community and get the full member experience.

Join For Free

In the "Using Heroku to Quickly Build a Multi-Tenant SaaS Product" article, I documented the foundation for a new SaaS solution that I am building (initially for my sister-in-law) — utilizing the Heroku ecosystem. At the end of that article, I had planned to write about the core technologies (Spring Boot, Angular 9, ClearDB, Okta, GitLab and Heroku) in place, as we raced for the 1.0.0 release of the solution.

However, things have been moving fast around here, and I largely attribute the speed of development and end-user adoption to everything that Heroku brings to the table for this service. As I have noted before, using Heroku as the destination for my service and GitLab's built-in CI/CD pipelines — updates that are merged from a feature branch into the master branch of the client or service are automatically deployed into production (Heroku) without any further intervention.

Instead of starting the 1.0.1 release, the project is actually finishing up the 1.0.3 release.  Below is an updated feature map:

Features by release

Progress Via Screenshots

Since using Spring JPA to create the initial schema, which it did without any issues, I have been keeping the fitness.sql up to date manually as model updates have been required.  Using IntelliJ IDEA and built in database tooling (including yFiles), I was able to quickly create the following diagram of the current MySQL/ClearDB database structure:

Schema in FitNess app

As noted in the prior article, everything ties back to the ID of the tenant (or fitness trainer) - which is also enforced in the base JPA Specifications employed at the service level.

These changes, along with screens for Client configuration and Workout configuration, allowed the Session screen to be introduced:

New session in FitNess

The Sessions (which is an instance of a workout and at least one client) are presented in an optional calendar view to give the trainer a view which is easy to comprehend at a glance:

Calendar in FitNess

In the example above, there is only one event scheduled - since the screen shot is from my test account in the production instance of the SaaS solution.

There is also a training-mode version of the Session screen, which is mainly a read-only adaptation and consolidates data for quick reference.

Managing workout and attendees page

The icons on this screen are still active, so that that trainer can not only check-in clients, but also provide session information (score and comments) too:

Workout score and notes modal

Along the Way ... Twilio Was Discovered

One of items which was slated for the 1.0.4 release was referred to as Client Check-In.  We soon realized the having a mechanism for the trainer to communicate with the clients was an important aspect, which needed to be handled next.

Below are the requirements that were employed for this feature:

  • send reminders to clients ~24 hours before their scheduled session
  • send a workout summary after their workout
  • resend a reminder for a session
  • allow trainer to send a broadcast text message (e,g, "I am going to be on vacation")
  • provide a link to allow client to confirm/cancel their session (planned for a future article)

Since the concept of a mobile client is beyond the current feature roadmap, I needed to find a solution that would work with most clients. Knowing that each client was communicating with my sister-in-law via a mobile device, I decided to use SMS (or text messages) to provide a lightweight client for the application.

It did not take long to settle on Twilio — especially since two colleagues (roberttables and blendedsoftware) who broadcast on Twitch had been participating in Twilio's "TwilioQuest" awesome learning experience. In fact, I would not be surprised if both still have videos on their channels of them playing the "TwilioQuest" game.

Twilio provides the functionality I need with a robust API. I was able to quickly set up a trial account (which communicates only with mobile numbers that are added to the system), but that was enough for me to validate the functionality.

Preparing for Twilio

The first step is to create a new account in Twilio by visiting the following URL:

https://www.twilio.com/try-twilio

Which should direct first-time users to a screen similar to what is shown below:

Getting started with Twilio


Once everything is setup, the Twilio dashboard will appear as shown below:

Fitness API on Twilio


The Account SSID and Auth Tokens are accessible via this screen.

I wanted to configure Twilio programmatically in mode project, so that using my production instance of Twilio was based upon environment variables passed in from Heroku. Below is a list of the attributes that I am managing in my API:

Shell
xxxxxxxxxx
1
13
 
1
jvc:
2
  sms:
3
    twilio:
4
      enabled: ${TWILIO_ENABLED}
5
      account-sid: ${TWILIO_ACCOUNT_SID}
6
      auth-token: ${TWILIO_AUTH_TOKEN}
7
      phone-number: ${TWILIO_PHONE_NUMBER}
8
      enable-reply: ${TWILIO_ENABLE_REPLY}
9
      reply-host: ${TWILIO_REPLY_HOST}
10
      trial: ${TWILIO_IS_TRIAL}
11
      trial-phone-number: ${TWILIO_TRIAL_PHONE_NUMBER}
12
      cron-schedule-reminder: "0 17 * * * ?"
13
      cron-schedule-summary: "0 27 * * * ?"


The README.md for my API repository provides the documentation details for the environment-specific values:

Shell
xxxxxxxxxx
1
 
1
${TWILIO_ENABLED} - Twilio (SMS) enabled [jvc.sms.twilio.enabled] (set to false in order to disable SMS)
2
${TWILIO_ACCOUNT_SID} - Twilio (SMS) account SID [jvc.sms.twilio.account-sid] (leave unset to disable SMS, recommended for non-Production environments)
3
${TWILIO_AUTH_TOKEN} - Twilio (SMS) auth token [jvc.sms.twilio.auth-token]  (leave unset to disable SMS, recommended for non-Production environments)
4
${TWILIO_PHONE_NUMBER} - Twilio (SMS) phone number to use for sending messages [jvc.sms.twilio.phone-number]
5
${TWILIO_ENABLE_REPLY} - Twilio (SMS) enablement of reply functionality [jvc.sms.twilio.enable-reply]
6
${TWILIO_REPLY_HOST} - Twilio (SMS) reply host URL (of Angular client) to use when reply functionality is enabled [jvc.sms.twilio.reply-host] (value should not end with a /)
7
${TWILIO_IS_TRIAL} - Twilio (SMS) trial indicator [jvc.sms.twilio.trial] (use for non-Production instances)
8
${TWILIO_TRIAL_PHONE_NUMBER} - Twilio (SMS) trial phone number to send to [jvc.sms.twilio.trial-phone-number] (use for non-Production instances)


The cron based items determine the schedule of the session reminder and session summary jobs which are running in each environment (if enabled).

Within Spring Boot, a simple TwilioConfigurationProperties class was created:

Java
xxxxxxxxxx
1
15
 
1
@Data
2
@Configuration("twilioConfigurationProperties")
3
@ConfigurationProperties("jvc.sms.twilio")
4
public class TwilioConfigurationProperties {
5
    private boolean enabled;
6
    private String accountSid;
7
    private String authToken;
8
    private String phoneNumber;
9
    private boolean enableReply;
10
    private String replyHost;
11
    private boolean trial;
12
    private String trialPhoneNumber;
13
    private String cronScheduleReminder;
14
    private String cronScheduleSummary;
15
}


With these values set up, I can use the Run/Debug dialog in IntelliJ IDEA to pass in the expected values:

Debugging in IntelliJ

In Heroku, these values are set up in the environment:

Twilio environment variables in Heroku

Keep in mind, the Heroku values are different, because that is actually the production instance that I have running.

The Sms Service

Within the SmsService, the following public methods are available:

Java
xxxxxxxxxx
1
38
 
1
@RequiredArgsConstructor
2
@Slf4j
3
@Service
4
@Transactional
5
public class SmsService {
6
  @Resource(name = "requestScopeUserData")
7
  private UserData userData;
8
9
  private final AttendeeRepository attendeeRepository;
10
  private final MotivationalQuoteRepository motivationalQuoteRepository;
11
  private final SessionRepository sessionRepository;
12
  private final TwilioConfigurationProperties twilioConfigurationProperties;
13
14
  public void sendGroupMessage(GroupSmsMessage groupSmsMessage) throws FitnessException {
15
    // allows a trainer to send a SMS message to a select group of customers
16
  }
17
18
  public void sendSimpleMessage(SimpleSmsMessage simpleSmsMessage) {
19
    // allows the system to send a single SMS message, which is used to send a message to the trainer each time a client confirms or cancels their session
20
  }
21
22
  public void resendSingleReminder(long attendeeId) {
23
    // allows the trainer to render the session reminder.
24
  }
25
26
  @Scheduled(cron = "${jvc.sms.twilio.cron-schedule-reminder}")
27
  public void sendSmsMessages() {
28
    // allows the trainer to render the session reminder.
29
  }
30
31
  @Scheduled(cron = "${jvc.sms.twilio.cron-schedule-summary}")
32
  public void sendSmsSummaryMessages() {
33
    // ran hourly, provides a workout summary (including a random fitness quote) to the customer - which can include:
34
    // -  the score from the session
35
    // - any comments provided by the trainer
36
    // - a random fitness quote
37
  }
38
}


These methods are called either by a scheduled task or triggered from a request from the Angular client. The next section presents and example of how the Angular client interacts with the Spring Boot service in order to communicate with the Twilio service.

Example - Sending a Group Message

Sending a Group Message is initiated by the Angular client.  An example is shown below:

Creating a test message

The request is passed to the following controller in Spring Boot:

Java
xxxxxxxxxx
1
10
 
1
@PostMapping(value = "/sms")
2
public ResponseEntity<Void> postSession(@RequestBody GroupSmsMessage groupSmsMessage) {
3
    try {
4
        smsService.sendGroupMessage(groupSmsMessage);
5
        return new ResponseEntity<>(HttpStatus.CREATED);
6
    } catch (FitnessException e) {
7
        log.error(e.getMessage());
8
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
9
    }
10
}


The GroupSmsMessage object has the following properties:

Java
xxxxxxxxxx
1
 
1
@Data
2
public class GroupSmsMessage {
3
    private String messageBody;
4
    private List<ClientDto> distributionList = new ArrayList<>();
5
}


The controller then calls the following method:

Java
x
41
 
1
public void sendGroupMessage(GroupSmsMessage groupSmsMessage) throws FitnessException {
2
    log.info("sendGroupMessage(groupSmsMessage={})", groupSmsMessage);
3
4
    if (CollectionUtils.isEmpty(groupSmsMessage.getDistributionList())) {
5
        throw new FitnessException(FitnessException.ENTITIES_MISSING, "Could not send group SMS message to an empty distribution list.");
6
    }
7
8
    if (StringUtils.isEmpty(groupSmsMessage.getMessageBody())) {
9
        throw new FitnessException(FitnessException.ENTITIES_MISSING, "Could not send group SMS message with an empty message body.");
10
    }
11
12
    if (!smsEnabled()) {
13
        throw new FitnessException(FitnessException.UNAUTHORIZED, "sendGroupMessage() SMS is NOT enabled to run on this instance");
14
    }
15
16
    Twilio.init(twilioConfigurationProperties.getAccountSid(), twilioConfigurationProperties.getAuthToken());
17
18
    log.info("sendGroupMessage() started");
19
    logSmsInformation();
20
21
    int successCount = 0;
22
    int errorCount = 0;
23
24
    for (ClientDto clientDto : groupSmsMessage.getDistributionList()) {
25
        Message message = Message.creator(
26
          new PhoneNumber(twilioConfigurationProperties.isTrial() ? twilioConfigurationProperties.getTrialPhoneNumber() : clientDto.getPersonDto().getPhoneNumber()),
27
          new PhoneNumber(twilioConfigurationProperties.getPhoneNumber()),
28
          groupSmsMessage.getMessageBody())
29
          .create();
30
31
        if (message.getErrorCode() == null && StringUtils.isEmpty(message.getErrorMessage())) {
32
            successCount++;
33
        } else {
34
            errorCount++;
35
            log.error("Error #" + message.getErrorCode() + "(" +  message.getErrorMessage() + ") occurred sending smsMessage=" + groupSmsMessage.getMessageBody());
36
        }
37
    }
38
39
    log.info("Successfully sent {} SMS messages ({} errors)", successCount, errorCount);
40
    log.info("sendGroupMessage() completed");
41
}


In the code above, after checking for elements which can yield a Fitness exception, the Twilio.init() method is called.  Thereafter, a Message is created and the logic has been included to function in production and non-production environments.  This basically ties to using the provided trial phone number when not running in production.  Everything else is straight forward and pass the data received by the Angular client directly to the target distribution list.

Conclusion

The goal of this article was to outline some client communication needs for the SaaS solution I am building.  Thereafter, I provided an example of how easy it is to create a Twilio instance and integrate the solution into my Spring Boot service and Angular client.  Using Spring Boot and an Angular client, I was able to quickly and easily get SMS communication configured in use within a matter of hours.  In my view, Twilio provides an awesome compliment to the services I am using with Heroku.

From a cost perspective, my sister-in-law has been using the SMS functionality for right at two months now and I just added more money into my production Twilio account two days ago.  At this point, the total usage has been right at $10 a month - which is far less than I expected to use.

In the next article, I plan to discuss how Twilio was used to send the session reminder, providing a mechanism where clients can confirm or cancel their attendance to this session.  I had initially planned to include everything in one article, but I felt it would be better to create an article dedicated to this functionality.  In another future article, I plan to dive into how an end-user's reply to the original (Twilio) SMS can be redirected to the actual trainer for that customer. Exciting articles planned, for sure!

Have a really great day!

Spring Framework SaaS intellij Session (web analytics) Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Using Kong Ingress Controller with Spring Boot Services
  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Actuator Enhancements: Spring Framework 6.2 and Spring Boot 3.4

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!