The TuitionKit Launch: 10 Lessons Learned

It’s been over a month since we launched TuitionKit. Here’s what I’ve learned thus far

At 2:00pm on Friday 10th April we launched TuitionKit, a GCSE and A-Level qualification elearning website. Fridays are terrible days to do anything, never mind launch a SaaS product, but that’s just how things fell in the end. My business partners Leon and Jonathan were waiting anxiously in their respective homes, waiting for a text message from me. I deployed the first live build just after lunch, and immediately texted them; “We’re live”. Soon afterwards Leon registered an account, to see what he could break.

As expected, things went terribly. I’d ballsed up the sign-in cookies big-time, thus on the server side the authorisation wasn’t working, resulting in Leon being repeatedly booted out of the system. I was stressed but not surprised; from bitter experience I know that launches are always shit – nerve-wrackingly, hand-wringingly, palm-sweatingly, teeth-grindingly shit.

But you always learn some new things. Here’s my takeaways:

Launch days are your most productive ones

Don’t know why this has just dawned on me, but you get more lines of production code written on a release day than any other day, mostly in the form of urgent patches. It’s interesting to know that panic makes me write more concise and solid code than calmness does.

Best practice my arse

The current so-called “best practice” for registration forms is having only one box for users to enter a password. Apparently you shouldn’t be asking them to repeat this password for confirmation; should they make a mistake they’ll use your website’s password recovery facility to retrieve/reset their password, and they’ll be happy. The raison d’être is that the more you streamline the registration process the more likely a prospective customer will complete it and sign-up…

…which is all well and good in principle, but unfortunately this idea turns out to be utter bollocks when put into practise. Over the first weekend we had over 50 sign-ups, and at least 25% of these had to resort to resetting their password. People make mistakes, but people don’t usually assume that it was them who made the mistake; their first instinct is to blame the system. So whilst all of these users have eventually managed to gain access to the system their patience has been ever-so-slightly strained. Take it from me, someone who has seen the evidence; put two password textboxes on your registration forms.

On the topic of confirmation inputs: chuck in a couple for their email address as well

Unfortunately, regular folk don’t seem to proof read. 10% of our registrations type their email address wrong, thus 10% of the user account table is a junkyard of misspelled email addresses. Most of the time users realise what they’ve done and register again immediately with the correct one. But many don’t, and those users are never coming back.

So I recommend you bung a confirmation box for email addresses on your registration form too. In fact, I’d go so far as to advise that if you like neatness and organisation – or have a phobia of clutter – never build anything with a user account table, because end-users don’t give a crap about the tidiness of your apparently wonderful database.

People still double-click stuff on web pages

Originally (before a few tweaks) every day I would receive a barrage of error log emails, telling me that database insert queries have failed, all of which are generated by people double-clicking on the “Register” button, submitting their details multiple times. I don’t currently have a good solution for this, but whilst the burden of website usability is on me this hasn’t stopped me from forming a grudge. With our customer-base heavily centred on 16 year olds I can’t help but feel that the youth of today ought to be savvy to how the web works, but apparently many of them aren’t, so that’s a wake-up call for me. My best weapon (so far) in preventing this double-click nightmare is a piece of JavaScript to lock the UI while the postback is occurring, but it’s hardly an elegant solution.

Have one customer contact email address – for everything – and operate a triage process

This was one thing I insisted on from the get-go, and I’m happy I did. All company correspondence goes to one helpdesk inbox, to which all of us have access. Jonathan is the main operator of this, and he answers what he can without bothering me. Any customer queries that have a technical edge are forwarded on to me, I remedy their problems, and then I implement extra functionality to combat the common technical issues that keep cropping up (proactive fire fighting, if there can be such a thing).

When outsourcing any work always ascertain where the freelancers are actually from – never assume, like I did

Many weeks ago Leon outsourced the creation of the marketing site, and I had to liaise with the freelancer to coordinate the migration of the WordPress assets onto our web host. We communicated via Skype messaging, and his written English was no worse than the average standard of the UK populous, so it never occurred to me that he was a foreigner; I assumed it was just some bloke that Leon already knew.

When my outsourcing buddy commented that the web host “wasn’t too good” I was baffled. He was referring to the speed of the FTP connection, which for me was blazingly fast; it’s an Irish web host, so in internet terms right on my doorstep. It was at this point that I spotted the tiny writing at the top of the Skype window: the freelancer, Waheed, was talking to me from Islamabad in Pakistan…and upon this discovery several things fell into place in my mind.

In an earlier conversation I had used the phrase “okey dokey” – which replaces “yes, that’s fine” in my vocabulary. Since I’d typed those words Waheed had begun using them at the start of every exchange we had, and other than being a little whimsical I hadn’t found this especially unusual. It was only after I discovered where he was situated that I realised he had assumed “okey dokey” was some kind of English salutation, and he had immediately started using it in place of “hello” in our conversations: “Okey dokey Drew, how are things?”

From that point on I heavily sanitised my messages; anything that was in the least bid idiomatic I replaced with the most vanilla English I could muster, which was painful. At one point I almost sent a message containing the phrase “done and dusted”, which I swiftly spotted and removed: I can’t begin to imagine how a non-native English speaker would interpret that expression. As for the connection speed thing: it turns out Pakistan has fatter internet pipes to the United States than Europe, meaning all the Pakistani freelancers I’ve come across use GoDaddy, which I do not.

Most publicity is good publicity

Haters are, inevitably, gonna hate. We were initially removing disparaging comments from the company’s Facebook and YouTube presences, expunging the usual melodramatic nonsense, such as: “Why aren’t you giving this away for free!?” and “Money greedy bastards”.

But after our first week we had a change of tack, and started to heed the advice of a colleague who’s also in the elearning biz; now we’re leaving the comments well alone. Every last remark, every tweet and retweet, no matter how insulting, is a discussion of our product: the more you have, the more people see, and the more people see, the more customers come to us. If people are going to hate the product they’ll hate it regardless of what other people are saying, and conversely if people are going to like it then the haters won’t convince them otherwise. By leaving all comments in play, both negative and positive, we’re slowly filling the internet with discussions about us.

Free-loaders never pay. Forget them

People who make a habit of consuming web content for free will never pay for stuff, period. Don’t try too hard to market to them in an attempt to encourage upgrades to paid plans, because 99% of them will never pay for stuff (I know this because I’m one of those free-loaders, and it’s difficult to make money off tightwads like me).

Sherlock your analytics

By following the flow of the acquisition funnel through the marketing site we discovered that a typical visitor’s journey began on the home page, followed by a swift navigation to the pricing page, then finally to the registration page or away from the site. We had a massive drop-out rate from the pricing page, higher than should be expected, so we deduced that we’d priced far too high. After one week we took a gamble and reduced our prices, making sure to refund our prior sign-ups in line with the changes. Since then we’ve had an upsurge in registrations.

Test your traction channels

In our case:

  • Google Adwords isn’t effective for us at all, with an 80% bounce rate
  • Radio adverts have been utterly pointless, with zero referrals
  • Flyers have been reasonably useless
  • Newspaper adverts have gone practically unseen
  • Facebook advertising is working a treat

Following these broader methods we’re now purely targeting the Facebook ads and social sharing, and I’d like to give a spot of Twitter advertising a try. We continue to burrow into our Facebook referral demographics, but so far our conclusions are: children Like stuff, parents Share it. For us, targeting a mix of the two is the answer.

AppHarbor: Horizontal vs. Vertical Scaling

Continuing my experiments with AppHarbor, this time it’s all about scaling

As a hosting company AppHarbor do a splendid job of simplifying the process of server configuration, with a minimalist approach that appealed to me instantly when I first saw it. But I’m assuming I’m not the only one who’s a little perplexed by the server scaling options with which one can tinker. Having used AppHarbor for a number of years for prototypes and modest SaaS projects I openly admit that I’ve never had to delve too deeply into the complexities of worker process scaling – the most I’ve done up to now is add an extra worker or two, having made safe assumptions about the definition of “horizontal scaling” and its effects.“Vertical scaling”, however, remained an enigma to me, the question is: why and when would it be useful?

As 2015 is turning out to be a big year for my SaaS rollouts it was time I got myself schooled. A quick email to AppHarbor’s support guys garnered the following explanation:

You’d usually scale out to multiple workers (i.e. instances of your app running multiple servers) to increase your throughput, parallel processing and/or to increase availability of your application. Vertical scaling is usually beneficial when you need more worker resources per worker – for instance, you might want to process a CPU-intensive workload faster, and 2-4x the CPU resources would help you do that. Vertical scaling is described in more detail in this blog post which also provides common use cases for choosing this over (or in combination with) horizontal scaling.

Having read this explanation and the recommended blog post, and using my own SaaS projects as an example, I have surmised the following:

  • TuitionKit has a mundane computational complexity (mostly regular CRUD operations) but has a large user volume. An ongoing increase in user sign-ups will require more worker processes (horizontal scaling) to cope with the increase in page requests. As the system doesn’t do anything overly complex over and above serving pages and querying databases the horizontal scaling should cover most growth scenarios.
  • RopeWeaver involves serious number crunching for each company customer at a set number of times per day. The periodic nature of the computationally-heavy calculations means that they are done on a background worker process, removing the burden from the main web server thread, which does make it easier to manage the scaling. Ultimately most of the work is to be migrated to the database tier and done in T-SQL stored procedures, which will remove the burden from the web server entirely. Whilst it is obvious that careful management of vertical scaling is required, as a decision support system intended for use in factories and warehouses (where most users tend to be logged in for extended periods of time, e.g. an 8 hour shift) I fear a horizontal scaling aspect will also come into play. RopeWeaver poses a tricky server resource conundrum which I’ll have to experiment with further before I can come to any proper conclusions.

A Custom “RequireHttpsAttribute” for Use With AppHarbor

Further to my previous article, here’s how I handle one of the common code quirks of AppHarbor web hosting

In my article on AppHarbor gotchas I promised that I would detail my own method for handling the critical issue that the [RequireHttpsAttribute()] simply won’t work on their network of load balanced servers. By way of introduction I’ll quote from that article:

You’ll have to make several concessions in your code to the load balancer, accepting that it needs to do its thing in a certain way. Because of the manner in which web requests get internally routed through AppHarbor’s system of servers you’ll need to stop using the controller action attribute [RequireHttpsAttribute()], as this won’t work correctly; once the request has passed beyond the edge server it is stripped of its HTTPS distinction, and thus internally the controller action can’t be identified if this attribute is attached to it.

The solution – well, my solution at least – is a custom attribute with which you can decorate your secured actions, which I’m calling [RequireHttpsRemixAttribute()]. You attach it in exactly the same way as the regular HTTPS-enforcement attribute, but inside it’s doing some subtly different checks to confirm the specifics of the HTTP request. Here’s the code:

/// <summary>
/// Custom implementation of the RequireHttpAttribute, serving 2 purposes:
/// 1) Allows debugging by ignoring the SSL requirement on localhost
/// 2) AppHarbor load balancing SSL corrector, see URL: http://support.appharbor.com/kb/tips-and-tricks/ssl-and-certificates
/// </summary>
[AttributeUsage(AttributeTargets .Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequireHttpsRemixAttribute : System.Web.Mvc.RequireHttpsAttribute
{
	#region " Public methods "

	public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
	{
		if (filterContext == null)
		{
			throw new ArgumentNullException ("filterContext");
		}
		else
		{
			if (filterContext.HttpContext.Request.IsSecureConnection)
			{
				//Connection is direct SSL                      
				return;
			}
			else if (string.Equals(filterContext.HttpContext.Request.Headers["X-Forwarded-Proto"], Uri.UriSchemeHttps, StringComparison .InvariantCultureIgnoreCase))
			{
				//Connection is SSL beyond the load balancer level
				return;
			}
			else
			{
				HandleNonHttpsRequest(filterContext);
			}
		}
	}

	#endregion

	#region " Protected methods "

	/// <summary>
	/// Could use the outer "If" statement to factor in SSL ignoring on specific testbed environments (ie. by specifying server names)
	/// </summary>
	protected override void HandleNonHttpsRequest(System.Web.Mvc.AuthorizationContext filterContext)
	{
		if (!filterContext.HttpContext.Request.Url.Host.Contains("localhost"))
		{
			if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.InvariantCultureIgnoreCase))
			{
				throw new InvalidOperationException("The requested resource can only be accessed via SSL");
			}
			else
			{
				string url = string.Format("{0}://{1}{2}", Uri.UriSchemeHttps, filterContext.HttpContext.Request.Url.Host, filterContext.HttpContext.Request.RawUrl);
				filterContext.Result = new System.Web.Mvc.RedirectResult (url);
			}
		}
	}

	#endregion
}

You’ll notice, if you read through the AppHarbor help page to which I’ve linked in the class’s comments, that this code is based on the AppHarbor team’s recommended implementation of the custom attribute. I have, however, spiced up their example with a few changes of my own, mainly the splitting of HTTPS and non-HTTPS checking into separate functions. The HandleNonHttpsRequest function allows you to run the same code on your localhost dev environment (which probably won’t have SSL, and thus would fail the previous checks) and also specify additional testbed server names (should you need that layer of staging before pushing the code to AppHarbor).

And there you have it; short and sweet.