A Low Code World with Twilio, AWS, Python and R
The Setup
This post came about because I am big on New Year’s resolutions. This year, I put down a resolution to publish something once a month, so that is how we got here. Another resolution is to run 1,500 miles and that, alongside an interest in Twilio, is how we got to the topic of this piece. Although this is the first writing that I have done voluntarily and with a goal to share publicly, I hope it will not be my last.
This is not meant to be a step-by-step tutorial on using Twilio, it is not meant to be about the code and it is not meant to be an actual viable product (however, I share most of the code and resources). This is more about the experience of using Twilio and getting a better feel for the product. Then, as I was building this basic application, I was amazed how quickly you can get an application up from scratch these days. It was fascinating to see how you can connect all these different APIs with infrastructure as a service (IaaS), how important APIs are for the future of software and how these API-first companies can be good businesses. I think at the end of this piece you will see just how quickly an application can get up running in the cloud and how critical APIs are in this process. With this intro, let’s move on to what I set out to do.
Because I am committed to running 1,500 miles, I figured that I would build a simple app to record my runs and display them on the internet. The end product is a simple front-end dashboard that displays my completed runs. I have an Apple Watch, so there are various apps that can accomplish this goal better (see Strava for an awesome app that does this and much more), but I wanted to try something new. I also wanted to see how quickly I could get something off the ground. With an interest in Twilio, I decided to log my runs via text message. The text message would include the date and miles and trigger an AWS Lambda function. Twilio and Lambda are connected via AWS API Gateway service. The Lambda function would then send the data to a database table to be updated. For the front end / website, all I wanted was a simple dashboard, so I chose to use R and the Shiny package. With Shiny pulling data from the DB, once the text message data is uploaded, so is the web app. Using Twilio’s API, I text a phone number that I purchased through Twilio and there are two outcomes:
Success:
This response updated the database with the date (2021-01-13) and the number of miles (4.25)
It also gave me a little positive reinforcement by telling me that I did a great job!
Failure:
This is the error response in case things go wrong. This way I know if my miles were not logged. Although, maybe a bug in my code would’ve been a good scapegoat if I miss my goal…
For the lambda function, I decided to write the code in Python, since Twilio has a helper library in this language. That is the entirety of the process. For those who are more visually inclined, the workflow looks like the following:
The final frontend can be seen here: run tracking (I used R Studios shinyapps.io for hosting). This frontend displays the following couple of metrics: 1) Miles logged for the past 5 days and 2) If I am on pace or not.
Twilio
To help me with this exercise, I relied on Twilio’s documentation. Their documentation is well thought out and makes it easy for developers to get started. Here is a screenshot from one of their tutorials:
This screenshot is for Twilio’s getting started documentation. This tutorial gave insight into what I was about to do and helped me decide if it was the right tutorial for me. Another thing I really like about their documentation is they keep the tutorial on the left and have the code on the right. This makes it easy to read the explanation for what I was doing and then match up that explanation with the corresponding code. Documentation beside the code makes for a nice experience. Also, as you scroll down the documentation, the code on the right scrolls to keep up with the relevant sections, making it easy for the developer to have the code alongside the proper documentation.
Along with the documentation, Twilio has developed several helper libraries for most major programming languages. This makes it easy and intuitive to perform simple tasks with their API in your favorite programming language. For example, in Python, I simply call MessagingResponse()
to build out the proper xml format that the Twilio API expects.
With the helper library, you write the following:
To generate the corresponding Twilio Markup Language
Having beautiful docs that save time cannot be understated. As a developer, I will happily pay more for a product if I can quickly find an answer to my question. Well thought-out docs do just this. Twilio has spent time developing docs for a myriad of cases that made coding up my simple application easy. Instead of googling and relying on third parties to help me out, I was able to use Twilio’s doc to power my API journey. Sure, I was not doing anything too complex, but this simple use case and their well-thought-out documentation make me believe that going deeper into their API will not be much more difficult.
In addition to their API and docs, it is also easy to purchase products, like phone numbers, from Twilio’s console and connect that phone number with a webhook. To add a number, you just click a button. Once that number is purchased, you have many capabilities (i.e., voice messages, text messages, etc.). Using Twilio, it is easy to see that it is a great product and a well-developed API. I think this API makes for a powerful communication as a service business.
API Powers
To talk about the business of Twilio and API-first companies, we will draw on Helmer's work. I believe that Twilio exhibits two powers that are common to API-first companies: network economies and switching costs.
As more people use Twilio, the network of developers will grow and most likely these same developers will give back to the product they use, creating more resources for free. As more questions arise on Stack Overflow, more answers and use cases for Twilio will be covered. This will help other developers get started and their services grow, while also helping Twilio understand where they should focus their API development and where they need more documentation. The effects of a growing community not only make Twilio a more attractive product to other developers but makes it a more viral business as more developers advocate on behalf of the product. As Twilio continues to increase its market share in the communication as a service space, the network economies will continue to come to fruition.
Next, there is stickiness that is associated with an API. If I get proficient using Twilio’s product, the switching costs becomes even further embedded into my software. The more embedded, the stickier. Now if I want to switch, I must learn a new API and replace the existing API, something I would prefer not to do. If Twilio continues to advance and add features, I will be happy using their product. Based on their acquisition history, most recently Segment, it seems Twilio will continue to add to their suite of products. As a result, I will happily pay a little more to keep using the product I know versus learning a new API. I do not think a developer is going to want to rip out Twilio’s API and replace it with another product to save a few bucks. But for Twilio, these “few bucks” of pricing power can truly add up overtime as their customer base grows.
These are just a few things that come to mind from the simple application that I built. Even from this simple application, a couple of pain points arise when thinking about switching. You can see that a much larger organization relying on many more services from Twilio is not going to want to switch from Twilio without careful considerations, making a good case for switching cost power.
There is one other advantage that API companies can benefit from that is slightly different than your standard company: their ability to grow with smaller companies. API companies are a little like seed investors in this way. If they can get their API used by 100 small businesses / startups, they can grow with those businesses. If two of the 100 succeed to be 100x larger, then Twilio’s business likely grows alongside these companies. That is not a bad business model, especially, when it is tacked on top of the other power’s API-first companies exhibit.
As a developer, it was a delight to use their product. From a business perspective, I can see the powers the product possesses and that the Company has great strategic positioning.
Finally, IaaS is changing the software world. I was able to connect Twilio to an RDS instance via API gateway that relied on a lambda function written in python, that connects to a frontend written in R. I just mentioned seven different products / software’s that are heavily API dependent and came away with an actual application in under eight hours. APIs really are taking over the world. Connecting software has never been easier and this ease is facilitated by beautifully written developer-oriented APIs. APIs empower the low code movement and empower developers to focus on what differentiates their software instead of setting up infrastructure and writing libraries unrelated to their core business. APIs are advancing software development at an amazing pace. Stripe basically solved payments for small merchants via their famous 7 lines of code API. I think all these APIs bring us closer to a low code future. As these APIs evolve, we get even closer to a future where code does not have to hold someone back from creating something truly great – this is an exciting future to think about.
If you enjoyed, please subscribe and share!
Appendix
Disclaimer: All opinions expressed by the author are his own opinions and do not reflect the opinion of Old Well Partners. This article is for informational purposes only and should not be relied upon as a basis for investment decisions nor constitute a general or personal recommendation or take into account the particular investment objectives, financial situations, or needs of individual investors. Old Well Partners may maintain positions in the securities discussed in this article.
Python Code
def handler(event, context):
"""Lambda function handler for Twilio response."""
print("Event:", str(event))
try:
connection = psycopg2.connect(
user='zak',
password=db_pw,
host=db_host,
database='run'
)
cursor = connection.cursor()
body = event['Body']
body = urllib.parse.unquote_plus(body)
body = body.split(":")
date = body[0]
date = datetime.strptime(date, '%Y-%m-%d')
miles = float(body[1].strip())
insert_query = """INSERT INTO runner (date, miles) VALUES (%s, %s)"""
date_of_run = date.date()
item_tuple = (date_of_run, miles)
cursor.execute(insert_query, item_tuple)
connection.commit()
except Exception as e:
s = str(e)
print(s)
resp = MessagingResponse()
resp.message(s)
return str(resp)
finally:
cursor.close()
connection.close()
text_back = "Great Run!"
resp = MessagingResponse()
resp.message(text_back)
return str(resp)
R Code
ui <- fluidPage(
# Application title
titlePanel("Zak Run Tracking App"),
tabsetPanel(
tabPanel("Run Track",
column(6, plotOutput('last_seven_days')),
column(6, plotOutput('cum_plot')))))
server <- function(input, output) {
con <- dbConnect(RPostgres::Postgres(), dbname = db, host=db_host,
port=db_port, user=db_user, password=db_password)
sql <- "SELECT * FROM runner ORDER BY date"
query <- dbSendQuery(con, sql)
df <- dbFetch(query)
# Add fields needed
df[, 'zak_total_to_date'] <- cumsum(df[, 'miles'])
df[, 'average_needed'] <- 1500 / 365
df[1, 'average_needed'] <- 0
df[, 'pace_needed'] <- cumsum(df[, 'average_needed'])
# Cumsum plot
cum_data <- df %>%
select(date, zak_total_to_date, pace_needed) %>%
tidyr::pivot_longer(., c('zak_total_to_date', 'pace_needed'))
cum_data$name <- ifelse(cum_data$name == 'zak_total_to_date', 'zak', 'pacer')
output$last_seven_days <- renderPlot({
# show last 5 runs
x <- tail(df, 5)
x$label <- as.character(x$miles)
g <- ggplot(x, aes(date, miles, fill="red"))
g +
geom_col() +
ggtitle("Last 5 Runs") +
xlab("Date") +
ylab("Miles") +
scale_x_date(breaks = x$date) +
geom_text(label=x$label, position = position_dodge(width =0.9), vjust=-0.75) +
theme_classic() +
theme(legend.position = "none")
})
output$cum_plot <- renderPlot({
ggplot(cum_data, aes(x=date, y=value, color=name)) +
geom_line() +
ggtitle("Total Miles Ran vs. Pace Needed") +
xlab("Date") +
ylab("Miles") +
theme_classic()
})
}
# Run the application
shinyApp(ui = ui, server = server)