TLDR
There’s a summary at the bottom of this article!The Origins of Laudspeaker’s design
Eight months ago we decided to build an MVP of Laudspeaker, a drag and drop, no-code, editor for designing messaging workflows. We wanted our users to be able create flow charts, which we called “journeys”, that encapsulate logic defining how and when to reach the users of their own applications. The journeys would be used to “engage” or (re)activate customers, for example sending reminders about new products or features, sending newsletters, or reminding customers about abandoned shopping carts. When we started to design the Laudspeaker MVP, it was not clear how to begin, since resources about building no code tools from first principles are not readily available despite their recent uptick in popularity. We did have experience using our competitor’s products - one of the founders had used braze at a previous role in product and had a laundry list of issues with it. The problem with fixing a hundred small issues, though, was that it seemed more like treating symptoms of a problem rather than treating the underlying cause. Instead of iterating on the current offerings, we wanted to begin from first principles: define the problem abstractly, boil it down to into as few basic abstract concepts as possible and then see if these core concepts completely ‘cover’ a set of common real world uses. We included complex examples in our uses, to test the limits of our concepts. For example we included: All visitors who- have signed up to an ecommerce site
- have added a sku to their cart
- have NOT completed a transaction
- live in Atlanta should receive
- an sms only during black Friday
- an email every other day
- An event is an external action that should trigger some change in laudspeaker.
- An audience is some subset of users.
- A trigger is a set of conditions that move a user from one audience to another audience
- A message is a member of some set of email, sms, slack etc.
Missing Engineering Design Principles
Even with these concepts however something felt missing - when we looked at the existing products, they also mostly had versions of our concepts at play yet still felt hard to grasp or somewhat incomplete. In particular with existing solutions it was hard to tell if a user was going to be sent a message, had been sent a message or why a message was sent. What was missing, after some rumination, was a clear understanding and separation of ‘state’ from ‘logic’. Here logic pertained to when and who to send messages to, state tracked whether a user had been sent a message or not. We realised there are a number of great engineering abstractions to help with this. They included Directed Acyclic Graphs (DAGs), Finite State Machines (FSMs), and other more esoteric automata (like Petri Nets).Directed Acyclic Graphs (DAGs)
We considered each of them in detail, and also looked to see if they had been used as the idea behind other tools and software we knew. DAGs in particular seemed to be pretty popular, and are used in a number of graphical applications, including tools like Apache Airflow. Airflow was an instructive example for us to study, because at a high level they are many similarities: Airflow is “a platform that lets you build and run workflows”; we too are defining workflows but specific to customer messaging. Some of the nice properties of DAGs are they are easy to visualize, have a clear start and end point, can be used to separate state and logic, and are amenable to parallelization. One major issue for us however, was by definition they don’t include cycles. One of our example real world use cases was: Whenever a user clicks on a button in an app, send them a push notification. The number of times a user clicks the button is not predefined, and a DAG would not be able to capture this case.(Visual) Programming languages and Finite State Machines
This led us to consider directed graphs more generally, but another realization helped us here. A Visual drag and drop editor is in some non-trivial way akin to a (visual) programming language! With this small insight we decided to revisit some theory we had learnt at university - in particular the theory of computation. We wanted our users to be able to easily drag and drop components in our tool to design complex journeys that could include branching (if statements), loops, and could easily be used to track state. Given these requirements, and the fact we wanted to keep our tool simple, a great abstraction to use here was that of finite state machines. From wikipedia: “a FSM can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some inputs; the change from one state to another is called a transition.” Graphically they can be represented like this:
They include loops, and it is easy to keep track of which state you are in.
When converting our concepts to a finite state machine, the design seemed to click and the mappings were simple:
Mappings
| Our Concept : | FSM concept |
| A step : | FSM state |
| A trigger : | FSM state transition |
If a user is in a particular step, you can be sure they have received the message that is in that step.
If a user completes an action that is defined in a trigger, they move to another step (the transition). If that next step contains a message, the user will receive the message on entering that step.
Another nice property of the FSM abstraction for us, was that we felt it would be extensible as we added more functionality. We are planning to add random branching soon, so that a user who completes a trigger could randomly be moved to any one of many different states. The theory of non-deterministic FSMs, and its equivalence to deterministic FSMs means we won’t have to change our design principles to handle this.
Finally, an under-appreciated bonus of using FSMs, is that ease with which we can describe a journey: we simply need a state transition diagram, and knowledge about a users current state to capture relevant information. If you want to track a users history you simply track previous states. You could imagine a future, where if Laudspeaker users wish they could describe our journeys both visually and as code thus opening up the ability to define dynamic journeys; you could write a program to define hundreds or thousands of similar journeys differing in a couple of parameters automatically, and could easily log and test them.