Customers are your assets

I’ve been wrangling with use registration for years, partly because it’s an interesting time sink.  Partly because it’s annoying when it’s wrong.  Plus, usually when you start building any application in this day and age you need some form of user registration.  The basic systems these days are:

  • Username and Password  (e.g. yahoo, google)
    • Key signup form elements: Username, Email, Password, Captcha
  • Email address and Password (e.g. facebook, linkedin)
    • Key signup form elements: Email, Password, Capthca
  • Pure Facebook Connect (or Google Connect, etc.)
    • Key signup form elements: Big Facebook button
  • Hybrid systems
    • ..topic of discussion…

Fundamentally username+password and email+password are just variants of themselves, really coming down to how you are known on the system (unique handle “johndoe77″ vs. real name “John Doe”).  Both systems typically will send you and email asking you to confirm your email and they always have a Captcha since the spammers are going to bully you into submission if you don’t. It’s really hard to innoviate in this space it’s just getting the implementation right, but I’ve build four or fourty of these in the past.

The new kid on the block is the Facebook Connent, Google Connect, OAuth/OpenID systems.  The happy part is that as sites start implementing these systems we can stop having to type our passwords into site after site after site — do you really make unique passwords?!?.   The challenge is that as a service you still want to have connection with your user.  Things I’ve noticed:

  • Google gives you email address _easily_ (thanks guys)
  • Facebook gives you a handle to email address and prompts for permission (so, so)
  • LinkedIn you’re just a pain in this front, I want a relationship with your user, not just some handle to use your funked up messaging platform.
  • Twitter/Yahoo – data exchange is a pain.

Back to owning our user!  It’s easy to own the user if they type a password, it gets progressively harder when we start using the Open provider marketplace to have the user.

Why should you care? Because users are your biggest asset!  If you don’t realize that you’re not thinking hard enough.  The cost of customer acquisition is $X in this day and age the cost of marketing to a known user is a fraction of $X (say 1/10th).  Which means that if you have a service and spent $100 on Google ad words to find customers the cost to market a new product to them is $10 rather than $100 — plus you don’t have to re-train them hopefully so your support costs are lower.

That brings me to my last world view of customer registration — hybrid registration.  No I don’t have a quick example deployed at the moment (currently in alpha).  The basic premise is that you ask for the basics up front:

  • Username
  • Password
  • Email address

But now instead of the classic “captcha” page you hit them with the Facebook Connect/Google Auth page to confirm their account.  The complexity of getting this working is slightly higher than the Captcha, but the advantage is that in addtion to having about the same barrier as a Captcha from a spam protection standpoint you also now have a secondary form of authentication and potentially access to very useful data about your customer.

  • Friends/Contacts
  • Full Name
  • …etc…

While you’re probably a smart service and don’t hit the customer over the head with “spam your friends” what you can do is save this away for later use, like when “Sally Smith” joins you notice that “Tom Jones” is a friend of hers and you can point this out in “Suggested Friends”.  Sally just thinks that you’re a smart service — maybe goes “how did they know that” — but you’re really  using the information for their own good.

The second benifit is that since you’re still running a user through a classic registration flow, you’ve got an email address to conduct future marketing to.   But not just that you’ve also now got a guaranteed second touch with the customer.  Why’s that important?

  • Your first touch was getting them to signup and confirm their account, because they connected a social network to their account we’ve got a high confidence that it’s a real user.
  • Your second touch is the confirmation email, which the user will now open 30 minutes to a day later.  Where you can remind them of all the cool things they should be doing with your service.
  • Your third touch is the message you send in three to seven days thanking them for signing up for your service and potentially looking at some of their recent behaviors on the site to determine some simple bucketing of their behavior to help send an appropriate message.

If you feel like I jumped from Hybrid authentication to customer contact, you’re right.  But, fundamentally as I’ve played with user authentication and buit a variety of systems and looked at the long term value of that customer.  My mindset has evolved into these tenets:

  • Own your user — don’t give them away
  • Enable them to login with a variety of system — lower the barriers
  • Keep the lifetime value of your user in mind

Hybrid authentication is the way to go!

Tagged , , ,

Zendesk and Django integration

Part of this post is the gratuitous, gosh that was easy to integrate!  Of course part is a small point that I would like developers to think about.

First off here’s the code snippet, which owes it’s history to:

Zendesk Remote Authentication with Django and Unicode Names and Zendesk remote authenticatin with Djnago (the original posting)

def authorize(request):
    if not request.user.is_active :
        return HttpResponseRedirect(reverse('openauth:signin'))

    try:
        timestamp = request.GET['timestamp']
    except KeyError:
        raise Http404

    u = request.user

    data = ''.join((u.profile.name(), u.email, u.username, settings.ZENDESK_TOKEN, timestamp))
    hash = md5(data.encode('UTF-8')).hexdigest()

    url = "%s/access/remote/?%s" % (settings.ZENDESK_URL, urllib.urlencode({
                'name'        : u.profile.name(),
                'email'       : u.email,
                'external_id' : u.username,
                'timestamp'   : timestamp,
                'hash'        : hash,
            }))

    return HttpResponseRedirect(url)

What’s different — or why am I making this post:

  • Use u.get_full_name() rather than the appends with the spaces, in my case I’ve got another object hanging out (profile) which contains the users name.
  • Use the django username as the external_id — I though about using user.id, but since username should be unique and fairly inflexible that’s a good approximation.
  • Use a join rather than a whole bunch of “%s%s%s%s” no formatting needed…

Finally, the big rant the original code used a bunch of formatted prints to build the URL argument.  If you’ve been handed a language with libraries like python and a framework like django, you don’t think about cross site scripting or other breakages (& in the username) which is going to cause problems…. It’s trivial to use urlencode to avoid these problems.

Time for Money (business 101)

I just read a post talking about the “Does your startup pass The Sleep Test“, in principal the idea is sound it’s a little simplistic.

Fifteen years ago I worked as a consultant, the money was good but the problem I quickly realized is that fundamentally I was just trading time for money.  We can all see how a construction worker trades time for money — $15/hour here I come I’ll hammer and pour concrete all day.  Being a ski instructor is the same way, just like any trade job you’re just on the work for X hours get Y dollars, but you have to wake up and do it all again…  What’s funny is there are a lot of white collar jobs that are the same way, Lawyer is being a great example — hence why many people are slaving away on the partner path — then at least they get the benefit of their co-workers time.

Fundamentally, you need to build a business that for every hour you put in yields a % gain in a core asset.  That asset can then be realized in a few different ways.

  • A blog — you post frequently — have a regular readership that grows and advertising to support it.  This passes the sleep test, people read while you’re asleep, but at some point to grow you need to be a publisher (e.g. TechCrunch) because if you don’t and go on vacation your asset will decrease in value.
  • A piece of software — you or a small team builds a piece of software and sell it, you potentially can sell the software for many times the cost of development.  If done right you can build service contracts and upgrades that yield long term revenue on your customers post sale.
  • Software as Service — similar to above, but now you can charge on a reoccurring basis, though fundamentally this is really just shrink wrapped software with the support side built into the monthly charges.  Variants on this are advertising supported web apps. etc. etc.
  • Secondary assets — you build a widget give it away for free, but fundamentally you’re building a secondary asset that you hope to either data mine or sell directly.   When you signup for that free car at the mall, the offer is the car, but the company is now getting access to your contact information to both sell you a timeshare but also now they’ve got Name/Address/Phone/Email all in a nice neat package..  Plus as a “Are you interested in a Timeshare” phone call you can verify all of the information.

Once a business has an asset, Readers/Customers/Core Asset it now has something to grow and foster.  This is asset is then what the business should be organized around the care of.

Twisted code review…

If you have a few minutes and speak python & twisted, it would be useful to have an extra set of eyes on this section of code. The basic idea of this is to be a reconnecting thrift client, such that I can just write simple client.function(a,b,c) calls without having to worry about if there is or isn’t a client and it will queue reconnect as needed.

from thrift.transport import TTwisted
from thrift.protocol import TBinaryProtocol
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.internet import defer, reactor
from twisted.python import failure
from collections import deque
from redback import log

class ClientBusy(Exception):
    pass

class ClientDead(Exception):
    pass

class InvalidThriftRequest(Exception):
    pass

class ManagedThriftRequest(object):
    def __init__(self, method, *args, **kw) :
        self.method = method
        self.args   = args
        self.kw     = kw

class ManagedClient(object) :
    def __init__(self, factory) :
        self.__factory = factory

    def _is_connected(self) :
        return self.__factory.is_connected()

    def __getattr__(self, name) :
        if hasattr(self.__factory.client_class, name) :
            def f(*args, **kw) :
                return self.__factory.pushRequest(ManagedThriftRequest(name, *args, **kw))
            return f
        raise InvalidThriftRequest("Cant find method: %s" % name)

class ManagedThriftClientProtocol(TTwisted.ThriftClientProtocol):
    def __init__(self, client_class, iprot_factory, oprot_factory=None):
        TTwisted.ThriftClientProtocol.__init__(self, client_class, iprot_factory, oprot_factory)
        self.client_class = client_class
        self.deferred = None
        self.alive    = False

    def connectionMade(self):
        log.debug(self, "Connection made to", "%s.%s" % (self.client_class.__module__, self.client_class.__name__), "[%s]:%s" % self.transport.addr)
        self.alive = True
        TTwisted.ThriftClientProtocol.connectionMade(self)
        self.client.protocol = self
        self.factory.clientIdle(self)

    def connectionLost(self, reason=None):
        log.debug(self, "Connection lost to", "%s.%s" % (self.client_class.__module__, self.client_class.__name__), "[%s]:%s" % self.transport.addr)
        self.alive = False
        try :
            TTwisted.ThriftClientProtocol.connectionLost(self, reason)
            self.factory.clientGone(self)
        except Exception, e :
            log.error(self.connectionLost, e)

    def _complete(self, res, request, dfd):
        self.deferred = None
        if isinstance(res, failure.Failure) and dfd :
            self.factory.pushRequest(request, dfd)
        else :
            if dfd :
                dfd.callback(res)
            self.factory.clientIdle(self)
        return res

    def submitRequest(self, request, dfd):
        if not self.alive :
            raise ClientBusy
        if not self.deferred :
            fun = getattr(self.client, request.method, None)
            if not fun:
                raise InvalidThriftRequest("No such method as : %s" % request.method)
            else :
                try :
                    d = fun(*request.args, **request.kw)
                except Exception, e :
                    log.error(self.submitRequest, "calling : ", request.method, e)
            self.deferred = d
            d.addBoth(self._complete, request, dfd)
            return d
        else:
            raise ClientBusy

class ManagedClientFactory(ReconnectingClientFactory):
    maxDelay        = 5
    thriftFactory   = TBinaryProtocol.TBinaryProtocolAcceleratedFactory
    protocol        = ManagedThriftClientProtocol
    submitLoopSleep = 0
    client_class    = None

    def __init__(self, client_class=None):
        self._stack        = deque()
        self._protos       = defer.DeferredQueue()
        self.deferred      = defer.Deferred()
        self.client_class  = client_class or self.client_class
        self.client        = ManagedClient(self)

    def _errback(self, reason=None):
        if self.deferred :
            self.deferred.errback(reason)
            self.deferred = None

    def _callback(self, value=None):
        if self.deferred :
            self.deferred.callback(value)
            self.deferred = None

    def clientConnectionFailed(self, connector, reason):
        ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
        self._errback(reason)

    def buildProtocol(self, addr) :
        self.resetDelay()
        p = self.protocol(self.client_class, self.thriftFactory())

        p.factory = self
        #self._protos.put(p)
        return p

    def clientIdle(self, proto) :
        self._callback(True)
        if proto.alive :
            self._protos.put(proto)

    def clientGone(self, proto):
        pass
        #if proto in self._protos :
        #    self._protos.remove(proto)

    def _protoErr(self, proto):
        pass
        #import traceback
        #traceback.print_stack()
        #print "IN PROTO ERR", proto

    def _protoReady(self, proto):
        if proto.deferred :
            log.msg(self._protoReady, "Proto currently active!")
            return
        if not proto.alive :
            log.msg(self._protoReady, "Proto currently dead!")
            return

        try:
            request, deferred = self._stack.popleft()
        except defer.QueueUnderflow :
            pass

        d = proto.submitRequest(request, deferred)
        return d

    def pushRequest(self, request, din=None) :
        d = din or defer.Deferred()
        self._stack.append((request, d))

        dfd = self._protos.get()
        dfd.addCallback(self._protoReady)
        dfd.addErrback(self._protoErr)
        return d

    def shutdown(self) :
        """Shutdown this factory"""

        self.stopTrying()
        for p in self._protos:
            if p.transport:
                p.transport.loseConnection()</pre>
Tagged , ,

Good ideas, inquire within

I’ve had lots of idea of things to build, as somebody once said ideas are a dime a dozen, it’s doing it that’s the hard work.  Between talking to companies over the years, or just random things I’ve stumbled across there has to be a good idea to put more energy into.

Things I’ve built in the last few years — in some state of “goodness” (90% complete or better).

  • totallypsp.com - Basic idea was to be a daily featured marketplace for PSP games and accessories.   It’s there it’s running, but once it was going to move from a “build” to manage step I ran out of interest… [100% done]
  • zapquiz.com - Originally came out of a need to have a way to make some review quizes for my PSIA exams.  The core idea was to have a community of people contribute questions on a subject and they have people take sample tests and work forward.   My daughter wants me to finish it, but I’m not sure what “finished” looks like.  This is a case where I’ve not figured out what the product is fully… or how to make it FUN. [95% done]
  • notewave.com - Shared drawings in the vein of sketchfu.com or other shared drawing services.  This half in my mind morphed into focusing on creative clipart on photos… Which yeilded a non-existant iPhone app which would allow you to take a photo, mark-it up and share with your friends on facebook.  The facebook side of things is working where you can create wallposts with photos from your albums and creative drawing, though the UI is crude and it’s only avalable if you have a secret AppID…  [95% done]
  • IrisWall – my Windows Flickr screen saver — it was finished, just never released… Mentioned it to a friend the other day and he was like “Cool Can I get it…” hmm.. source code is in some dusty folder… [100% done]
  • feedini.com – Yet another news agregator, I think this space is dead again…  Had some cool ideas, but not worth anything [90% done].
  • A CMS for google app engine, it allowed for uploading of zip files as template bundles…  But, the problem is that it’s a total pain to configure appengine for 3rd party applications.  The idea was that a small business could use the CMS as the base for a website — with all the CMS features (WYSIWIG, etc.) and a template collection ala wordpress to quickly get an online presence.   [90% done]

Now things I’ve been thinking about:

  • a CRM for job seekers — though I’ve now found about three others, not sure if it’s a worthwhile space.
  • Combining the idea of short status updates and 360′ reviews into a tool that allows you to have a shared vision of how you’re performing and what you’re working on.   Image if a tool periodically pinged you or your co-workers about what you were doing and your performance/perception…
  • Simple portal pages – think kosmix.com, but with the baggage of a search engine.  Focus in on domain name parking and companies like that where potentially providing a more delightful experience around a subject area might increase the parked domain experience.
  • Tools to extract structured information from webpages in a way similar to Yahoo!s Search Monkey but utilzing some better document techniques to provid automated infrastructure.  I keep on having vauge visions of a meeting a quack.com from 10 years ago.  They probably had the best system I’ve seen to date, but yet there are still some huge things to do.

Fundamentally, I want to build things to delight users.   It’s not too hard to get passionate about a problem, but fundamentally I want to really know who I’m building for…  Heck, it doesn’t even have to be “hard” — just has to be something that people find annoying and want improved.

What are your pain points – or where should I spend my energy?

Tax Your Calories

Just a random idea that came out of a strange hallway conversation on the cost of health care. Here’s the idea of the week.

One of the current big problems in America is the over consumption of food yielding both health problems and just waste. Since we’re currently trying to figure out how to pay for our healthcare system the quick idea is to tax calories in the style of other sin taxes. Think of it this way, a typical person consumes 2000 calories a day, if there was a sin tax of $0.20 (e.g 1 cent per 100 calories) it would be a small price to pay. The quick observation is that if you’re eating a Bacon Double Cheeseburger with fries and a large coke you’re probably just at a 2000 calorie lunch. So now when you eat breakfast and dinner you’re clearly contributing to your long term health care plan… that emergency room visit for a coronary bypass.

The quick modification is that there really should be no tax on any fresh or frozen produce. If you really were eating half of your calories in produce clearly your health care costs are going to be lower, than Mr. Bacon Dbl Cheeseburger.

Tagged ,