How I made a CRM in 3 hours

A couple of friends asked me “how?”, after I told them that I was not using a third-party CRM solution and had made my own (for my company) in a really short amount of time. This is my response to you 3 (in a stack-agnostic way) and anyone else who might find it useful.

A little context, first: I spent a few weeks (in my sales-allocated time) evaluating existing affordable CRM solutions for my lightweight single-salesman use. I actively signed-up and tried Hubspot CRM, PersistIQ, Close.io, and Mixmax. While all these products are really great, two things kept hitting the engineer in me:

  • The functionality that many CRM tools do is relatively straightforward. The ability to build+view lists of your customers and filter through specific properties, the ability to track threads of conversations, track and be notified on opens/clicks, make templates, follow-up automatically, etc. weren’t necessarily technically complex problems.
  • Many of them lacked a strong workflow around bulk-outreach for sales, tied to existing customers. For some reason, the CRM world and the marketing automation world have in the past treated these as two discrete functions, and I saw them more like a continuum. Admittedly, the newer tools have this realization too – but the moment a tool starts solving both these problems for you, that’s when your credit card doesn’t like you very much.

To be honest, before my trials with these products, I didn’t even think about the build-vs-buy decision because it felt very obvious to not get distracted from the core function of my job, and to just adopt an existing free product in this competitive market. But added to the following reasons, I decided to make something from scratch:

  • I had done a ton of email marketing using custom HTML templates (hint: use the Mailchimp editor to design your emails for the first time, and then copy the generated code and re-use and modify yourself in future) through the simple “send_email” functionality Django gave me. I had everything in place to build lists, send “mail-merged” templates into people’s inbox, being notified on opens + clicks, and unsubscribe functionality. Deliverability was handled by Mandrill (kids, once upon a time…).
  • I had very easily re-usable web components for lists of things, routing, popups, and action-handling on individual items on a list (think action on an email in your Gmail). To clarify, I wasn’t using funky Polymer components – I literally mean I had all the UI classes, JavaScript event handling through React and react-router, and an easy icon scheme in place. Most “web-apps” have these re-usable components lying around.
  • I wanted query-ability and manipulatability. My Django console gave me exactly what I wanted.
  • I wanted control over workflow. These apps were rigid about how you went about doing things.
  • I am cheap and didn’t want the artificial limits on these apps, before I was roped into exorbitant plans for MBAs who would call it “worth-every-penny”. Also, several of them are bandwidth and memory suckers, so I didn’t care for 8MB single-page apps downloading and then occupying another 50MB in memory in a new tab every time I wanted to see how many opens I got on my last email.
  • I am the sole salesman. I already have all the thread-tracking I needed in my personal mailbox. Not sure serving a team complicates things technically for any of these products, but hey, its something else to worry about.

It’s important to emphasize the infrastructure-in-place bits. I had things like a server, an authentication system, a UI, security, a deployment system, a (basic) testing system, a cron system, a queue, etc. setup and working for our product in a non-experimental way that made my job so simple and easy. I doubt it will take as less time if you had to deploy a new app for this. But may be these Heroku and Firebase and Docker thingies have solved this too.

Anyways, instead of putting some stack-dependant code on GitHub, I am just going to describe how you can do this for yourself. It’s straightforward if you are a developer who has done sales or studied about it. Also, please note that this is in no way a fully-flushed CRM – I needed some basic pieces of functionality, and that’s the stuff I built in that limited time-period. It is, however, infinitely extendible to my ongoing needs. I call it “People” (not “People CRM”, just “People”).

The models

Lead
[This represents the person you are planning to reach out to]
– title (varchar)
– salutation (varchar)
– email (email field / varchar)
– first_name (varchar)
– last_name (varchar)
– phone (number field / varchar)
– notes (text)
– organization (varchar)
– user (null-able foreign key to your app’s user model)

List
[A set of leads you have brought into the system from a particular source]
– title (varchar)
– leads (many-to-many to Lead)
– category (varchar)

Campaign
[An email blast that you are going to send out]
– title (varchar)
– created (datetime)
– leads (many-to-many to Lead)

Touchpoint
[A single experience that your leads are going to have with your campaign; through email in this case]
– title (varchar)
– template (varchar) // I use .html template assets, but you could very easily use the ‘text’ field to hold the entire template here.
– subject (varchar)
– from_name (varchar)
– from_email (varchar)
– scheduled_for (datetime)
– campaign (foreign key to Campaign)

Interaction
[A specific touch that one lead is going to have with your campaign]
– touchpoint (foreign key to Touchpoint)
– lead (foreign key to Lead)
– opened (datetime)
– replied (datetime)
– sent (datetime)
– clicked (datetime) // Assuming a simple one CTA per email.

Clicks
– url (url field / varchar)
– touchpoint (foreign key to Touchpoint)

The controller

In a server-side language agnostic way, here are some of the most basic functions I chose to write in my controller (or view, depending on how you / your framework represent it).

get_campaigns()
/* AJAX or rendered page listing of all campaigns in the system */

get_lists()
/* AJAX or rendered page listing of all lists in the system */

get_touchpoints(campaign_id)
/* AJAX or rendered page listing of all touchpoints tied to a campaign */

get_leads(list_id)
/* AJAX or rendered page listing of all leads tied to a list */

send(touchpoint_id)
/* Send all interactions linked to a particular touchpoint.
Put actual code of sending the emails in an async queue/scheduler. The process of sending the emails not just substitutes template tags in {{ }} on the .html template, but also in the subject field for really contextual subject lines.
*/

read()
/* A tracking pixel generator which first passes the user+touchpoint ID params on the request to an async queue which records the open of the email.

This also asynchronously connects to a public IRC server, and pings my personal user; who is logged in on boot on Empathy inside Ubuntu generating a beautiful system notification on each open. I realize you could use Slack if you are a tech hipster; I am just a one-tab person and not an active Slacker.
*/

click()
/* A URL redirector which first uses the unique ID in the params to resolve to the actual URL, but also passes the user+touchpoint ID params to record the clicks on the email */

Open tracking
If you haven’t done this before, don’t fear it as being complex. It’s actually pretty straightforward. It’s an industry-practice to embed a 1x1px transparent image anywhere in your email (usually at the bottom). Let your send function take care of that too. When your recipient opens the email in their email client, their client makes a GET request on that image (it doesn’t flag 1x1px; usually loads it naturally), and you should respectfully return an image.

Now, instead of it being routed to an actual image file in your assets, set your URL patterns to process that request like a regular web-page request in a function. And then, after you have actually recorded the email open on the ‘Interaction’ record (use the unique interaction params added to the img’s src URL when it was originally sent through the send function, all of which was appended to the email template), return a base64 encoded image file like this:

HttpResponse('R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='.decode('base64'), mimetype='image/gif')

This is a Python/Django specific example, but you get the point and should be able to modify it easily for your needs. To the client, this is nothing but an image asset being served by a web-server.

The view / UI

People CRM - The view

I have an ultra simple UI that only does the listing bits for me. I have three main sections:

  • Lists: Place to list templates, click on any of them, see the the leads and some of their properties in column. Easily extendible to enable functionality where you can click on a lead to show the threads of conversations, if this app had a Gmail integration and was listening to a registered webhook (yes, they have that in their API) on new emails in the Inbox.
  • Campaigns: Place to list campaigns, click on any of then, see the touchpoints. Here one could use the extra column space to show stats on the success of the campaign using some very very simple database functions.
  • Templates: Listing of all templates. You could even add a simple form to add/update templates here.

I actually have no edit functionality in the UI because Django admin just gives me that for free. Plus, we always have the QuerySet API (or the equivalent in your framework / world).

The process for a new sales-email campaign

I begin by using a standard Google sheets template to manipulate and fill out a new list of leads as and when I come across them or build one. Nothing stops you from using Excel or LibreOffice Calc for the same. I export this into a CSV file, and upload it into an S3 bucket.

I then pass the path of the CSV to a simple script I wrote that’s sitting on my server and easily executable through the command-line (or just create a function in your controller that you can call through a GET or POST request!). This script basically does the simple job of reading my standard CSV format and creating a bunch of Lead records with it. I then go in and manually create a List record, and add the leads to them.

I then go design the email template – for sales, this is usually plain-text with some
<br/>s and <p>s. Right after, I create a Campaign through the Django admin interface. I follow this up by creating a Touchpoint through the same web interface if this is the first time I am reaching out.

I now open my Django shell (it’s usually always open, anyways) and create a bunch of Interaction records from the list I have added leads to. I tie them to this touchpoint.

When I am ready to send, I just pass the touchpoint ID to my send function, and it does the rest. Recipients ignore, I don’t come to know. Recipients open, I get pinged on Empathy. Mandrill reports (or used to report) bounces and failures in delivery to a webhook endpoint you can define through their API, and it’s utterly easy to just delete that lead from your system, and drop you a notification in the process of doing so.

One of the most obvious extensions of this which the models have accounted for is ‘automatic follow-ups’ (yes, something ToutApp charges a ton for). You can run a daily cron, create a second touchpoint but not send it, and send the touchpoint out on the `scheduled_for` datetime for all people in a campaign who haven’t opened or replied (a flag that you’d need to manually update on each record without a Gmail OR browser extension) from an earlier touchpoint in the campaign.


Boom; that’s it!

Now I fully realize there are a few people for whom the above is intimidating, or may be you have other needs and setups. Highly recommend the tools I tried; I think all of them were very good for the problem they solve if you are on a budget. If you want to learn more about the sales process, read close.io’s blog frequently, Steli is a beast who has taught me most of things I know about sales.

4 thoughts on “How I made a CRM in 3 hours”

    1. I am asked this question often, but my most common stance on this has been that open sourcing it is probably not going to be of use to any one. There are plenty of open source CRMs out there (beginning with SugarCRM), but what is lacking is people’s ability to think through the needs and components of a CRM system, which is why I chose to break down the thought process.

Leave a Reply

Your email address will not be published. Required fields are marked *