$9 Marketing Stack: A Step-by-Step Guide

$9 Marketing Stack

Update 2016-10-18: This tutorial has been updated to reflect the latest version of my stack (now with Drip!). I’ve also updated pricing info (it’s technically a $0 stack now) and screenshots. The original outdated article is archived here.

“Just tell me what to do so I can stop worrying about tools and start making money.”

When it comes to setting up a marketing stack (analytics, email, automation, etc.), you can quickly become paralyzed by choice, especially if it’s your first time.

I’ve written about our industry’s need for default marketing stacks that prescribe which components to use and how best to use them, so you can get up and running quickly, avoid common mistakes, and focus on your business.

Below is a step-by-step guide to setting up what I call the Boostrapper’s Stack. I’m going to walk you through the exact steps I used to configure this stack for my SaaS app Munchkin Report.

What’s in the stack?


Marketing Automation (opt-in forms, email marketing, automation)

With this stack, I can:


What’s missing?

Not much, to be honest! The goal is to keep the stack pretty lean. If you’re starting off with more than this, you’re probably overdoing it.

If money were no object, I could swap Drip out for HubSpot, but they’re so comparable these days. Alas, Drip is $49/month for what I need and HubSpot is definitely NOT $49/mo.

This particular stack doesn’t include a nice landing page creation tool like you’ll find in HubSpot, so I simply create landing pages like I would any other page on my sales site and embed a Drip form. Pretty easy. For this project, the cost/overhead of adding a tool like Unbounce or LeadPages to the mix outweighs the benefit. If you want a drag-and-drop landing page builder, LeadPages 💙 Drip.


Other super-cheap or free marketing tools you’ll catch me using for Munchkin Report, but I consider outside the scope of my core marketing stack include:

Now that I’ve described what is in the Bootstrapper’s Stack, feel free to wander off and experiment on your own. However, if you also want to know exactly how I use the stack, stick around.

Some context

Before I dive into the detailed setup steps, I think it’s important understand Munchkin Report as a business. Munchkin Report is web-based SaaS with monthly and yearly subscription revenue.

Munchkin Report

It is used by parents who want to track their child’s activities at home (perhaps with a nanny) and daycare centers / preschools that want to ditch paper tracking sheets, improve communication with families, and differentiate themselves.

Users track naps, food, diaper changes, moods, photos, etc. The information helps parents make better decisions day-to-day, spot trends in their child’s behaviors and habits, and stay in-sync with caregivers during the day.

The north star metric for Munchkin Report is the number of parents who receive an activity report per day.

The app also serves as a memory book with pictures and a milestone timeline, but that’s a secondary benefit for most people.

Now that you know the business, let’s take a look at the marketing machinery behind it.

Step 1: Create a tracking plan

The first part of the stack to setup is analytics, which is a foundational element. But before you install analytics, it’s crucial to think through what you REALLY need to track. I highly recommend that you go through this exercise.

Here’s the outline for Munchkin Report’s tracking plan.

Our core lifecycle events are:

Our second-tier lifecycle events are:

We want to track the following key pages:

We will identify users on:

Step 2: Setup Segment

Segment is only the best invention EVER.

Install Segment and it becomes the One True Interface to all your third-party apps. You send data to Segment and it takes care of mapping and routing that data to the apps you enable.

It simplifies your setup, makes it trivial to enable new apps, and it’s more efficient. It also a cool replay feature (business plan only) which lets you send HISTORICAL data to a newly-enabled tools.

Virtually all of our setup tasks for analytics and tracking will be done via Segment.

To start, create a free account and start a new JavaScript source for your app.

2.1 Install the JavaScript Library

Click on the Settings tab for your new source, then click API Keys.


Here you’ll find your API Write Key:


Make sure you add your API Write Key to the default analytics.js code snippet.


Add the code to your website’s header template so that it loads on every page. You should start seeing pageview events in your live debugger now.

Add it to your web app, too!

If you also want to make client-side tracking calls from your web app (and you should), you’ll need to install the analytics.js library in your application source code as well.

For Munchkin Report, which is is a Rails app, I created a file called /app/vendor/assets/javascripts/analytics.js that contains the same snippet of code I used on my sales site.

Tip: to ensure that this library is loaded on all pages of the app, validate that your application.js file contains the following line:

# /app/assets/javascripts/application.js

//= require_tree ../../../vendor/assets/javascripts/

Reference: https://segment.com/docs/libraries/analytics.js/quickstart/

2.2 Install a server-side library (optional)

If you want to make server-side HTTP calls to Segment, you’ll want a server-side library. Luckily, Segment has a whole bunch of ’em.

They also have a great article which answers the question: “When should I use client vs. server tracking?

I’m using the Ruby library, which I installed by adding a gem to my Gemfile:

# /Gemfile

gem 'analytics-ruby', '~> 2.0.0', :require => 'segment/analytics'

And then I created an initializer:

# /config/initializers/analytics_ruby.rb

Analytics = Segment::Analytics.new({
  write_key: 'YOUR_WRITE_KEY',
  on_error: Proc.new { |status, msg| print msg }


Reference: https://segment.com/docs/libraries/ruby/quickstart/

2.3 Install the WordPress plugin (optional)

If you have a blog on a separate platform or codebase, you’ll want to add analytics.js there, too.

While munchkinreport.com is a static HTML site, the blog runs on its own WordPress instance. Segment has a handy WordPress plugin that automagically tracks everything you’d want to track. You can install it by searching for “segment” in the WordPress plugin search tool.

Segment - WordPress Plugin

Reference: https://segment.com/docs/platforms/wordpress/

2.3 Implement the tracking plan

Now we need to drop in bits of code to tell Segment when key things happen, according to our tracking plan.

Page Tracking

I have 3 key pages I want to track, so on each of them I’ll add a one-liner to indicate that the visitor is viewing a key page.

Add a page call to my Tour page:

<!-- /pricing/index.html -->


Add a page call to my Pricing page:

<!-- /pricing/index.html -->


The signup page is hosted in the app itself, not the sales site, so I add the page call to the JavaScript on the signup page in my Rails project:

# /app/views/devise/registrations/new.html.erb

<%= javascript_tag do %>
  analytics.page('Sign Up');
<% end %>

Identifying Users

Segment automatically cookies every new visitor to your site with an anonymous ID. When a user becomes known–via opt-in or sign up/in–calling identify links that identity to all the previously anonymous activity you’ve collected.

Managing identities can be a major source of pain with analytics tools, so pay close attention to this.

There are 2 points in my funnel where I’m going to identify a user: when they’re logged into the app and after they download a resource.

Here’s what the identify code looks like when someone lands on any of my app’s internal pages after logging in:

# /app/views/shared/_analytics.html.erb

<% if current_user %>
  // Segment
  analytics.identify('<%= current_user.id %>', {
    name: '<%= current_user.full_name %>',
    email: '<%= current_user.email %>'
<% end %>

To see how I’m identifying users after a resource download, read the section below titled Downloaded Resource.

Lifecycle Event Tracking

The Signed Up event is called from the app as well:

# /app/views/shared/_analytics.html.erb

<% if params[:init] == "true" %>
  analytics.alias('<%= current_user.id %>');
  analytics.track('Signed Up', {
    userLogin: '<%= current_user.email %>',
    type: 'organic',
    accountId: '<%= current_user.account.id %>'
<% end %>

The Logged Activity event triggers whenever a user (parent or teacher) adds activitiy for a child. Since I have multiple controllers for each activity type, I have a concern that includes all of the shared methods for those controllers.

# /app/controllers/concerns/eventable_controller.rb

def track_logged_activity(activity_type)
    user_id: current_user.id,
    event: 'Logged Activity',
    properties: {
      type: activity_type,
      accountId: current_user.account.id

Then in each of the 5 activity controllers, I add a method call to track_activity within the create action:

# /app/controllers/meals_controller.rb

def create
  @meal = @munchkin.meals.build(meal_params)

  respond_to do |format|
    if @meal.save
      # Send "Logged Activity" event to Segment
      track_logged_activity controller_name.classify.singularize

Downloaded Resource

For resource downloads I use a single modal form at http://www.munchkinreport.com/resources to collect leads. When someone clicks the submit button, I have some JavaScript that pauses the submission, assigns some hidden field values, and then reports to Segment before the request transitions to the “thank you” page.

<!-- /resources/index.html -->

<!-- Set the right resource name in the modal -->
$(function () {
  // Set resource name when the modal form opens up
  $('a.resource-item').click( function () {
    var resourceName = $(this).attr('data-resource-name');
    return true

  // Grab a reference to our form
  form = $('form');
  // Run the function when the form submits
  form.on('submit', function(e) {
    // Stop the form from submitting... for now
    // Get the email address the user has entered
    email = $(e.target).find('[name=email]').val();
    name = $(e.target).find('[id=resourceName]').val();
    // Identify this visitor using their email address as a distinct ID
    // and as a new property
    analytics.identify(email, {
      email: email
    analytics.track("Downloaded Resource", {
      resourceName: name
    // Submit the form now that all our analytics stuff is done


function setResourceName(resourceName) {
  $('form input#resourceName').val(resourceName);
  $('#resourceModal #myModalLabel').text(resourceName);

Note: if I had a landing page for each resource, I would use Segment’s trackForm helper. But because I have to dynamically set and read the hidden resourceName field on-the-fly, I had to roll my own.

Invited User

# /app/controllers/parents_controller.rb

def track_invited_user
    user_id: current_user.id,
    event: 'Invited User',
    properties: {
      inviteeEmail: @parent.email,
      accountId: current_user.account.id

Added Munchkin

# /app/controllers/munchkins_controller.rb

def track_added_munchkin
    user_id: current_user.id,
    event: 'Added Munchkin',
    properties: {
      creatorType: current_user.type,
      accountId: current_user.account.id

Sent Daily Report

# /app/controllers/munchkins_controller.rb

def track_sent_daily_report(to_list)
    user_id: current_user.id,
    event: 'Sent Daily Report',
    properties: {
      recipientList: to_list,
      munchkinId: @munchkin.id,
      reportDate: @selected_date.strftime('%Y-%m-%d'),
      accountId: current_user.account.id

2.4 Enable integrations

Now I’m going to enable 2 integrations:

  1. Google Analytics
  2. Mixpanel
  3. Drip

At this point, I’ve created accounts for the tools listed above, but I haven’t touched them yet. Instead of “installing” these tools, I’m simply going to enable them in Segment.

Enable Integrations with Segment

The foundation I laid with Segment’s analytics.js and the track, page, and identify calls will start paying off.

Ah, the beauty of Segment.

Step 3: Google Analytics reports

There’s not much to configure in GA since Segment handles your tracking code and will automatically log GA events for each track call:

Google Analytics Events created by Segment

3.1 Configure Goals

Goals are important to setup because they add vital context to many built-in GA reports. For example, it’s nice to know which channels generate the most traffic to your site (via your Channels report), but it’s critical to know which traffic sources convert the best. You can’t do that without Goals.

Channels report (now with Goals!)

Since I already have my key conversion events coming in via Segment, it takes no time to setup Goals based on them.

I’m not going to setup a Goal for every event that I track. That’d be overkill. I’m only going to set them up for the following core lifecycle events (for now):

Just go to Admin and under your View section, click on Goals and create a new goal based on an Event:

Google Analytics Goals

3.2 Enable Demographic and Interest Reporting

I like to turn on the Demographic and Interest Reporting feature (via Segment, which will handle the GA tracking code update for you automatically):

Enable Demographics and Interests

And then enable those report features in my GA property settings:

Demographics Reports setting in Google Analytics

Because who doesn’t want to know their audience demographics and interests?! Age, gender, and categorical interest data will now be visible under the Audience tab.

Other things I look at frequently in Google Analytics:

I like Portent’s Perfect GA Dashboard.

Step 4: Mixpanel reports

I use Mixpanel for two core features: funnels and cohorts.

Mixpanel has already been receiving events from Segment, so I can create funnels on-the-fly:

Resource download funnel

I’ve created funnels to visualize the steps people take towards critical events:

And so on.

The other report I like to use in Mixpanel is the Retention report, which tells me which cohorts of users are sticking around and continually using my app. You can segment by any property to get a really granular view of things. For example, you can easily get a breakdown of retention rates for each subscription plan.

I haven’t really used the people reporting and notification features in Mixpanel yet, but they look pretty cool.

Step 5: Drip

Drip is a very solid, well-designed marketing automation tool. It’s not as comprehensive as HubSpot, which has a built-in CRM, a website hosting platform, and social media tools, but they have all the core marketing automation functionality you’d expect and it’s very easy to use.

Right now I use Drip for the following things:

  1. Capture leads via forms (and the API)
  2. Send email nurture sequences (one for new leads, one for trials)
  3. Send one-off broadcast emails (like product announcements, etc.)

5.1 Capture leads via the Drip toaster widget

The easiest way to get going with Drip is to create a form and enable the toaster widget. I have one that appears on most pages on munchkinreport.com that offers a sample daily report.

Drip Toaster Widget

You can easily customize the design of your widget and configure when and where it triggers. When a visitor closes the widget, it doesn’t go away completely. You can still see the little tab. I wrote a little bit of custom JavaScript that makes the widget disappear completely when minimized:

// Close the Drip form when someone clicks "X"
$(function checkFormDrip() {
  if( $('.drip-tab').length > 0 ) {
    // form is loaded, hook up the event handler
    $('.drip-close').click(function() { $('.drip-tab').hide(); });
  } else {
    setTimeout(function() { checkFormDrip(); }, 200 );

I have a simple rule setup that, upon submission of the widget, does the following:

  1. Sends a one-off email to deliver the sample report
  2. Tags the subscriber with “Sample Report”
  3. Adds them to my Parenting Email Course drip campaign

5.2 Capture leads via embedded forms (optional)

I created a few different lead capture forms, configured the fields, and placed the embed code on the resources page on my site.

Embedded Forms with Drip

When a visitor fills out any embedded resource form, they follow the same 3 steps as the subscribers that fill out the widget.

5.3 Capturing leads via the API when a trial is started

My trial signup form posts to the Rails app, not Drip, but I DO want these users in my marketing database so I can send them an onboarding email sequence, tag them, etc.

So, after a new user signs up, organically or by accepting an invite from another user, I sync them to Drip via the HTTP API.

I could use the Drip Ruby Client, but I already had Zapier configured so I just made a zap instead.

Here’s the method I wrote on the User class:

# /app/models/user.rb

def update_marketing_contact(signup_type, tags)
  params = {
    email: self.email,
    first_name: self.first_name,
    last_name: self.last_name,
    subdomain: self.account.subdomain,
    plan: self.account.plan,
    signup_type: signup_type,
    tags: tags
  Zapier.zap(:contact_sync, params)
rescue => error
  logger.error error

After a new trial user is “zapped” into Drip from my app, they’ll enter the onboarding automation sequence which sends helpful emails about how to use them app. Here’s a sneak peak at how that automation is setup:

Drip Automation for SaaS Trial Onboarding

Drip has one of the best automation workflow builders around. I’ll dive deeper into how my trial onboarding automation is setup on another post, including the copy from each of the emails I send.

5.3 Set up Conversions

Remember the 4 goals I setup in Google Analytics? I’m going to set them up in Drip, too. Drip calls them “Conversions.”

Just click on Analytics in the main nav and choose Conversions.

Important: you should name your conversion exactly the same as the event you’re firing. For example, my Segment tracking code does this:

analytics.track("Started Trial");

So I named my Drip conversion this:

Setup Drip Conversion Goals

When you’re triggering conversions from events (not based on a URL), just leave the URL field blank:

Drip Conversion Settings

I love the way all of the events from my tracking plan clearly display in the Drip subscriber activity feed.

Drip Subscriber Timeline

I can also segment subscribers based on the events they’ve triggered, tag them, enroll them in different automations, and customize email content with Liquid conditionals:

{% unless subscriber.tags contains "Activated" %} 
  Need help adding your first Munchkin? Get free concierge onboarding.
{% endunless %}

5.4 Send NPS surveys (optional)

If you’re interested in doing NPS surveys with Drip to gather customer feedback, I’ve got a whole article that teaches you how to do that. It includes a Drip-ready NPS email template.

Wow. That was A LOT, huh?

This just goes to show that laying a solid foundation for analytics and marketing automation takes foresight and planning, some technical chops, an analytical mindset, and some trial and error. And my app is small potatoes compared to many of the businesses you guys are dealing with.

I hope this guide can serve as a step-by-step implementation plan for those that want to use this precise stack, and for those that want to mix it up, maybe you can use my setup as a loose template.

Feeling overwhelmed? No worries, I’ve setup analytics and marketing automation for dozens of companies. From e-commerce to SaaS to membership sites and more. Reach out and we can work together.

Also, if you do decide to try Drip, and you got value from this article, consider using my affiliate link (just click the image):


Featured image credit (cc): http://www.flickr.com/photos/basheertome/13739968364