site/blog/building-a-micro-saas-with-mailerlite-netlify-stripe-and-zapier/index.html
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Building a micro SaaS with MailerLite, Netlify, Stripe and Zapier</title>
<meta property="og:title" content="From stock market email newsletter side project to micro SaaS" />
<meta
property="og:description"
content="A couple of months back, during this crazy world pandemic, I had an idea for a Stock Market email newsletter."
/>
<meta
property="og:image"
content="https://bullish.email/blog/building-a-micro-saas-with-mailerlite-netlify-stripe-and-zapier/social.png"
/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bullish.email" />
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:description"
content="A couple of months back, during this crazy world pandemic, I had an idea for a Stock Market email newsletter."
/>
<meta name="twitter:title" content="From stock market email newsletter side project to micro SaaS" />
<meta
name="twitter:image"
content="https://bullish.email/blog/building-a-micro-saas-with-mailerlite-netlify-stripe-and-zapier/social.png"
/>
<meta name="twitter:creator" content="@eduardosasso" />
<meta name="generator" content="Leter" />
<meta
name="description"
content="A couple of months back, during this crazy world pandemic, I had an idea for a Stock Market email newsletter."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://cdn.jsdelivr.net/" crossorigin />
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/assets/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/assets/icons/favicon-16x16.png"
/>
<style>
:root {
--background_color: #fff;
--page_align: 0 auto;
--text_font: "Rubik", sans-serif;
--text_color: #212529;
--text_size: 20px;
--heading_font: "Montserrat";
--heading_color: #1b262c;
--accent_color: #1b262c;
--link_color: #21bf73;
}
</style>
<link rel="stylesheet" href="/bullish.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.min.css"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Rubik|Montserrat:500,800&display=swap"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@glidejs/glide@3.4.1/dist/css/glide.core.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@glidejs/glide@3.4.1/dist/css/glide.theme.min.css"
/>
<script
async
src="https://cdn.jsdelivr.net/npm/@glidejs/glide@3.4.1/dist/glide.min.js"
></script>
<script>
window.onload = function() {
var conf = {
type: "slider",
perView: 1,
focusAt: "center"
};
var sliders = document.querySelectorAll(".glide");
for (var i = 0; i < sliders.length; i++) {
var glide = new Glide(sliders[i], conf);
glide.mount();
}
};
</script>
<!-- MailerLite Universal -->
<script>
(function(m,a,i,l,e,r){ m['MailerLiteObject']=e;function f(){
var c={ a:arguments,q:[]};var r=this.push(c);return "number"!=typeof r?r:f.bind(c.q);}
f.q=f.q||[];m[e]=m[e]||f.bind(f.q);m[e].q=m[e].q||f.q;r=a.createElement(i);
var _=a.getElementsByTagName(i)[0];r.async=1;r.src=l+'?v'+(~~(new Date().getTime()/1000000));
_.parentNode.insertBefore(r,_);})(window, document, 'script', 'https://static.mailerlite.com/js/universal.js', 'ml');
var ml_account = ml('accounts', '2024694', 'v7f1i7g4p5', 'load');
</script>
<!-- End MailerLite Universal -->
</head>
<body id="classic">
<section>
<div class="logo"><a href="/"><img src="../logo.png" /></a></div>
<h1>Building a micro SaaS with MailerLite, Netlify, Stripe and Zapier</h1>
<hr />
<p>
A couple of months back, during this crazy world pandemic, I had an idea
for a <a href="https://bullish.email">Stock Market email newsletter</a>.
</p>
<p>
Nothing novel, the initial premise was to build a fully automated
hands-off email with some key stats I care about and send it every
weekday before the markets open, and that’s how
<a href="https://bullish.email">https://bullish.email</a> came to be.
</p>
<p>
Doing rounds trying to promote my little pet project, I posted it on
<a href="https://news.ycombinator.com/item?id=22870667">Hacker News</a>
and received great feedback, and one thousand people were convinced
enough to subscribe.
</p>
<p>
That initial traction, combined with all the positive comments and
suggestions, was the signal I needed to upgrade from side hustle to tiny
micro SaaS indie venture.
</p>
<p>
I planned to offer a premium version of the newsletter with a lot more
data points and insights and charge money for it. Plain and simple.
</p>
<p>
But to do that, I had to do a good amount of code refactoring, hookup
Stripe, update the website, make all the plumbing work, and promote it.
</p>
<p>And that’s where the fun begins again. Let’s get to work!</p>
<p>
The first thing I tackled was to
<a href="https://github.com/eduardosasso/bullish/tree/master/services"
>build new data points</a
>, all of them based on variations of the unofficial
<a
href="https://github.com/eduardosasso/bullish/blob/master/services/config.rb"
>Yahoo Finance API</a
>.
</p>
<p>From those API’s I’ve extracted insights like</p>
<ul>
<li>Performance by sector</li>
<li>Trending stocks</li>
<li>Top gainers and losers</li>
<li>All-time high stats</li>
<li>Crypto performance</li>
</ul>
<p>There’s a lot you can do with Yahoo’s API.</p>
<p>
For each of those data points, I formatted their results with an
emphasis on percentage performance to keep it consistent with the
original version of Bullish and maintain its uniqueness.
</p>
<p>
Next, I had to find a way to design and easily code email layouts.
Creating email templates is a huge pain. You have to inline CSS
everywhere, nothing works, and every change is a pain and hard to reuse.
There’s gotta be a better way.
</p>
<p>
After some research, I found <a href="https://mjml.io/">MJML</a> , which
is essentially a markup layer on top of HTML built for designing
responsive emails. It works great, no more writing arcane HTML and
fighting email client compatibility.
</p>
<p>
My mental model for Bullish Pro centered around the concept
<a href="https://github.com/eduardosasso/bullish/tree/master/editions"
>editions</a
>, and that translated to a
<a
href="https://github.com/eduardosasso/bullish/blob/master/editions/free.rb"
>free edition</a
>, a
<a
href="https://github.com/eduardosasso/bullish/blob/master/editions/morning.rb"
>morning edition</a
>, and an
<a
href="https://github.com/eduardosasso/bullish/blob/master/editions/afternoon.rb"
>afternoon edition</a
>
for paid subscribers.
</p>
<p>
Editions are composed of
<a
href="https://github.com/eduardosasso/bullish/blob/master/editions/widgets.rb"
>elements or widgets</a
>
and change daily, like a newspaper. Between Monday and Friday, each
edition includes a set of widgets reused interchangeably to create the
final email layout.
</p>
<p>
This notion of daily editions combined with elements gives a lot of
versatility when creating layouts.
</p>
<p></p>
<div class="glide">
<div class="glide__track" data-glide-el="track">
<ul class="slider glide__slides">
<li class="glide__slide">
<figure>
<img src="./email4.png" alt="Other variations" />
<figcaption>Other variations</figcaption>
</figure>
</li>
<li class="glide__slide">
<figure>
<img src="./email2.png" alt="Different elements combined" />
<figcaption>Different elements combined</figcaption>
</figure>
</li>
<li class="glide__slide">
<figure>
<img src="./email1.png" alt="Different elements combined" />
<figcaption>Different elements combined</figcaption>
</figure>
</li>
<li class="glide__slide">
<figure>
<img src="./email3.png" alt="Other variations" />
<figcaption>Other variations</figcaption>
</figure>
</li>
</ul>
</div>
<div class="glide__bullets" data-glide-el="controls[nav]">
<button class="glide__bullet" data-glide-dir="=0"></button>
<button class="glide__bullet" data-glide-dir="=1"></button>
<button class="glide__bullet" data-glide-dir="=2"></button>
<button class="glide__bullet" data-glide-dir="=3"></button>
</div>
</div>
<p>
Each element gets hooked up to a data point, so for trending stocks, you
can expect to have a widget to render that, same thing for pre-market
futures, crypto, etc.
</p>
<p>
Elements use the
<a href="http://mustache.github.io/">Mustache</a> template engine to
take variables and replace them with data and do some formatting like
green or red if the value is positive or negative.
</p>
<p>
With this design, it’s easy to add new components and move them around
to create unique
<a
href="https://github.com/eduardosasso/bullish/blob/master/templates/template.rb"
>templates</a
>
with almost zero code.
</p>
<p>
Editions have a tag specifying their group ID, so emails go out to the
right group free or premium.
</p>
<p>
Of course, there’s a lot of plumbing involved in stitching everything
together like compressing emails to be
<a href="https://mailchimp.com/help/gmail-is-clipping-my-email/"
>under 102k</a
>, so they don’t break in Gmail and things like that, but that’s a good
overview of how I revamped Bullish to support different email formats
without giving away the idea of full automation.
</p>
<p>
The infrastructure is still pretty much the same, three CRON jobs set up
in a Raspberry PI to create the HTML of each edition and calls to
<a href="https://www.mailerlite.com/">MailerLite</a> API to schedule
distribution. Check the
<a
href="https://bullish.email/blog/turning-my-obsession-in-the-stock-market-into-a-side-project/"
>previous article</a
>
for more details.
</p>
<p>
For payments, I used Stripe and their drop-in
<a href="https://stripe.com/docs/payments/checkout">checkout flow</a> to
accept payments and
<a href="https://stripe.com/billing">Stripe Billing</a> for managing
recurring subscriptions connected to a few
<a href="https://zapier.com/">Zapier</a> recipes to handle users moving
from free group to premium and downgrading from premium back to free
when they cancel.
</p>
<p>
With Stripe Billing and a sprinkle of Javascript
<a href="https://www.netlify.com/products/functions/">cloud function</a>
deployed to <a href="https://www.netlify.com">Netlify</a>, I’ve set up a
magic link on premium emails, so paid users can update or cancel their
subscriptions directly in Stripe securely.
</p>
<p>
Based on the customer id, Stripe generates a unique link on demand so
users can update their subscription without having to log in or create
an account, it’s zero friction.
</p>
<p>
Leveraging Zapier was a big time saver, and it’s less code I need to
write and maintain. They offer a free plan that includes five zaps, and
that’s what I use.
</p>
<p>
Another huge time saver was moving the website over to Netlify. They
have such an excellent product with killer features like branch previews
for testing, automatic asset compression, and serverless functions that
are ridiculously easy to use with no config whatsoever write Javascript
and deploy plus a generous free tier.
</p>
<p>
<a href="https://stripe.com">Stripe</a> is also another great product.
From documentation to testing and configuration and support for Apple
and Google pay, everything was a joy to use and simple to integrate into
the flow I had in mind and seamless to users—big fan.
</p>
<p>
I let this setup run for a few days to make sure everything worked as
expected before I made my first move into upselling the premium version.
</p>
<p>Time to sell.</p>
<p>
My initial strategy was to convert existing subscribers first before
going out to the world.
</p>
<p>
So I crafted an email in a personal tone, where the subject was “<a
href="https://preview.mailerlite.com/o2y8k1"
>It’s launch day!</a
>”. In this email, I started by giving an overview where Bullish was at
and then introducing Bullish Premium for 4.99/mo along with all the
features we were releasing and a big green call to action button saying,
“<strong>Subscribe now</strong>.”
</p>
<p>
My honest expectation was to get maybe four or five users to convert,
but we end up closing ten on the first day. We celebrated with Sushi!
</p>
<p>
That got us to an instant
<strong>$50 monthly recurring revenue!</strong> Almost ramen
profitability.
</p>
<p>
The numbers are so small, but you’ve got to start somewhere. I’ve done
lots of side projects over the years, and they all brought me some
indirect form of monetization. It was through a side project that I
ended up in Silicon Valley, but this is the first time I’m directly
selling things to people.
</p>
<p>
After some validation from the first sales, I moved on to the next big
topic in my todo—a new website.
</p>
<p>
I’ve put together the first version of the website in a couple of hours
using my yet-to-be-released static site generator called
<a href="https://github.com/eduardosasso/leter">Leter</a>, which also
powers this article by the way.
</p>
<p>
<figure>
<img src="./first_site.png" alt="First version" />
<figcaption>First version</figcaption>
</figure>
</p>
<p>
It was Ok but far from what I had in mind, but I had to ship something,
so I pushed a professional better-looking website for later.
</p>
<p>
I know my weaknesses, and web design is a time sink for me. I’m very
opinionated, and I like to think I have a good eye for it, but so far, I
couldn’t deliver anything I was proud of on my own.
</p>
<p>
After learning from my fail attempts over the years trying to come up
with an exclusive design this time around, I looked for references,
inspiration, and templates that could help me jump-start the process.
</p>
<p>
As an engineer, I always feel inclined to start everything from scratch,
and with the new website was no different, the utopian dream of the
perfect, valid HTML and clean CSS a trap that I fell into way too many
times but not again.
</p>
<p>
I ended up
<a
href="https://themeforest.net/item/fold-software-and-app-template/24295615"
>finding a template</a
>
that was pretty close to what I had in mind. I just had to brush off my
CSS skills and do some customization to take my vision to reality.
</p>
<p>
Of course, I have a list of things to improve like adding archives or a
way to update the sample email dynamically, but overall I’m pretty happy
how it turned out.
</p>
<p>
<figure>
<img src="./new_site.png" alt="New version" />
<figcaption>New version</figcaption>
</figure>
</p>
<p>With the site up and running, it was time to promote it.</p>
<p>
Before trying the big leagues on
<a href="https://www.producthunt.com">Product Hunt</a>, we tested the
waters by engaging on Twitter and getting a few subscribers. Then, I
asked my
<a href="https://www.linkedin.com/in/abduzeedo/">influencer brother</a>
to post on his
<a
href="https://www.linkedin.com/feed/update/urn:li:activity:6693739953026945024/"
>LinkedIn</a
>, which generated a nice amount of traffic and about fifty new readers,
not hockey stick growth but decent.
</p>
<p>
For
<a href="https://www.producthunt.com/posts/bullish">Product Hunt</a>, we
scheduled our launch for a Wednesday, and I remember going to sleep that
night, excited to see what the next day would bring, and we bombed, we
never made to the frontpage and only got like 20 upvotes it was a total
disaster.
</p>
<p>
The next day was business as usual, a little bruised, maybe, but that’s
how things go, I guess it will take a few more years to be an overnight
success.
</p>
<p>
Fast forward to Sunday morning. Things are back to normal, I’m having
pancakes for breakfast, and I start seeing this uptick in subscribers
out of the blue.
</p>
<p>
So it turns out
<a href="https://www.producthunt.com/posts/bullish"
>Product Hunt featured us</a
>
in their Sunday edition, and we were on the front page the whole day,
gaining around 200 new subscribers. What a comeback!
</p>
<p>
We're still trying to find the right niche to go after and experimenting with a bunch of ideas to see what sticks. It's all learning sometimes by trial and error. Most times it is by building something that I would like to use it myself and maybe also useful to others.
</p>
<p>
All and all, Bullish has been a welcome surprise in
<a href="https://eduardosasso.co">my life</a>. I’m happy about the
consistency to which I can work on it almost every night and a few
weekends and how having constraints helps you narrow your focus, and it
compounds beautifully.
</p>
<p>Lots more to come.</p>
<p>Give <a href="https://bullish.email">Bullish▲</a> a try!</p>
<p>Cheers.</p>
</section>
</body>
<script>
window.ga =
window.ga ||
function() {
(ga.q = ga.q || []).push(arguments);
};
ga.l = +new Date();
ga("create", "UA-148146327-2", "auto");
ga("send", "pageview");
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
<