Seth Godin: Drawing a line in the sand

Links , Startups

There are two real problems with this attitude:

First, drawing lines. Problems aren’t linear, people don’t fit into boxes. Lines are not nuanced, flexible or particularly well-informed. A line is a shortcut, a lazy way to deal with a problem you don’t care enough about to truly understand.

Most of all, drawing a line invites the other person to cross it.

Second, the sand. Sand? Really? If you’re going to draw a line, if you’re truly willing to go to battle, you can do better than sand.

  • Seth Godin

That saying has always bugged me too, and we never draw lines here at Flybase.

Uncovering the why behind customer questions


Paulina Welnic:

When it comes to customer support, great product knowledge and technical skills go to waste if we’re answering the wrong question.

Customers reach out to you when they hit a roadblock in using your product and getting their job done. So getting your customers answers not just quickly, but correctly, is key.

All or something



One of the most pervasive myths of startup life is that it has to be all consuming. That unless you can give your business all your thoughts and hours, you don’t deserve success. You are unworthy of the startup call.

This myth neatly identifies those fit for mission: Young, without obligations, and few if any extra-curricular interests. The perfect cannon fodder for 10:1 VC long shots. They’re also easier to rile up with tales of milk and honey at the end of the rainbow, or the modern equivalents, “compressing your working life into a few years” and “billon-dollar waves”.

But running your life in perpetual crunch mode until the buy-out or bullshit-IPO fairy stops by your door is not surprisingly unappealing to lots of people.

The problem is that most “exciting new company” lore is intermingled with that of Startup Culture™. This means it’s hard to find your identity when it doesn’t match the latest company write-up of How Those Crazy Kids Turned VC Millions Into Billions!!!

Most people will look at that and say that’s not me. I don’t have 110% to give. I have a family, I have a mortgage, I have other interests. Where’s my place in the startup world if all I have to give is 60%? What can putting in part-time give?

DHH wrote this five years ago and just republished it. It’s a worthwhile read for anyone starting up something.

Betakit's Open Letter from the Canadian Tech Community


The Canadian tech community comprises many different nationalities, religions, sexual orientations, gender identities, mental and physical abilities, and perspectives. We believe that this diversity is a source of strength and opportunity.

On this topic, we are united.

Canadian tech companies understand the power of inclusion and diversity of thought, and that talent and skill know no borders. In choosing to hire, train, and mentor the best people in the world, we can build global companies that grow our economy. By embracing diversity, we can drive innovation to benefit the world.

The 21st century will be driven by pluralistic economies powered by pluralistic societies.

This is a belief founded upon personal experience for many in our community. Many Canadian tech entrepreneurs are immigrants, are the children of immigrants, employ and have been employed by immigrants.

As connected economies, decisions by the United States can directly impact every business north of the border. The recently signed Executive Order to block entry of citizens from seven countries has already impacted several in our community. As a community, we are all affected.

As a community, we stand together in opposition to the marginalization of people based on their birthplace, race, or religion.

The Canadian tech community supports Prime Minister Justin Trudeau’s message that Canada will and must remain inclusive to all nationalities. We also stand directly opposed to any and all laws that undermine or attack inclusion, and call on Prime Minister Trudeau and our political leaders to do the same.

We’ve signed the open letter, one of 1297 other signatures on the list and 1 of the 881 Canadian companies who have signed at the time I shared this post.

Build a real-time SMS group chat tool with Flybase, Twilio, Node.js and Heroku

Code , Twilio

This is an update to our original Post about building a group chat tool. Some things remain the same, but we've updated the library to Flybase (finally) and also removed the ngrok section for Heroku.


Last November, I went to a conference with several co-workers, and we wanted to keep everyone organized so we could keep track of what our plans were.

We set up a group chat system that let one of the members send an SMS and everyone else get the message, then if someone replied, we’d all see the reply.

That was handy, and today, I’m going to show you how to build a similar web app. The app will consist of a simple control panel where you can manage who is part of a group, and a backend that will handle incoming and outgoing text messages and route them to the proper group members.

You will also be able to send and receive messages from a page on the site in real-time, for when you may not have you phone on you but want to send a message to the group, and vice versa.

We’ll use Flybase to handle the data-storage and real-time aspects of the app, Twilio to handle the actual SMS work, and Node.js for the system itself.

We’re going to build this particular app for one single group, but it wouldn’t be hard to extend it for multiple groups.

Then finally, we’ll host this app on Heroku as a free app.


We’ll be using a few tools to build this app. You’ll want to have these set up before you continue on:

  • Twilio: To send and receive SMS messages.
  • Flybase: A real-time database API. We’ll be using it to store a record of incoming and outgoing messages, and also people who are part of our group.
  • Node.js: A platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications.
  • Heroku: A handy hosting platform for getting your projects up and running quickly, especially handy when combined with Flybase and Twilio.

Node.js will be the backend portion of our app, it’s where we will build our listeners for Twilio to talk to when ever we send or receive a text message.

Flybase is a real-time app platform and will be our datastore of choice for our app, it will be used to manage who is a member of a group, and to store incoming and outgoing messages and who they came from. If you haven’t already, Sign up for a free Flybase account now, then create a new app from inside your dashboard. You’ll use this app for your group chat system.

Twilio is our every handy phone API, which lets us build services like a group chat app, or even a call center. Don’t have a Twilio account? yet Sign up for Free

Getting Started

We first need to set up our Node.js app.

Besides the Twilio and Flybase modules, we’ll be using the Express framework to set up our node web server to receive the POST request from Twilio so we’ll need to install the express package. We’ll also be using the body-parser module so we are going to install that as well.

Let’s create our package.json file:

  "name": "group-chat",
  "version": "0.0.1",
  "description": "SMS Group Chat powered by Flybase, Twilio and Node.js",
  "main": "app.js",
  "repository": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  "keywords": [
  "author": "Roger Stringer",
  "license": "MIT",
  "dependencies": {
    "body-parser": "~1.16.0",
    "ejs": "~2.5.5",
    "express": "~4.14.0",
    "flybase": "^1.8.2",
    "less-middleware": "~2.2.0",
    "method-override": "~2.3.7",
    "moment": "~2.17.1",
    "node-buzz": "~1.1.0",
    "twilio": "~2.11.1"

Save this file, and from the terminal run the following command:

npm install

This will create a node_modules folder containing all of the modules we want to use.

Let’s set up our folder structure, create a folder called views, this is where we will keep our frontend.

Now, create a folder called public, this will host our static files, inside that folder, create a css folder and a js folder, we’ll come back to these later.

The first file we want to create is config.js, this will hold our configuration information:

module.exports = {
    // Twilio API keys
    twilio: {
        sid: "ACCOUNTSID",
        token: "AUTHTOKEN",
        from_number: "YOUR-NUMBER"
    flybase: {
		api_key: "YOUR-API-KEY",
		app_name: "YOUR-FLYBASE-APP"
    un: 'admin',
    pw: 'password'

This file is for our configuration, we can access anything in here at anytime by referencing the file and calling the keys, for example, to get our Flybase API Key, we would call:

var config = require('./config');
console.log( config.flybase.api_key );

Replace ACCOUNTSID, AUTHTOKEN and YOUR-NUMBER with your Twilio credentials, and a phone number in your Twilio account that you’ll be using.

Then, replace YOUR-API-KEY, and YOUR-FLYBASE-APP with your Flybase API Key to use.

At the beginning of our app.js file we’ll need to require express and initialize it into a variable called app. We’re also going to use the bodyParser middleware to make it easy to use the data we’ll be getting in our POST request.

Create a new file called app.js and require the twilio, express and flybase packages:

var express = require('express');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var path = require('path');
var config = require('./config');

var app = express();
app.set('views', path.join(process.cwd(), 'views'));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({	extended: true	}));
app.use(express.static(__dirname + '/public')); // set the static files location /public/img will be /img for users

var port = process.env.PORT || 8080; // set our port

var twilio = require('twilio');
var client = twilio(config.twilio.sid, config.twilio.token );

var flybase = require('flybase');
var messagesRef = flybase.init(config.flybase.app_name, "messages", config.flybase.api_key);
var groupRef = flybase.init(config.flybase.app_name, "groups", config.flybase.api_key);

Flybase uses collections to organize data inside apps, so one app could have several collections. If you’re familiar with relational databases, this is the equivalent of a table.

We’ll be using two collections for our project, one will contain messages, the other will contain groups. With that in mind, we’ve created two different references to our Flybase app, one for messages, and one for our group.

This is the start of our app, next we’ll build our web interface to manage group members and also allow for sending and receiving messages.

After that, we’ll build our Twilio interface and you’ll have a fun app to play with.

Sending and receiving texts

We’ll need to add a few things to send and receive texts, our first step is to add a listener for Twilio.

Twilio uses webhooks to let your server know when an incoming message or phone call comes into our app. We need to set up an endpoint that we can tell Twilio to use for the messaging webhook.

We’re going to add a route for /message that responds with some TwiML. TwiML is a basic set of instructions you can use to tell Twilio what to do when you receive an incoming call or SMS message. Our code will look like this:

//	listen for incoming sms messages'/message', function (request, response) {
	var d = new Date();
	var date = d.toLocaleString();

	groupRef.where( {"memberNumber":request.param('From')} ).limit(1).on( "value", function ( data ){
		if( data.count() ){
			data.forEach( function( snapshot ){
				var member = snapshot.value();
					sid: request.param('MessageSid'),
					tstamp: date,
	var resp = new twilio.TwimlResponse();
	resp.message('Message received.');
	response.writeHead(200, {

This will listen for any incoming sms messages and store them inside your Flybase app, specifically inside the messages collection.

As part of storing the message, we perform a look up to find the groups member with the same phone number the message was sent from, we then use this lookup to verify the member was part of the group, and also to get the member’s name.

If no member was found, then no message gets sent.

Once a message has been received, we use the Twilio node library to initialize a new TwimlResponse. We then use the Message verb to set what we want to respond to the message with. In this case we’ll just say “Message received”.

Then we’ll set the content-type of our response to text/xml and send the string representation of the TwimlResponse we built.

Listening For Changes

As part of our app.js code, we also want to add some asynchronous listeners to listen for changes to our Flybase apps.

//	when a new message is added to the Flybase app, send it via Twilio...
messagesRef.on("added", function (data ){
	var snapshot = data.value();

groupRef.on("added", function ( data ){
	var snapshot = data.value();
	var msg = snapshot.memberName + ' has joined the group';
		sid: "",
		tstamp: new Date().toLocaleString(),

groupRef.on("removed", function ( data ){
	var snapshot = data.value();
	var msg = snapshot.memberName + ' has left the group';
	//	send broadcast that a group member has been removed
		sid: "",
		tstamp: new Date().toLocaleString(),

//	broadcast a message to the group
function sendMessage( group_number, from_name, from_number, message ){
	var msg = from_name + ": " + message;
//	loop through the group members and get list of people to message:
	groupRef.where( {"memberNumber":{"$not":from_number}} ).on( "value", function ( data ){
		if( data.count() ){
			data.forEach( function( snapshot ){
				var member = snapshot.value();
				client.sendMessage( {
				}, function( err, data ) {

We’ve set up three asynchronous listeners, one for the message collection, which listens for any messages being added to it, and when it receives a notification of a new message, calls our sendMessage function and sends the message on to the other members of the group.

The other two asynchronous listeners are for our groups collection, the first one, listens for any new members being added to a group and then sends an announcement that the member has joined the group.

The last listener will listen for any members being removed from a group, and sends an announcement that the member has left the group.

Finally, our sendMessage function is used for sending messages on to the other group members, it will perform a query to return all members of the group, excluding the person who sent the message, and send the message onto each member.

Messages will appear formatted with the member’s name followed by the message.

	John: How about pizza after work?

Finally, let’s set our server to listen on port 8080, and tell it what to do when we view it from a brower:

// frontend routes =========================

// Create basic auth middleware used to authenticate all admin requests
var auth = express.basicAuth(config.un,;

// route to handle all frontend requests, with a password to protect unauthorized access....
app.get('*', auth, function(req, res) {
	res.render('index', {

var server = app.listen(port, function() {
	console.log('Listening on port %d', server.address().port);

This is the backend portion of our group chat app it listens for incoming text messages, stores them in our Flybase app, and then sends broadcasts to the other members of the group.

Now, we need to build our control panel, where the admin can manage group members, and also send and receive messages.

We’ll build that now.

Managing Your Group

We’re going to build a simple web interface to manage our group members.

The data we store for our group members will consist of the following three pieces of data:

  • Group phone number (the Twilio number we stored in the twilio_number variable in the Getting Started section)
  • Member name
  • Member phone number

We’ll also display a basic chat box which will let our admin send messages and see what messages are being sent.

First, let’s create our view, in the /views folder, create a file called index.ejs:

<!doctype html>
	<link href='//,300italic,400italic&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
	<link rel="stylesheet" type="text/css" href="//" />
	<link rel="stylesheet" href="//">
	<link href="//" rel="stylesheet">
	<link rel="stylesheet" type="text/css" href="/css/style.css">

	<script src=""></script>
	<script src=""></script>
	<script src=""></script>
	<script src="/js/group.js"></script>

	<title>Group Chat, powered by Flybase and Twilio</title>
	<div class='container'>
		<div class="row">
			<div class="col-md-6">
				<h3>Group Members</h3>
				<div id="group_wrapper"></div>
				<hr />
				<h2>Add new member</h2>
				<div class="well">
					<form id="group_form" method="post" accept-charset="utf-8" class="form-inline">
						<div class="form-group">
							<div class="input-group">
								<div class="input-group-addon"><i class="fa fa-pencil"></i></div>
								<input type="text" class="form-control" id="name" name="name" placeholder="name">
						<div class="form-group">
							<div class="input-group">
								<div class="input-group-addon"><i class="fa fa-mobile"></i></div>
								<input type="tel" class="form-control" id="phone" name="phone" placeholder="+11112223333"/>
						<button type="submit" class="btn btn-primary">Save</button>
			<div class="col-md-4 col-md-offset-1">
				<div id="chatBox" class='chat'>
					<header>Chat Log</header>
					<ul id='messagesDiv' class='chat-messages'></ul>
						<form id="msg_form" method="post" accept-charset="utf-8" class="form-inline">
							<input type="text" id="messageInput" placeholder="Type a message..." />
//			inititialize our Flybase object
			var myGroupManager = new groupManager( "<%= api_key %>", "<%= app_name %>", "<?%= group_number %>");

This will display out control panel, which will be split into two panes, the left side for viewing group members, the right side for viewing the chat log.

At the bottom of the page, we’re initializing our groupManager class. We’ll create that file shortly.

Next, let’s create our style sheet. In the public/css folder, create a file called style.css:

.chatWindow{float:left;margin:20px;border:1px solid #000;width:300px;background:#e5e5e5;border-radius:5px}
.tstamp{font-size:9px;padding:2px;margin-bottom:10px;border-bottom:1px dotted #666;color:#666}
.messageForm textarea{float:left;width:220px;margin:5px}
#chatBox{background-color: #f8f8f8;background: rgb(229, 228, 228);margin:10px;}
.hide {display: none; }
.chat {font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;border-radius: 3px;-webkit-box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.2);box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.2);background-color: #dfe3ea;border: 1px solid #CCC;overflow: auto;padding: 0px;font-size: 18px;line-height: 22px;color: #666; }
.chat header {background-color: #EEE;background: -webkit-gradient(linear, left top, left bottom, from(#EEEEEE), to(#DDDDDD));background: -webkit-linear-gradient(top, #EEEEEE, #DDDDDD);background: linear-gradient(top, #EEEEEE, #DDDDDD);-webkit-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.9), 0px 1px 2px rgba(0, 0, 0, 0.1);box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.9), 0px 1px 2px rgba(0, 0, 0, 0.1);border-radius: 3px 3px 0px 0px;border-bottom: 1px solid #CCC;line-height: 24px;font-size: 12px;text-align: center;color: #999; }
.chat input {-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;-webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);border-radius: 3px;padding: 0px 10px;height: 30px;font-size: 18px;width: 100%;font-weight: normal;outline: none; }
.chat .chat-toolbar {background-color: #FFF;padding: 10px;position: relative;border-bottom: 1px solid #CCC; }
.chat .chat-toolbar label {text-transform: uppercase;line-height: 32px;font-size: 14px;color: #999;position: absolute;top: 10px;left: 20px;z-index: 1; }
.chat .chat-toolbar input {-webkit-box-shadow: none;box-shadow: none;border: 1px solid #FFF;padding-left: 100px;color: #999; }
.chat .chat-toolbar input:active, .chat .chat-toolbar input:focus {color: #1d9dff;border: 1px solid #FFF; }
.chat ul {list-style: none;margin: 0px;padding: 20px;height: 200px;overflow: auto; }
.chat ul li {margin-bottom: 10px;line-height: 24px; }
.chat ul li:last-child {margin: 0px; }
.chat ul .chat-username {margin-right: 10px; }
.chat footer {display: block;padding: 10px; }
.chat footer input {border: 1px solid #ced3db;height: 40px; width:75%;}

Now, let’s move onto the brains of our system, inside the public/js folder, we’ll create a file called group.js:

var groupManager = function(api_key, app_name, group_number) {
//	store the group number
	this.group_number = group_number;
//	reference to our messages collection...
	this.messagesRef = new Flybase(api_key, app_name, "messages");

//	reference to our group collection...
	this.groupRef = new Flybase(api_key, app_name, "groups");

	this.group_members = [];

This is the first part of our groupManager class, so far we’ve told it to start up two Flybase references, one called messagesRef and one called groupRef, we also stored our group number as a variable called group_number.

Now, let’s set up our actions:

groupManager.prototype.start = function(){
	var _this = this;

//	list group members if any
	this.groupRef.on("value", function( data ){
		if( data.count() ){		
			data.forEach( function( snapshot ){
				var member = snapshot.value();
				_this.group_members[member._id] = member;				

//	listen for new members being added
	this.groupRef.on("added", function( snapshot ){
		var member = snapshot.value();
		_this.group_members[member._id] = member;

//	save new group member to our app
	$("#group_form").submit( function(e){
		var member = {
			'groupNumber': _this.group_number,
			'memberName': $("#name").val(),
			'memberNumber': clean_phone( $("#phone").val() )
		_this.groupRef.push( member );
		return false;

//	listen for members being removed
	$('div').on('click','a.delete', function(e){
		var _id =;
		return false;

	this.groupRef.on("removed", function( snapshot ){
		var member = snapshot.value();
		_this.group_members[member._id] = undefined;

//	list any existing chat message
	this.messagesRef.on('value', function (data) {
		if( data.count() ){
			data.forEach( function(message){				
				_this.displayChatMessage(message.value() );
//	listen for incoming chat messages
	this.messagesRef.on('added', function (data) {
		var message = data.value();
		_this.displayChatMessage( message );

//	listen for outgoing chat messages
	$('#msg_form').submit( function(e){
		var message = {
				"tstamp": new Date().toLocaleString(),
				"fromName": "Admin",
				"fromNumber": "",
				"message": $('#messageInput').val(),
				"fromCity": "",
				"fromState": "",
				"fromCountry": "",
				"groupNumber": _this.group_number
		_this.messagesRef.push( message );
		return false;

Our start function sets up our asynchronous listeners, as well as listeners for form submissions and members being deleted by pressing the delete button.

If a group member is added, then the member will be added to the groups collection and a notification will be sent to the other members of the group. The listing of group members will also show the new member.

If a person is removed, then they will vanish from the list, and a message will be sent to the remaining group members.

The other side of our groupManager class is the actual chatting side of things, when the admin types in a message, it will get broadcast to the other group members, at the same time, when another group member sends a message, the admin will see the message in the chat box.

We have two functions left, one is to display all members of a group, and the other displays chat messages.

For our groups, we stored information in a class-wide variable called group_members, this lets us quickly add, update or remove members as we receive notifications about it.

//	Display group members
groupManager.prototype.displayGroup = function(){
	for (var i in this.group_members ) {
		var member = this.group_members[i];
		if( member !== undefined ){
			var html = '';
			html = '<span>'+member.memberName+' ( ' + member.memberNumber + ' )</span> <a href="#delete" class="delete" id="' + member._id+'">[remove]</a>';
			$('<div/>').prepend( html ).appendTo($('#group_wrapper'));

Our last function, displays each chat message as it is received:

//	Display chat messages
groupManager.prototype.displayChatMessage = function( message ){
	var _this = this;
			$("<strong class='example-chat-username' />").text(message.fromName+': ')
			).appendTo( $('#messagesDiv') );
	$('#messagesDiv')[0].scrollTop = $('#messagesDiv')[0].scrollHeight;

One last thing to do, Let’s fire up our app:

node app.js

We’ve told our app to run on port 8080, so if you go to your web browser and type in http://localhost:8080/ you should see your call center.

Hosting it on Heroku

Heroku is great for making server configurations easy and painless. We can build faster and worry about the things that matter to us instead of trying to configure our own servers. This works perfectly with our philosophy here at Flybase and lets us build things quickly.

Let’s look at how we can deploy our group chat app to Heroku in mere seconds.

Go ahead and go to and create your free account. At first glance, the dashboard is incredibly simple and user friendly.

Next, you’ll want to install the Heroku Toolbelt. The Heroku toolbelt will give us access to the Heroku Command Line Utility.

After we install the Toolbelt, we’ll have access to the heroku command.

Now, you’ll want to perform the following operations:

  1. git init inside the folder you created your group chat app in to create a new git repository.
  2. heroku login to log into Heroku.
  3. heroku create to create the application within Heroku.
  4. git push heroku master to push your group chat repository to Heroku.
  5. heroku ps:scale web=1 to tell Heroku to create a dyno (a worker, to respond to web requests).
  6. heroku open to open Safari at your new, custom URL.

And that’s it. Your app is now running on Heroku.

Assigning your group chat to a phone number in Twilio.

Now, we want to go back to our Twilio account, and open the phone number we were using to send messages.

When you created your app on Heroku, it gave you a unique URL. For example, let’s say it’s:

Our URL to receive messages via SMS will now be:

So with that in mind, we need to tell Twilio to use this messaging url as a our Message Request URL:

Go to the area in this picture and add your Heroku URL with /message appended to it.

Now, send an SMS message to your Twilio number and you should get a response back. If you don’t, take a look at the Twilio App Monitor to help determine what went wrong.

Finishing Up

We’ve built a real-time group chat app using Flybase and Twilio

You can find our group chat app here at Github.

This app can be used for a group of people to carry on a conversation. This can be handy when attending events.

You could use this to notify even attendees of upcoming talks, for example, a conference could add their attendees to a group and then send a broadcast when it is time for a talk to begin, or when it is lunch time.

You can extend this to include support for multiple groups, by simply giving each group it’s own phone number for example.

How do I know if I have product-market fit?


The term product-market fit was coined by Marc Andreessen of Andreessen Horowitz back in 2007 as a way of describing a startup in a good market with a product that can satisfy that market.

Andreessen’s belief is that in a good market — one with lots of real potential customers — the market pulls product out of the startup. Markets with a strong need would be fulfilled by the first viable product that comes along. The product doesn’t need to be great, it just has to basically work; the team doesn’t even need to be great, as long as the team can produce that viable product.

Today, product-market fit is cited by many venture capitalists as one of the most important indicators of startup success. At the seed stage, some investors will fund pre-product-market fit startups based on a hypothesis or minimum viable product. However, beyond the seed stage, product-market fit is often seen as a prerequisite for financing.

So many founders and investors claim that their startups have product-market fit, when, in fact, very few companies ever truly reach this milestone. In this post, I want to talk about how I measure product-market fit, and I’d love to hear in the comments how you measure product-market fit in your company.

Ask An Investor is a new weekly series by Betakit that you should check out, where two investors give advice about topics all startups encounter at some point.

Turning amateur users into experts


Geoffrey Keating:

The question of what it takes to become an expert has occupied psychologists for decades.

As user onboarding and customer success have become more important, it’s pre-occupied businesses too.

So how do you get users to master your product? Most businesses try in one of the following ways.

  1. You can drag them kicking and screaming towards the finish line. Sure, you can successfully push people to do something in the short term, but the minute you stop, they stop.

  2. You can patiently hold their hand, and spoonfeed them the answers. Again, this works well in the short term, but because users are relying so much on autopilot, it’s hard to get anyone past the “OK plateau”.

  3. Or you can give people the tools they need to teach themselves, coupled with gentle prompts towards success.

While options 1 and 2 might work for you in the short term, they still rely heavily on one-to-one interaction. Unless you plan on building a business around white-glove onboarding, you’re going to need to help customers become experts by themselves. That means giving them the tools to succeed, with graduated hints and gentle prompts along the way. Here’s a few ways you can do that.

Interesting reading from Geoffrey.

David Heinemeier Hansson on why Writing software is hard


David Heinemeier Hansson:

Good software is uncommon because writing it is hard. In the abstract, we all know that it is hard. We talk incessantly about how it’s hard. And yet, we also collectively seem shocked — just shocked! — when the expectable happens and the software we’re exposed to or is working on turns out poor.

This is classic cognitive dissonance: Accepting that writing software is hard, but expecting that all of it should be good.

It’s also an application of a just-world theory of effort and accomplishment. That despite the odds, everyone who has the right intent at heart, and puts in the work, will succeed. No they won’t. That’s just delusional. And those delusions are anything but harmless. When we expect good software to be the most likely outcome from the hardship of writing it, we’re setting ourselves up for inevitable disappointment. Even worse, if we feel we deserve good software from our imperfect efforts, we’ll project the inevitable failures on everyone and everything but ourselves.

Another good read from DHH.

We actually treat all of our code as if someone else will use it to develop or learn from at some point, so we try to follow a scheme to keep it easy to read and jump into. That’s been our policy from before Flybase and continues to this day.

Wishing you and yours a Merry Christmas and a Happy New Year


As another year comes to a close, we wanted to wish everyone a Merry Christmas and a Happy New Year in 2017.

As we enter our third year of operation, we like to reflect back on the previous years and think about what we’ve done, and what we have planned for the new year and it makes us happy to think of all we’re bringing to you, our users.

We’ve seen a lot of growth as people realize what they can do with Flybase, and as we bring on our nextgen set of features, people will be able to do even more.

Launch mode vs iterate mode

Links , Startups

Paul Adams:

We had a really successful launch, with hundreds of customers now using it in production, but we don’t yet have a successful product. We must obsessively work to understand how our customers are using the product, what is working well, what needs to be improved. We need to talk to many prospective customers or customers on trial and understand what they need us to change for them to adopt the product.

Launching a product, and iterating a product, are two very different things and they require the team to think and operate in very different ways. We have been in launch mode for many months, and it is now critical to change into iterate mode. This is a really hard thing to do because it means breaking all our habits and ways of working and thinking in the team. The ability to do this asap though will define our success over the coming months. The very things that made the launch successful will make us unsuccessful in this new mode.

When we are in launch mode, we are ruthless about scope. We are narrowing and narrowing. Deciding to do anything other than the defined launch list requires much deliberation. “No” is the default answer to new things. We are necessarily risk averse.

Iterating a product is the opposite. We need to be very open minded and try many small things. We need to make changes to the product based on customer feedback, knowing that we may roll back those changes or change them a second or third time. Success for Educate in 2017 means changing much of our product. We ship to learn. We know that the product has a long way to go.

When I came across this post, I wanted to share it as it’s definitely true. We launched Flybase two years ago, and we’ve been enjoying steady growth since and as we iterate with new features, we’re seeing even more growth, but we also remember the important things. Rather than launching a ton of new features at once, it’s easier to launch them in pieces and gauge how well received they are, this helps us focus more on what our users want.