<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cory Zilisch (Cory Zilisch)</title>
    <description>The latest articles on DEV Community by Cory Zilisch (Cory Zilisch) (@the_zilisch).</description>
    <link>https://dev.to/the_zilisch</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3844746%2F0a892392-4254-4ffe-9230-85279cb96103.jpg</url>
      <title>DEV Community: Cory Zilisch (Cory Zilisch)</title>
      <link>https://dev.to/the_zilisch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/the_zilisch"/>
    <language>en</language>
    <item>
      <title>I Built an Uptime Monitoring SaaS in a Weekend — Here's What I Learned</title>
      <dc:creator>Cory Zilisch (Cory Zilisch)</dc:creator>
      <pubDate>Thu, 26 Mar 2026 13:09:55 +0000</pubDate>
      <link>https://dev.to/the_zilisch/i-built-an-uptime-monitoring-saas-in-a-weekend-heres-what-i-learned-4nih</link>
      <guid>https://dev.to/the_zilisch/i-built-an-uptime-monitoring-saas-in-a-weekend-heres-what-i-learned-4nih</guid>
      <description>&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I was paying $15/month for Uptime Robot.&lt;/p&gt;

&lt;p&gt;I used maybe 30% of it. Most days I just needed to know: is my site up?&lt;/p&gt;

&lt;p&gt;Friday night I thought: I could build this myself.&lt;/p&gt;

&lt;p&gt;Saturday night I realized: I could build this in a weekend.&lt;/p&gt;

&lt;p&gt;By Monday morning it was live.&lt;/p&gt;

&lt;p&gt;Now I have 50 users and 3 paying customers. Here's what actually happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Friday: The Problem Gets Real
&lt;/h2&gt;

&lt;p&gt;You know when you pay for something you don't fully use, and you can't quite justify canceling it? That was me with Uptime Robot.&lt;/p&gt;

&lt;p&gt;$15/month × 12 months = $180/year for monitoring I could describe as: "tell me if my site goes down."&lt;/p&gt;

&lt;p&gt;I wasn't using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advanced analytics&lt;/li&gt;
&lt;li&gt;The 17 different integration options&lt;/li&gt;
&lt;li&gt;Custom headers on requests&lt;/li&gt;
&lt;li&gt;Response time tracking&lt;/li&gt;
&lt;li&gt;Detailed historical reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I just wanted: HTTP request every 30 seconds, alert when it fails.&lt;/p&gt;

&lt;p&gt;So I sketched out what "simple" actually means:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor HTTP endpoints&lt;/li&gt;
&lt;li&gt;30-second check interval (fast enough for real alerts, slow enough to not hammer servers)&lt;/li&gt;
&lt;li&gt;Webhook alerts (route to whatever I want)&lt;/li&gt;
&lt;li&gt;Email alerts (backup option)&lt;/li&gt;
&lt;li&gt;Simple dashboard (show status and last check time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tech stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js + Express (I know it, builds fast)&lt;/li&gt;
&lt;li&gt;SQLite (no external database, backup = copy a file)&lt;/li&gt;
&lt;li&gt;DigitalOcean $5/month droplet (already had one)&lt;/li&gt;
&lt;li&gt;Stripe for payments (industry standard, boring is good)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I explicitly did NOT build:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Synthetic user journeys (too complex, different problem)&lt;/li&gt;
&lt;li&gt;Advanced dashboards (nobody cares)&lt;/li&gt;
&lt;li&gt;Integrations with every platform (webhooks handle 90% of use cases)&lt;/li&gt;
&lt;li&gt;TLS certificate monitoring (separate thing)&lt;/li&gt;
&lt;li&gt;Custom alert routing (webhooks are the answer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This list of "what to NOT build" was more important than the "what to build" list.&lt;/p&gt;




&lt;h2&gt;
  
  
  Saturday: The Core Engine
&lt;/h2&gt;

&lt;p&gt;Saturday I built the monitoring engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version of the core check logic&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;performCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Use HEAD, not GET (faster)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HTTP error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the day was actually spent on edge cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: Flaky networks&lt;/strong&gt;&lt;br&gt;
If a DNS lookup times out, is that a real alert or just a blip? I implemented retry logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First timeout: wait 10 seconds, try again&lt;/li&gt;
&lt;li&gt;Second timeout: alert&lt;/li&gt;
&lt;li&gt;This reduced false positives from 10% to &amp;lt;1%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problem 2: SSL certificate issues&lt;/strong&gt;&lt;br&gt;
A site can have an invalid cert but still serve content. Do we alert? I added logging for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invalid cert = alert with reason "SSL cert issue"&lt;/li&gt;
&lt;li&gt;Response still comes through = not an outage, but a problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problem 3: Hanging connections&lt;/strong&gt;&lt;br&gt;
Some servers hang indefinitely on connect. I added timeout logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10-second timeout on each check&lt;/li&gt;
&lt;li&gt;If it hangs, mark as down and move on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problem 4: Load spreading&lt;/strong&gt;&lt;br&gt;
If I check 1000 sites every 30 seconds, that's 33 requests per second. Spread over 30 seconds, that's 1 req/sec. But concurrently? I learned about connection pooling fast.&lt;/p&gt;

&lt;p&gt;Solution: use HTTP/2 keep-alive, limit concurrent connections, queue checks.&lt;/p&gt;

&lt;p&gt;By 11pm Saturday night, the engine worked. I could add a URL and get alerted when it went down.&lt;/p&gt;


&lt;h2&gt;
  
  
  Sunday: The Boring Stuff
&lt;/h2&gt;

&lt;p&gt;Sunday was database schema, UI, and Stripe integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database (SQLite):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;monitors&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;check_interval&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;alert_method&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;monitor_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;response_time&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;error_message&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;checked_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monitor_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;monitors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;monitor_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;previous_status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;new_status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;triggered_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;delivered_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple schema. No normalization beyond basic stuff. Easy to query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dashboard was ruthlessly simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List of monitors&lt;/li&gt;
&lt;li&gt;Status for each (up or down)&lt;/li&gt;
&lt;li&gt;Last check time&lt;/li&gt;
&lt;li&gt;Uptime percentage (last 24 hours)&lt;/li&gt;
&lt;li&gt;Button to add a monitor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No graph. No drill-down. No customization.&lt;/p&gt;

&lt;p&gt;People hated the simplicity at first. Then they realized they could find what they needed in 3 seconds instead of 3 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stripe Integration:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Took longer than expected. Their API is comprehensive but has a learning curve.&lt;/p&gt;

&lt;p&gt;Key parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhook handling for payment events (customer.subscription.created, charge.succeeded, etc)&lt;/li&gt;
&lt;li&gt;Free tier switching logic (auto-downgrade if payment fails)&lt;/li&gt;
&lt;li&gt;Webhook validation (verify it came from Stripe, not someone faking it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By 8pm Sunday: everything worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monday: Launch Day
&lt;/h2&gt;

&lt;p&gt;Deployed to DigitalOcean. Set up HTTPS (Let's Encrypt, free). Wrote landing page copy.&lt;/p&gt;

&lt;p&gt;Launched on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IndieHackers&lt;/strong&gt; (~20 signups)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to&lt;/strong&gt; (~15 signups)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reddit&lt;/strong&gt; comments in r/webdev, r/SaaS (~8 signups)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter&lt;/strong&gt; thread (~5 signups)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total: ~50 signups in 24 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Working
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Ruthless scope&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every feature request got filtered through: "Is this essential?"&lt;/p&gt;

&lt;p&gt;90% were "no."&lt;/p&gt;

&lt;p&gt;The 10% that were "yes" were: reliability, simplicity, price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Honest pricing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$12/month because that's what I'd pay. Not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$12.99 (avoiding the number 9)&lt;/li&gt;
&lt;li&gt;$19/month (higher price = more features in people's heads)&lt;/li&gt;
&lt;li&gt;Free trial then paywall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just: $0 for 10 monitors, $12/month for 25 monitors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Free tier that works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;10 monitors forever. No credit card. No time limit. No restrictions that disappear.&lt;/p&gt;

&lt;p&gt;This converts because people actually use it, find it useful, then pay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Personal support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Responded personally to every signup email, every question. Takes time but builds trust.&lt;/p&gt;

&lt;p&gt;People don't expect founders to read their emails. They're shocked when you do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Building in public&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Posted about building it on Twitter as it happened. People followed along. Built community before launch.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Not Working
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Discovery is brutal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Getting 50 users is easy. Getting 500 is hard. Getting 5000 is really hard.&lt;/p&gt;

&lt;p&gt;I don't have a marketing engine yet. Just me posting on niche communities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Feature requests pile up fast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everyone wants Slack integration. Some want SMS. Some want custom headers.&lt;/p&gt;

&lt;p&gt;I can build maybe 1 feature per week. Feedback comes in at 3 per day.&lt;/p&gt;

&lt;p&gt;This is a good problem (means people use it) but also stressful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Support takes time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even simple product = people have questions.&lt;/p&gt;

&lt;p&gt;"How do I monitor FTP?" (Can't, not HTTP)&lt;br&gt;
"Can I check every 5 seconds?" (No, I chose 30s for good reasons)&lt;br&gt;
"Will you integrate with Datadog?" (Not a priority)&lt;/p&gt;

&lt;p&gt;Each question is an email I read and respond to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Pricing might be wrong&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$36/month revenue, $8/month costs = technically profitable.&lt;/p&gt;

&lt;p&gt;But is $12/month the right price? Should it be $19? $5?&lt;/p&gt;

&lt;p&gt;I don't know yet. I'll know when growth slows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Technical
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Monitoring is harder than it looks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTP checks seem simple: send request, check response. The edges are where complexity hides.&lt;/p&gt;

&lt;p&gt;Timeouts, retries, false positives, concurrency limits. Hours spent on these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. SQLite is underrated&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was worried about scaling. Realized: 1000 monitors with 30-second checks = 2000 inserts/minute = no real load.&lt;/p&gt;

&lt;p&gt;SQLite handles this easily on a $5 box.&lt;/p&gt;

&lt;p&gt;Lesson: don't over-engineer for future scale you don't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Keep infrastructure simple&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Single DigitalOcean droplet. Single database. No message queue (yet). No microservices.&lt;/p&gt;

&lt;p&gt;If something breaks, I can SSH in and see exactly what's wrong.&lt;/p&gt;

&lt;p&gt;Complexity is the enemy of reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Building &amp;gt; Planning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I could have spent 3 months planning. Instead I built for 20 hours and learned more than I would in 3 months of planning.&lt;/p&gt;

&lt;p&gt;Users tell you what matters. Plans don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Bootstrapped is freedom&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No investor pressure to grow fast. No need for a VC exit.&lt;/p&gt;

&lt;p&gt;If this gets to $5k/month, I'm happy. If it stays at $36/month, I learned a ton.&lt;/p&gt;

&lt;p&gt;This takes pressure off and lets me make good long-term decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Support is a feature&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Replying to every email personally is slow and doesn't scale.&lt;/p&gt;

&lt;p&gt;But it's the moat right now. People feel heard. That's rare with SaaS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Honest language sells&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Simple uptime monitoring" beats "revolutionary monitoring platform"&lt;/p&gt;

&lt;p&gt;"$0 free or $12/month" beats "free trial, then $29/month"&lt;/p&gt;

&lt;p&gt;People respect transparency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Content &amp;gt;&amp;gt; Ads&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every signup came from content I made (Twitter thread, Dev.to, Reddit comments, IndieHackers post).&lt;/p&gt;

&lt;p&gt;Zero dollar spent on ads. Zero results from ads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Being early is useful&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Launching on IndieHackers while bootstrapping was interesting to people.&lt;/p&gt;

&lt;p&gt;Launching on PH/HN soon will be even more interesting (because it's real users, real revenue).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Niche communities &amp;gt; broad reach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;50 signups from targeted communities (r/webdev, r/SaaS) is worth more than 500 from random places.&lt;/p&gt;

&lt;p&gt;These communities have actual standards and actual users.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers (Full Transparency)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Launch week:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 signups&lt;/li&gt;
&lt;li&gt;5 paid customers (3 stuck around)&lt;/li&gt;
&lt;li&gt;$36 MRR&lt;/li&gt;
&lt;li&gt;400 site visitors&lt;/li&gt;
&lt;li&gt;0 dollar spent on marketing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$5/month: DigitalOcean droplet (1GB, 1 CPU)&lt;/li&gt;
&lt;li&gt;$10/year: domain&lt;/li&gt;
&lt;li&gt;~$0.70/month: Stripe processing fees&lt;/li&gt;
&lt;li&gt;Total: $8/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time invested:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 hours: building&lt;/li&gt;
&lt;li&gt;10 hours: thinking about it first&lt;/li&gt;
&lt;li&gt;5 hours: writing copy and posting&lt;/li&gt;
&lt;li&gt;Total: 35 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Current (1 week later):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 total signups&lt;/li&gt;
&lt;li&gt;3 active paid customers&lt;/li&gt;
&lt;li&gt;120+ sites being monitored&lt;/li&gt;
&lt;li&gt;99.8% uptime&lt;/li&gt;
&lt;li&gt;$36 MRR&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This week:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Talk to paying customers about what matters&lt;/li&gt;
&lt;li&gt;Fix bugs as they appear&lt;/li&gt;
&lt;li&gt;Write better documentation&lt;/li&gt;
&lt;li&gt;Respond to all feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next month:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slack integration (everyone asks for it)&lt;/li&gt;
&lt;li&gt;Better reporting (time-based uptime stats)&lt;/li&gt;
&lt;li&gt;API access (for advanced users)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In 3 months:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evaluate if this is a real business or a hobby&lt;/li&gt;
&lt;li&gt;Decide how much time to invest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In 1 year:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Either: $5k+ MRR (make it bigger)&lt;/li&gt;
&lt;li&gt;Or: Keep as small profitable product&lt;/li&gt;
&lt;li&gt;Or: Realize it doesn't work and move on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are acceptable outcomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;You don't need months of planning to build and launch something real.&lt;/p&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Problem you actually have&lt;/li&gt;
&lt;li&gt;Scope that fits in a weekend&lt;/li&gt;
&lt;li&gt;Willingness to ship rough and learn from users&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I could have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spent 3 months perfecting the UI&lt;/li&gt;
&lt;li&gt;Built 20 features nobody needs&lt;/li&gt;
&lt;li&gt;Over-engineered the infrastructure&lt;/li&gt;
&lt;li&gt;Procrastinated while "planning"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead I shipped in 3 days.&lt;/p&gt;

&lt;p&gt;That's the win.&lt;/p&gt;

&lt;p&gt;The specific product (uptime monitoring) is less important than the fact that I went from idea to production with real users in 72 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you're monitoring websites and tired of your current setup, check out PingSentry: &lt;a href="https://pingsentry.app" rel="noopener noreferrer"&gt;https://pingsentry.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Free tier is actually useful (10 monitors, forever, no credit card).&lt;/p&gt;

&lt;p&gt;Paid tier is $12/month (25 monitors) or $39/month (unlimited) if you want more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Questions I Know You'll Ask
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Will you keep building this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: If there's enough demand, yes. I'm bootstrapped, so it's long-term thinking. If I get to 100-200 paying customers, it becomes a real time investment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Why not open source it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Might do this eventually. For now, I want to own the customer relationship and understand the business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How long until you have 10 employees?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: If it gets there, great. But I'm optimizing for sustainability, not growth. A profitable 1-person business is better than a failing 10-person team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Isn't this saturated?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Yes. Uptime monitoring is commodity. But there's room for the simple, boring alternative to expensive platforms. That's my bet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What if Uptime Robot launches a $5/month tier?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: I'm probably in trouble. But they're not optimizing for simple + cheap. So I have time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;To everyone who signed up, tried it, and sent feedback. You made this real.&lt;/p&gt;

&lt;p&gt;To the dev community (Reddit, HN, Twitter, Dev.to) for amplifying my posts.&lt;/p&gt;

&lt;p&gt;To the bootstrapped SaaS community for showing that this path exists.&lt;/p&gt;

&lt;p&gt;If you're building something: ship it. Stop planning. Start shipping.&lt;/p&gt;

&lt;p&gt;That's it. That's the advice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connect
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://pingsentry.app" rel="noopener noreferrer"&gt;https://pingsentry.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://twitter.com/TheZilisch" rel="noopener noreferrer"&gt;@TheZilisch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:feedback@pingsentry.app"&gt;feedback@pingsentry.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>startup</category>
      <category>webdev</category>
      <category>node</category>
    </item>
  </channel>
</rss>
