What keeps Hubbub's kissing squirrels going?
During my interview at Hubbub, I was asked how I would design a system to handle promotions on orders, like discounts and so on. Little did I know that I would soon find myself being asked to do it for real, nor what an interesting problem it would turn out to be. It's fair to say that everybody was surprised to discover that it would end up providing a solution to several months of seemingly unrelated scheduled work in our backlog, and completely bypassing the need to develop a complex promo-codes system.
In a sentence, Hubbub is a site that lets you shop online with your local independent shops, like butchers, fishmongers and greengrocers. We're a small but growing team, and at the time I joined a surprising amount of the day-to-day administration of orders still had to be done manually. We could add discounts, refunds and special promotional items to orders if we wanted, but it was all done by adding individual line items to each order for the relevant amounts. Even free delivery was handled as a minus £3.50 line item to counteract the system-added charge.
To illustrate how inefficient this was: if you gave the name of a friend who recommended you when you signed up for an account, you were eligible for a free mystery goodie box. Our operations manager would have to watch for incoming signup alerts, notice that they'd given the name of a friend, then make a note for herself in her big Spreadsheet of Doom. Days, weeks or sometimes months later, when this customer placed an order, she'd have to go into that order and manually add the mystery box as a line item, update her Spreadsheet of Doom to flag that the promotion had now been used, and then contact the shop who supplied it to let them know they needed to pack it. Needless to say, this kind of busywork does not scale as your business begins to take off, and it was a huge distraction for the person concerned which stopped her working on much more important matters, like making toasties for everybody.
Our system did, however, have one or two very special hard coded promotions. It would spot orders by staff members and apply the relevant percentage discount. It would issue a particularly juicy cash discount to one individual who had given Hubbub a helping hand early on in life. And crucially, the office staff could configure it to send very basic emails back to HQ whenever particular customers checked out.
The actual brief I was given was simple: try to make life better for our operations manager. Up until that point, she had relied upon the combination of her Spreadsheet of Doom and these automatic emails, which we called "Checkout Alerts", to keep tabs on everything that needed doing. The first task was to sit down and look at all of the checkout alerts sent in a certain time period, categorise them by type and then count how many fell into each category. We were then able to focus our attention on the most common scenarios to get the most bang for our buck. It turned out that they tended to fall into a relatively small number of scenarios:
- There were a lot of "If this person orders from Jonathan Norris Fishmongers, then..." type messages
- Lots of our promotions had a minimum spend on them, like "spend over £40 to claim your free mystery goodie box"
- Many of them read like a note from Mission Impossible, saying "please delete this message once you've taken action"
- A few of the messages were better solved in an entirely different way, and we made some minor system changes to remove the need for these messages altogether
- Some of them were just general alerts that applied to all orders by that customer, and there was very little hope of ever being able to automate anything
First attempt: duct tape and special cases
From the start we recognised that the existing checkout alert feature had great potential to form the basis of any new system. We could beef it up a little bit by adding some more sophisticated logic to determine when it would trigger (rather than just always firing off emails on every checkout). Maybe we could add some extra flags that could allow them to self-destruct once they'd triggered. A little bit here, and a little bit there, and before you know it... well, sadly, as is often the case when you take an existing (very simple) feature and start adding bells and whistles, the result wasn't pretty. Lots of "if a minimum spend is set, then this", "if a shop constraint is set, then this" type special cases. It was all a little bit Heath Robinson and not very elegant, quickly becoming thoroughly unwieldy. But hey, sometimes it takes a bit of rapid prototyping to get the creative juices flowing, right?
Attempt two: beautiful symmetry
It wasn't long, however, before the light dawned, and we realised that everything we had been doing could be very nicely generalised. Essentially, each alert consisted of two things:
- A bunch of conditions that decided whether the alert was relevant
- A bunch of actions that should trigger when those conditions were satisfied
The first round of conditions are really obvious: it's just all of the things we've been talking about: "ordered from shop", "spent over £X", "this alert has been used < X times already by this customer". Sending an email to the office at the point of checkout then just became one of the possible actions that could trigger. Other actions that quickly got written were things like "add promotional item to order" and "add free delivery".
Seeing it this way made the code fall into place very quickly, in a much more manageable fashion than before. We had two abstract base classes, "CustomerAlertCondition" and "CustomerAlertAction", and then a whole series of inherited classes that encapsulated the logic of the various conditions and actions, including making decisions about whether they were relevant for any given alert that the operations manager had set up.
It also led to a really nice drag-and-drop user interface, where we can just drop on a few conditions, drag over a few actions, type in a few configuration values, and hey presto, it's all sorted.
Better than a kiss on the nose from a squirrel
Only now did the full potential of what we had developed become apparent. Having such a generalised system meant that it was now trivial to add new conditions and new actions. We quickly managed to do away with the old staff discount code, as well as the juicy credit, with a "customer has role 'staff'" condition, and "add percentage/cash discount" actions.
A day later and we were now able to start selling gift vouchers on the site, with a "total credit issued by this alert over all time is less than £X" condition. A nice little touch was that this could also provide a solution to our year's supply of free bacon recruitment bonus for developers, allowing it to give you 400g of bacon's worth of credit with each new order, but never exceeding the total value of the bonus.
Before we knew it, we'd solved a whole range of problems that had been stuck in our project backlog for months - issues that were never quite serious enough to get prioritised, but which at the same time really added value to the system.
Alerts can now send messages to the shops, they can add notes to the route plans for the drivers, they can have start dates and end dates. We even added the concept of "template alerts" that make it really easy to add pre-configured promotions to someone's account with just a couple of clicks. The system has opened up a whole range of possibilities that we'd just never conceived of before - for example, it would now be trivially easy for us to start selling delivery passes that gave free delivery until a certain date in the future.
When we relaunched our refer-a-friend scheme in November, now branded "Share the Love", we recruited the two kissing squirrels at the top of this post as our ambassadors. The deal is that if you give the name of an existing customer when you sign up to Hubbub, you'll get £10 off your first order that's over £40 in value, at which point your friend will also get £10 off their next order. Keeping tabs on this with the Spreadsheet of Doom would have been a complete nightmare, but with the new customer alerts it only took a tiny bit of extra work to automate the whole thing.
And best of all, we really did make life better for our operations manager.
- There is often value in rapidly prototyping something, as long as you don't spend too long pursuing dead ends
- Often a generalised approach is an order of magnitude more powerful than a bunch of special cases
- At the same time, it's good not to try and force every problem into your generalised framework - some are best addressed in a totally different way
- Our operations manager makes really nice lunch given half the chance
If you're interested in working on interesting problems like this, helping make the world a better place through the medium of code, we're currently recruiting for developers.