A little while ago we announced reaching 10,000 paying customers. It was, without a doubt, a huge milestone and we wanted to celebrate. Not only did we want to celebrate, but we wanted to celebrate with our customers who helped to make it all happen.
But how do you do that with customers spread out all over the world?
After some brainstorming we decided it would be pretty fun to let our customers share their company logos on our 10k customers page. Of course, we weren't given much time - we had about two weeks to develop, test and launch something. So, we started working on the feature right away.
We started by listing our general requirements:
- allow only paying customers to upload;
- enable customers to upload a small logo and their URL;
- provide them with a quick, easy way to share it on twitter;
- give ourselves the ability to moderate the logos being added.
After having a conversation with Kyle, the designer on the project, I got amped up to see if we could make this happen. What wasn’t to love about the project? It was a no-brainer to take this on and drive this feature to completion.
Existing or Standalone?
The first thing I had to figure out was if this was going to be built into one of our existing Olark services or as a standalone setup.
There were pros and cons to both: a standalone would be easy testing wise but there would be significant time spent getting infrastructure setup. Adding it to one of our existing services meant we got access to a lot of customer information and no infrastructure setup, but then we had to do more testing to make sure it wouldn’t introduce any new bugs or somehow take down the service.
Ultimately the requirement to be a paying customer drove me to integrate with an existing Olark service. Our existing customer database allowed us to quickly qualify whether customers were paying or on a free plan. It also meant that we could build a moderators panel into our existing backend dashboard instead of building an entirely new dashboard for it. Essentially, we killed two birds with one stone.
Handling Scale
Now that I knew where it would live, I had to figure out how to handle the potentially large number of images that our customers would share with us, especially since this was my first time building something like this, and the first time we had ever attempted something like this at Olark.
Initially we considered letting customers share a link to their image, but that posed some problems, namely image size. We wanted the logos to all be the same size so the page had some uniformity to it, and links just didn’t provide a good way of ensuring that.
Instead, we opted for Amazon’s S3 service. It’s low cost, fast, has a great API with wrappers in a lot of languages, and - on top of that - we could turn on Amazon’s CloudFront service and have a CDN backing us up. By having our customers upload images, we had a lot of control over image compression, dimensions, and making sure we were actually getting images sent to us in the best way possible to show them off as they deserve.
The logo submit form ultimately looked like this:
Here’s where things became a bit tricky...
Storing Data
There was a bit of information that we needed to store and deliver to the 10k customers page; specifically the stored image URL and the domain that went with that logo.
Seeing as this features API was living in an existing Olark service, I started down the path of just adding the data to our existing models. While in theory this was the right idea, practically it ended up being a headache. Mostly it ended up not working because where we wanted to store the data, was not optimized to pull out a large dataset. That meant the idea of storing it was a dead end and on top of that, we were running out of time until the page launched.
I began discussing the problem with Brandon, another engineer at Olark, and eventually we thought, why not just use S3 for storing the image metadata as well? We could store the data as a JSON file. Since the file is just text, we could update it easily by parsing it, manipulating it as an object then saving it back out.
In addition to that, we could provide the URL of the JSON file on the 10k customers page and parse it in the client side JavaScript to display the images and links. While there could be an issue where records might potentially be lost from the file while being edited by two threads at the same time, it solved some other bigger issues like how we’d deliver all the metadata quickly to whatever needed it and frankly those issues alone would easily put us beyond our deadline.
Since this wasn't mission critical data, it was an acceptable risk to take. After all, we didn’t limit the number of times you could upload an image, so if someone had an issue they could just upload again. It wasn't ideal, but it never blocked a customer from adding their logo, which was our main goal.
A Solution for Moderation
We had figured out how we’d let our customers share their logos. However, since this is the Internet, we needed a way to moderate what our customers were adding.
There were some basic initial bonuses we got from integrating with one of our existing services. We could enforce that a user was logged in, and additionally that they were a paying customer. That would deter most attempts at ruining the fun, although there was still a chance an image could get corrupted and things would get messed up, or a customer would change their mind and want their logo removed.
Still, it required us to build a tool that let us moderate. Again we had an internal tool already that I was able to build the moderation page in. We could get the list of JSON the same way we would on the 10k customers page. The only thing we really needed was a way to remove an image. So, we made it possible for any moderator to remove the object in the JSON with a button click. It was quick, it let us keep control, and the implementation was simple.
A Solution for Tweeting
Our last feature was relatively simple, but the one that helped drive the participation up.
Each customer who uploaded their logo needed to tweet at the end and share the 10k customers page. Due to the nature of the Twitter share button, and how we wanted to display it, we had to roll our own version of the share button. The implementation was relatively straight forward however; it involved popping up a new window, and directing the popup to Twitter’s share page. Our code provided the tweet parameters, like message, hashtag, etc in the URL and Twitter automatically applies that to the tweet box.
Here's the snippet of our React code that did that for us:
React = require('react')
TWEET_URL = "https://olark.com/10000"
TWEET_TEXT = "I'm one of @Olark's 10,000 paying subscribers. Are you? Tweet
your logo now!"
TWEET_HASHTAGS = "Olark10K"
TWEET_RELATED = "olark,olarksupport"
SHARE_URL = "https://twitter.com/share?url=#{TWEET_URL}" +
"&text=#{TWEET_TEXT}&hashtags=#{TWEET_HASHTAGS}&related=#{TWEET_RELATED}"
TweetButton = React.createClass
propTypes:
isUploading: React.PropTypes.bool
uploadFailed: React.PropTypes.bool
success: React.PropTypes.func
submit: React.PropTypes.func
componentDidUpdate: ->
if @props.uploadFailed and @_popup
@_popup.close()
clearInterval(@_closeInterval)
if @props.isUploading and !@_popup
@_activatePopup()
_activatePopup: ->
@_popup = window.open(
SHARE_URL,
'olark-twitter-popup',
'width=700,height=500,toolbar=0,menubar=0,location=0,status=0,' +
'scrollbars=0,resizable=0,left=0,top=0'
)
unless @_closeInterval
@_closeInterval = setInterval =>
if @_popup.closed
clearInterval(@_closeInterval)
@_handleTweetSuccess()
@_closeInterval = null
, 500
_handleClick: (event) ->
event.preventDefault()
@props.submit()
_handleTweetSuccess: ->
try
@props.success()
catch error
if console
console.error('Handler for success method might be missing.', error)
render: ->
<a href={SHARE_URL} className="tweet button" onClick={@_handleClick}>
Tweet to Submit
</a>
module.exports = TweetButton
Having our customers tweet accomplished a few things: other customers would see they could add their logos, the customer who tweeted it got to have their logo shown and site linked on our site, and it helped share our milestone a bit further.
Working on Deadline
As I was writing this feature there was a lot of problem solving along the way and course correcting when I hit a dead end. Due to the short timeline and clear goals it became a necessity to drop certain things to make sure we hit our objective. Finishing came down to the wire, doing final testing the day before we announced the milestone. As we buttoned up the last bits it became a waiting game to really see if it would be a hit.
Turns out the announcement was a huge success, and we helped our customers celebrate our milestone with us. While the timeline was short and there was a lot to implement, having constraints that forced us to make decisions quickly helped us hit our deadline. It also facilitated some, perhaps unusual solutions to the problems at hand. Had our timeline been a bit further out, those solutions likely would have looked different, but not necessarily better. While you can certainly innovate with an open ended project, sometimes applying boundaries or constraints helps get you there in a not so expected path.
The Results
The day of the announcement came and within a short period of our newsletter going out we had hundreds of customers logos. That was what really got the ball rolling, and as mentioned earlier each logo added sent a tweet linking back to the page. Now we had tweets from all of our customers helping to spread the announcement and get even more customers to add their logos. It was really awesome to watch all the new logos being added and seeing our customers excited to share in the milestone.
By the time the dust settled, we had almost 6,000 pageviews for our landing page, 500 logos submitted, and well over 500 tweets using our campaign hashtag, #Olark10K.
Here's what the final product looked like on our landing page:
Success! So here’s to solving problems, innovating, and another 10,000 customers.