<?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>Forem: MaxxMini</title>
    <description>The latest articles on Forem by MaxxMini (@maxxmini).</description>
    <link>https://forem.com/maxxmini</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%2F3773591%2Fbcf21d98-1095-45f0-a2e6-327367c65c15.png</url>
      <title>Forem: MaxxMini</title>
      <link>https://forem.com/maxxmini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/maxxmini"/>
    <language>en</language>
    <item>
      <title>Running an AI Agent 24/7 Taught Me These Security Lessons the Hard Way</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Sat, 28 Mar 2026 00:07:04 +0000</pubDate>
      <link>https://forem.com/maxxmini/running-an-ai-agent-247-taught-me-these-security-lessons-the-hard-way-1456</link>
      <guid>https://forem.com/maxxmini/running-an-ai-agent-247-taught-me-these-security-lessons-the-hard-way-1456</guid>
      <description>&lt;p&gt;I've been running an autonomous AI agent on a Mac Mini 24/7 for over a month. It manages multiple businesses, publishes content, monitors accounts, and makes decisions while I sleep.&lt;/p&gt;

&lt;p&gt;It's also gotten shadow-banned, suspended from platforms, and nearly leaked credentials. Twice.&lt;/p&gt;

&lt;p&gt;Here's everything that went wrong and the security architecture I built to prevent it from happening again.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Shadow Ban That Took 3 Days to Notice
&lt;/h2&gt;

&lt;p&gt;My agent was happily posting to a social platform. Engagement was growing. Then — silence. No errors, no warnings, no rejection messages. Posts were going through successfully (200 OK), but nobody could see them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow bans are invisible to the banned account.&lt;/strong&gt; My agent's monitoring looked at "did the post succeed?" not "can anyone else see it?"&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before any platform activity:
&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rate limiter that tracks every external action across every platform. Not just API rate limits — &lt;em&gt;behavioral&lt;/em&gt; limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Max posts per day per platform&lt;/li&gt;
&lt;li&gt;Minimum time between actions&lt;/li&gt;
&lt;li&gt;Cool-down periods after account creation&lt;/li&gt;
&lt;li&gt;Platform-specific rules (some need aged accounts, others check browser fingerprints)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; API success ≠ visible to humans. Always verify from an external perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Credential Leak That Almost Happened
&lt;/h2&gt;

&lt;p&gt;My agent writes daily logs. Detailed ones. One day I noticed an API key in a log file that was about to be committed to a git repo.&lt;/p&gt;

&lt;p&gt;The agent wasn't trying to leak anything — it was logging a failed API call, and the error message included the full request headers. Including the Authorization header.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;Three layers of defense:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: Credential isolation&lt;/strong&gt; — All secrets live in one file with &lt;code&gt;chmod 600&lt;/code&gt;. The agent reads credentials through a helper, never stores them in variables that get logged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Git pre-commit scanning&lt;/strong&gt; — Before any &lt;code&gt;git push&lt;/code&gt;, a hook scans for patterns that look like tokens, API keys, or passwords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: File permission enforcement&lt;/strong&gt; — Credential files are &lt;code&gt;chmod 600&lt;/code&gt;. Log directories are &lt;code&gt;chmod 700&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Agents are verbose loggers by nature. Treat every log line as potentially public.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Platform Suspension From Bulk Activity
&lt;/h2&gt;

&lt;p&gt;Day 1 on a new blogging platform: my agent published 7 articles. All high-quality, well-formatted, properly tagged content.&lt;/p&gt;

&lt;p&gt;Result: 3 articles deleted by moderation. Not because the content was bad — because &lt;strong&gt;no human publishes 7 articles in one day on a new account&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;The agent now has a "new account warming" protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Week 1: Read only, maybe 1 comment&lt;/li&gt;
&lt;li&gt;Week 2: 1 post, 2-3 comments&lt;/li&gt;
&lt;li&gt;Week 3+: Normal cadence (still conservative)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Platforms profile behavior, not content. A new account doing anything at volume = bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Token Refresh Race Condition
&lt;/h2&gt;

&lt;p&gt;OAuth tokens expire. My agent has a refresh mechanism. But when two cron jobs fire at the same time, both try to refresh the token simultaneously. One succeeds, invalidating the token the other one is using. Second job fails. Retry? It tries to refresh again — but the refresh token was already rotated.&lt;/p&gt;

&lt;p&gt;Result: Complete lockout requiring manual re-authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;Token refresh is now serialized through a single process with file locking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified version
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;FileLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.token-lock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;token_expired&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;new_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;save_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;load_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a recovery hierarchy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Try refresh token&lt;/li&gt;
&lt;li&gt;Try stored session cookies&lt;/li&gt;
&lt;li&gt;Try browser re-login automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then&lt;/strong&gt; ask the human&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Autonomous systems need autonomous recovery. Asking the human should be the last resort.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Time Zone Trap
&lt;/h2&gt;

&lt;p&gt;My agent runs in Seoul (UTC+9). Some platforms flag accounts active 24/7 — humans sleep. My agent doesn't.&lt;/p&gt;

&lt;p&gt;I got flagged for "impossible activity patterns" — posting at 3 AM and 3 PM with equal frequency.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Hard curfew rules:
# 09-22 KST: External activity OK
# 22-09 KST: Research + local work ONLY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Between 11 PM and 9 AM, the agent can research and write drafts, but it &lt;strong&gt;cannot&lt;/strong&gt; publish, push code, comment, or send any external requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Platforms expect human patterns. An agent that never sleeps looks like a bot. Because it is one.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Cascading Failure
&lt;/h2&gt;

&lt;p&gt;One morning I found my agent had burned through 47 API calls trying to upload a video that kept failing. Each retry was identical. Each failure was the same error.&lt;/p&gt;

&lt;p&gt;A 403 (permission denied) was being treated the same as a 500 (server error).&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;Error classification with different strategies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error Type&lt;/th&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4xx (client error)&lt;/td&gt;
&lt;td&gt;Stop immediately, log, alert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429 (rate limit)&lt;/td&gt;
&lt;td&gt;Exponential backoff, respect Retry-After&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5xx (server error)&lt;/td&gt;
&lt;td&gt;Retry 3x with backoff, then stop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network timeout&lt;/td&gt;
&lt;td&gt;Retry 2x, then skip to next task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus a &lt;strong&gt;circuit breaker&lt;/strong&gt;: if any platform returns 3+ errors in a row, all activity on that platform pauses for 1 hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Blind retries amplify failures. Classify errors before deciding what to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The "Helpful" Agent That Overshared
&lt;/h2&gt;

&lt;p&gt;In a group chat, someone asked about our tech stack. My agent — trying to be helpful — shared specific infrastructure details including server specs and internal tools.&lt;/p&gt;

&lt;p&gt;None of it was secret, exactly. But aggregated, it painted a very detailed picture of operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;Context-aware information sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private chat with owner&lt;/strong&gt;: Full access, no filters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group chat&lt;/strong&gt;: Public information only, no internal metrics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External platforms&lt;/strong&gt;: Curated persona, no operational details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Agents don't have social instincts. They'll share everything unless explicitly told what's private.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Architecture Today
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│         CORE RULES (always loaded)  │
│  • Time curfew (09-22 external)     │
│  • Rate limiter (per-platform)      │
│  • Error classification             │
│  • Credential isolation             │
├─────────────────────────────────────┤
│         PER-PLATFORM RULES          │
│  • Account age requirements         │
│  • Action limits (posts/comments)   │
│  • Warm-up protocols                │
│  • Platform-specific gotchas        │
├─────────────────────────────────────┤
│         RECOVERY HIERARCHY          │
│  1. Auto-retry (classified errors)  │
│  2. Token refresh (serialized)      │
│  3. Session recovery (cookies)      │
│  4. Circuit breaker (pause)         │
│  5. Human escalation (last resort)  │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with security, not add it after failures.&lt;/strong&gt; Every failure above was preventable with 30 minutes of upfront thinking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assume every platform has bot detection.&lt;/strong&gt; The question is how aggressive it is, not whether it exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log everything, share nothing.&lt;/strong&gt; Internal logs should be verbose. External-facing actions should be minimal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test with burner accounts first.&lt;/strong&gt; Before connecting real accounts to an AI agent, test automation on throwaway accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still Learning
&lt;/h2&gt;

&lt;p&gt;I'm 6 weeks in. New failure modes appear regularly. Last week it was a platform that changed their API without notice. The week before, a rate limit that wasn't documented anywhere.&lt;/p&gt;

&lt;p&gt;The goal isn't zero failures — it's fast recovery and no repeated failures. Every incident becomes a rule. Every rule prevents the next incident.&lt;/p&gt;

&lt;p&gt;That's the real security model: &lt;strong&gt;an agent that gets smarter about its own vulnerabilities over time.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Running AI agents autonomously means security can't be an afterthought. Here are some tools I've built along the way:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;📦 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=security-lessons-bottom" rel="noopener noreferrer"&gt;The $0 Developer Playbook&lt;/a&gt;&lt;/strong&gt; — Free toolkit including automation safety patterns&lt;/p&gt;

&lt;p&gt;🛠️ &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/ygivi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=security-lessons-bottom" rel="noopener noreferrer"&gt;Complete Dev Toolkit&lt;/a&gt;&lt;/strong&gt; — Project management templates with built-in QA checklists&lt;/p&gt;

&lt;p&gt;💰 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=security-lessons-store" rel="noopener noreferrer"&gt;Browse all free resources →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Automated a YouTube Channel With AI — 29 Videos, $0 Budget, Here's What Happened</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Fri, 27 Mar 2026 04:10:53 +0000</pubDate>
      <link>https://forem.com/maxxmini/i-automated-a-youtube-channel-with-ai-29-videos-0-budget-heres-what-happened-1ef8</link>
      <guid>https://forem.com/maxxmini/i-automated-a-youtube-channel-with-ai-29-videos-0-budget-heres-what-happened-1ef8</guid>
      <description>&lt;p&gt;Three weeks ago, I had zero videos on YouTube. Today I have 29 — including 4 long-form videos over 7 minutes each. Total budget: $0. Total manual editing: zero.&lt;/p&gt;

&lt;p&gt;Here's exactly how the pipeline works, what broke along the way, and the brutal truth about what happened to the views.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;Every video goes through the same automated pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script → TTS → Subtitle Sync → B-roll/Infographics → Build → Upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step is a Python script. The whole thing runs on a Mac Mini with 64GB unified memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Script Generation
&lt;/h3&gt;

&lt;p&gt;The AI agent writes scripts following a proven formula I discovered through A/B testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hook&lt;/strong&gt;: Contrarian statement ("Don't save in a savings account")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Korean financial context&lt;/strong&gt;: Specific to Korea's jeonse system, mandatory insurance culture, salary structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concrete numbers&lt;/strong&gt;: Always include specific amounts (₩270,000/month, not "a lot")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal tone&lt;/strong&gt;: Written as if sharing with a friend, not lecturing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The winning formula? &lt;strong&gt;Counter-intuitive claim + Korea-specific context + real numbers&lt;/strong&gt;. My best performer (1,635 views) was "Don't Follow the 50/30/20 Rule" — because in Korea, housing costs alone eat 50%+ of most salaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Text-to-Speech
&lt;/h3&gt;

&lt;p&gt;I use Edge TTS (Microsoft's free API) with specific rate tuning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Korean: +45% speed (natural conversation pace)
# English: +15% speed  
&lt;/span&gt;&lt;span class="n"&gt;edge_tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Communicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ko-KR-SunHiNeural&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+45%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting the speed right took multiple iterations. +95% sounded like an auctioneer. -30% sounded like a meditation app. +45% hits the sweet spot for Korean financial content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Subtitle Sync (The Hardest Part)
&lt;/h3&gt;

&lt;p&gt;This is where things got interesting. I have a silence compression script that removes dead air from TTS output. Great for pacing — terrible for subtitle timing.&lt;/p&gt;

&lt;p&gt;The subtitles would drift up to &lt;strong&gt;7 seconds&lt;/strong&gt; out of sync after silence removal.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;subtitle_sync.py&lt;/code&gt; — a custom script that uses MLX Whisper (running locally on Apple Silicon) to generate word-level timestamps, then realigns every subtitle line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# After silence compression, ALWAYS run:
&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;subtitle_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mp3&lt;/span&gt; &lt;span class="n"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ass&lt;/span&gt;

&lt;span class="c1"&gt;# Before upload, ALWAYS verify:
&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;subtitle_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mp3&lt;/span&gt; &lt;span class="n"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Whisper model (&lt;code&gt;mlx-community/whisper-large-v3-turbo&lt;/code&gt;) runs entirely on-device. No API costs, no latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Visual Assets
&lt;/h3&gt;

&lt;p&gt;For Shorts (&amp;lt; 60s): dark gradient background + ASS subtitles burned in (FontSize 78, bold, outline 4 — optimized for mobile).&lt;/p&gt;

&lt;p&gt;For Long-form (7-10 min): 4 custom infographics (matplotlib) + 20 B-roll clips + automated timeline assembly via &lt;code&gt;build_video.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Upload
&lt;/h3&gt;

&lt;p&gt;YouTube Data API v3 handles uploads. Metadata (title, description, tags, playlist) is pre-generated in JSON. OAuth token refreshes automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📦 &lt;strong&gt;Building something similar?&lt;/strong&gt; The &lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=youtube-auto-mid" rel="noopener noreferrer"&gt;$0 Developer Playbook&lt;/a&gt; covers automation patterns, rate limiting, and self-healing architectures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Results: Honest Numbers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First 8 days (Mar 6-12):&lt;/strong&gt; 🚀&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 videos, 6,194 total views&lt;/li&gt;
&lt;li&gt;Best video: 1,635 views ("Don't Follow 50/30/20")&lt;/li&gt;
&lt;li&gt;7 subscribers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Days 9-23 (Mar 13-26):&lt;/strong&gt; 📉&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15 more videos (including 4 long-form 7+ min)&lt;/li&gt;
&lt;li&gt;Views per day: &lt;strong&gt;0-1&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;New subscribers: &lt;strong&gt;0&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Algorithm Cliff
&lt;/h2&gt;

&lt;p&gt;My theory:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;8 videos in 8 days&lt;/strong&gt; from a new channel = spam pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One bad video&lt;/strong&gt; (13 views on a saturated topic) killed momentum&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New channel test period&lt;/strong&gt; ended on Day 8&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;✅ "Don't do X" + Korea-specific context = 1,635 views&lt;br&gt;
✅ Automated pipeline = consistent quality at scale&lt;br&gt;
❌ Daily uploads = algorithm punishment&lt;br&gt;
❌ Oversaturated topics = dead on arrival&lt;br&gt;
❌ More videos ≠ more views&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real lesson: Automation solves production. It doesn't solve distribution.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack (All Free)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Script writing&lt;/td&gt;
&lt;td&gt;AI agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTS&lt;/td&gt;
&lt;td&gt;Edge TTS (Microsoft)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subtitle sync&lt;/td&gt;
&lt;td&gt;MLX Whisper (local)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video build&lt;/td&gt;
&lt;td&gt;FFmpeg + Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infographics&lt;/td&gt;
&lt;td&gt;Matplotlib&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload&lt;/td&gt;
&lt;td&gt;YouTube Data API v3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  See the Videos
&lt;/h2&gt;

&lt;p&gt;🎬 &lt;strong&gt;Best performer&lt;/strong&gt; (1,635 views): &lt;a href="https://youtube.com/shorts/sOCGz31z5j4" rel="noopener noreferrer"&gt;Don't Follow the 50/30/20 Rule&lt;/a&gt;&lt;br&gt;
🎬 &lt;strong&gt;First long-form&lt;/strong&gt; (7m20s): &lt;a href="https://youtube.com/watch?v=Vjym03QPGQM" rel="noopener noreferrer"&gt;Complete Guide to Splitting Bank Accounts&lt;/a&gt;&lt;br&gt;
🎬 &lt;strong&gt;Latest long-form&lt;/strong&gt; (8m23s): &lt;a href="https://youtube.com/watch?v=pfkt40vh2sI" rel="noopener noreferrer"&gt;The Credit Card Points Trap&lt;/a&gt;&lt;br&gt;
📺 &lt;strong&gt;Channel&lt;/strong&gt;: &lt;a href="https://youtube.com/@MaxMiniDev" rel="noopener noreferrer"&gt;MaxMini Dev&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The pipeline works. The content quality is there. Now it's a distribution problem — and that's a much harder automation challenge.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building in public — from first video to (hopefully) monetization:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🛠️ &lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=youtube-automation" rel="noopener noreferrer"&gt;The $0 Developer Playbook&lt;/a&gt; | 📦 &lt;a href="https://maxmini.gumroad.com/l/ygivi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=youtube-automation" rel="noopener noreferrer"&gt;Complete Indie Dev Toolkit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>youtube</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>I Replaced Cloud AI APIs With a $600 Mac Mini — Here's What Actually Works</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Fri, 27 Mar 2026 01:40:12 +0000</pubDate>
      <link>https://forem.com/maxxmini/i-replaced-cloud-ai-apis-with-a-600-mac-mini-heres-what-actually-works-4iae</link>
      <guid>https://forem.com/maxxmini/i-replaced-cloud-ai-apis-with-a-600-mac-mini-heres-what-actually-works-4iae</guid>
      <description>&lt;p&gt;I've been running AI models locally on a Mac Mini M4 (64GB unified memory) for three months straight. Not for fun — this machine runs my entire business automation 24/7.&lt;/p&gt;

&lt;p&gt;Here's the honest breakdown of every model I've tested, what actually works, and when local LLMs are a waste of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine&lt;/strong&gt;: Mac Mini M4 with 64GB unified memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: Ollama (dead simple, just works)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case&lt;/strong&gt;: Content generation, code review, summarization, translation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Models tested&lt;/strong&gt;: qwen3:30b, devstral-small-2, qwen3:14b, gemma3:27b, qwen3:8b, deepseek-r1:70b, llama3.1:70b&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost after 3 months: $0 in API fees. The machine paid for itself in month 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tier List (Brutal Honesty)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  S-Tier: Daily Drivers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Qwen3 30B&lt;/strong&gt; — The sweet spot. Fast enough for real-time use, smart enough for 90% of tasks. I use this for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blog post drafts and rewrites&lt;/li&gt;
&lt;li&gt;Korean ↔ English translation (surprisingly good)&lt;/li&gt;
&lt;li&gt;Code explanation and documentation&lt;/li&gt;
&lt;li&gt;First-pass content review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generation speed: ~25 tokens/sec on M4 64GB. That's fast enough to feel like a conversation, not a waiting game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma3 27B&lt;/strong&gt; — Google's dark horse. Better than Qwen for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured data extraction&lt;/li&gt;
&lt;li&gt;Following complex formatting instructions&lt;/li&gt;
&lt;li&gt;Technical writing with specific constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Slightly slower than Qwen3 30B but more reliable at following instructions precisely.&lt;/p&gt;

&lt;h3&gt;
  
  
  A-Tier: Specialized Use
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Devstral Small 2&lt;/strong&gt; — Mistral's coding model. When I need code-specific tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refactoring suggestions&lt;/li&gt;
&lt;li&gt;Bug detection in Python/JS&lt;/li&gt;
&lt;li&gt;Generating test cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not great for general conversation, but for code? It punches way above its weight class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qwen3 14B&lt;/strong&gt; — The "good enough" model. When 30B is overkill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick summaries&lt;/li&gt;
&lt;li&gt;Simple translations&lt;/li&gt;
&lt;li&gt;Template filling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runs at ~40 tokens/sec. For batch processing 50 product descriptions? This is the one.&lt;/p&gt;

&lt;h3&gt;
  
  
  B-Tier: Impressive but Impractical
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DeepSeek-R1 70B&lt;/strong&gt; — The thinking model. It's genuinely smart. The chain-of-thought reasoning is impressive. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~8 tokens/sec on 64GB (memory pressure is real)&lt;/li&gt;
&lt;li&gt;Takes 30-60 seconds just to start generating&lt;/li&gt;
&lt;li&gt;Eats all your RAM — nothing else runs smoothly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use it maybe once a week for complex analysis. The rest of the time? Qwen3 30B at 3x the speed gives 95% of the quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Llama 3.1 70B&lt;/strong&gt; — Meta's flagship. Similar problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too slow for interactive use&lt;/li&gt;
&lt;li&gt;Great quality, terrible experience&lt;/li&gt;
&lt;li&gt;Swap death if you try to multitask&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  C-Tier: Skip It
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Qwen3 8B&lt;/strong&gt; — Too dumb for anything that matters. Saves RAM but the quality drop isn't worth it. If you need something this small, just use the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers That Matter
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Speed (tok/s)&lt;/th&gt;
&lt;th&gt;RAM Used&lt;/th&gt;
&lt;th&gt;Quality (1-10)&lt;/th&gt;
&lt;th&gt;Daily Use?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 30B&lt;/td&gt;
&lt;td&gt;~25&lt;/td&gt;
&lt;td&gt;22GB&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;✅ Primary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma3 27B&lt;/td&gt;
&lt;td&gt;~22&lt;/td&gt;
&lt;td&gt;20GB&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;✅ Formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Devstral Small&lt;/td&gt;
&lt;td&gt;~35&lt;/td&gt;
&lt;td&gt;12GB&lt;/td&gt;
&lt;td&gt;7 (code: 9)&lt;/td&gt;
&lt;td&gt;✅ Code only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 14B&lt;/td&gt;
&lt;td&gt;~40&lt;/td&gt;
&lt;td&gt;11GB&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;✅ Batch jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-R1 70B&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;td&gt;45GB&lt;/td&gt;
&lt;td&gt;9.5&lt;/td&gt;
&lt;td&gt;⚠️ Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1 70B&lt;/td&gt;
&lt;td&gt;~10&lt;/td&gt;
&lt;td&gt;42GB&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;❌ Retired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 8B&lt;/td&gt;
&lt;td&gt;~55&lt;/td&gt;
&lt;td&gt;6GB&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;❌ Too weak&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When Local LLMs Are a Waste of Time
&lt;/h2&gt;

&lt;p&gt;Let me save you the experimentation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't bother with local if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need GPT-4/Claude-level reasoning consistently&lt;/li&gt;
&lt;li&gt;Your tasks require real-time conversation with users&lt;/li&gt;
&lt;li&gt;You're processing images or audio (multimodal local = pain)&lt;/li&gt;
&lt;li&gt;You need the model to stay updated on current events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Local absolutely wins when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy matters (financial data, personal info)&lt;/li&gt;
&lt;li&gt;You're doing batch processing (translate 200 descriptions = $0)&lt;/li&gt;
&lt;li&gt;Uptime is critical (no API outages, no rate limits)&lt;/li&gt;
&lt;li&gt;You're iterating fast (no token counting, no billing anxiety)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hidden Benefit Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;When AI costs $0, you use it differently. I run my LLM on every commit message, every blog draft, every product description — because why not? There's no meter running.&lt;/p&gt;

&lt;p&gt;With APIs, I'd think twice about "wasting" tokens on a commit message. With local? I generate 5 variations and pick the best one. The quality compound effect is massive.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Actual Daily Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6 AM:  Qwen3 30B generates blog drafts from outlines
9 AM:  Devstral reviews overnight code changes
12 PM: Qwen3 14B batch-processes product descriptions
3 PM:  Gemma3 27B formats and structures data exports
Night: DeepSeek-R1 70B analyzes weekly business metrics (runs while I sleep)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total API cost: $0/month&lt;br&gt;
Electricity: ~$8/month (Mac Mini M4 is stupidly efficient)&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Do This?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you have a Mac with 32GB+&lt;/strong&gt;: Yes, start with Ollama + Qwen3 (14B for 32GB, 30B for 64GB). You'll be shocked how capable it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you have 16GB or less&lt;/strong&gt;: Skip it. The experience is terrible. Just use the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're on Linux with an NVIDIA GPU&lt;/strong&gt;: Even better. You'll get 2-3x the speed I get on Apple Silicon.&lt;/p&gt;

&lt;p&gt;The $600 Mac Mini running local AI 24/7 was the best infrastructure investment I've made this year. Not because any single model beats GPT-4 — it doesn't. But because "free" and "always available" changes how you work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run 6 businesses from this Mac Mini using AI agents and local LLMs. If you're building your own automation stack, here are some resources that might help:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;📦 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=local-llm-bottom" rel="noopener noreferrer"&gt;The $0 Developer Playbook&lt;/a&gt;&lt;/strong&gt; — The complete free toolkit I use daily&lt;/p&gt;

&lt;p&gt;🎮 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/ygivi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=local-llm-bottom" rel="noopener noreferrer"&gt;Indie Game Dev Complete Toolkit&lt;/a&gt;&lt;/strong&gt; — If you're building games on a budget&lt;/p&gt;

&lt;p&gt;💰 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=local-llm-store" rel="noopener noreferrer"&gt;Browse all free templates →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Run 6 Businesses With Cron Jobs and Sub-Agents — Here's the Architecture</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Thu, 26 Mar 2026 07:08:35 +0000</pubDate>
      <link>https://forem.com/maxxmini/i-run-6-businesses-with-cron-jobs-and-sub-agents-heres-the-architecture-3ie6</link>
      <guid>https://forem.com/maxxmini/i-run-6-businesses-with-cron-jobs-and-sub-agents-heres-the-architecture-3ie6</guid>
      <description>&lt;p&gt;I'm running 6 online businesses simultaneously.&lt;/p&gt;

&lt;p&gt;Not "thinking about starting" them. Actually operating them — uploading content, publishing products, writing blog posts, monitoring analytics, fixing bugs.&lt;/p&gt;

&lt;p&gt;The secret? I'm not doing most of it myself.&lt;/p&gt;

&lt;p&gt;I have a Mac Mini running 24/7 with an orchestration system that schedules AI sub-agents to handle specific tasks across all 6 businesses. Each business has its own "division file" — a living document that tracks status, KPIs, next actions, and lessons learned.&lt;/p&gt;

&lt;p&gt;Here's exactly how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Context Switching Kills Solo Founders
&lt;/h2&gt;

&lt;p&gt;When you're running one business, focus is easy. When you're running six, your day looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;9:00 — Check YouTube analytics&lt;/li&gt;
&lt;li&gt;9:15 — Fix a broken link on Gumroad&lt;/li&gt;
&lt;li&gt;9:30 — Write a blog post for Hashnode&lt;/li&gt;
&lt;li&gt;10:00 — Debug a CSS issue on your SaaS&lt;/li&gt;
&lt;li&gt;10:30 — Realize you forgot to upload yesterday's short&lt;/li&gt;
&lt;li&gt;11:00 — Context switch back to Gumroad, forget where you were&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;The cognitive overhead isn't the work itself. It's the &lt;strong&gt;switching cost&lt;/strong&gt;. Every time you change contexts, you lose 15-20 minutes ramping back up.&lt;/p&gt;

&lt;p&gt;My solution: &lt;strong&gt;delegate context switching to machines&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Registry + Divisions + Cron
&lt;/h2&gt;

&lt;p&gt;The system has three layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Business Registry (Single Source of Truth)
&lt;/h3&gt;

&lt;p&gt;Every business lives in a JSON registry. If it's not in the registry, it doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"youtube-shorts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YouTube Shorts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kpi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"subscribers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"total_views"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"videos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gumroad"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gumroad Digital Products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kpi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"products_live"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sales"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"revenue_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A CLI tool lets any agent query, update, or add businesses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dashboard view&lt;/span&gt;
python3 biz-registry.py dashboard

&lt;span class="c"&gt;# Update a KPI&lt;/span&gt;
python3 biz-registry.py kpi youtube-shorts subscribers 10

&lt;span class="c"&gt;# Inter-agent messaging&lt;/span&gt;
python3 biz-registry.py msg send youtube-engine gumroad-engine &lt;span class="s2"&gt;"New short uploaded, add CTA link"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: No hardcoding. When I add a 7th business, every agent automatically discovers it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Division Files (Business Memory)
&lt;/h3&gt;

&lt;p&gt;Each business has a markdown file that serves as its "brain":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# YouTube Shorts — Division Memory&lt;/span&gt;

&lt;span class="gu"&gt;## Current Status&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 23 videos published
&lt;span class="p"&gt;-&lt;/span&gt; Algorithm cliff since March 12 (0 views)
&lt;span class="p"&gt;-&lt;/span&gt; 3 shorts ready in pipeline

&lt;span class="gu"&gt;## Next Actions&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Upload kr-5am-club short (verified)
&lt;span class="p"&gt;2.&lt;/span&gt; Test new thumbnail style
&lt;span class="p"&gt;3.&lt;/span&gt; Analyze competitor posting times

&lt;span class="gu"&gt;## Lessons Learned&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Subtitle font size 78 (mobile readability)
&lt;span class="p"&gt;-&lt;/span&gt; Korean TTS rate: +45% (sweet spot)
&lt;span class="p"&gt;-&lt;/span&gt; Mid-video CTA &amp;gt; end CTA (4x effectiveness)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key insight: &lt;strong&gt;AI agents don't have memory between sessions&lt;/strong&gt;. Division files ARE their memory. When a sub-agent wakes up, it reads the division file, knows exactly where things stand, and picks up where it left off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: Cron Orchestration
&lt;/h3&gt;

&lt;p&gt;The cron layer schedules everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every 15 min  → Business Stepper (picks highest-impact task)
Every 30 min  → Revenue Engine (analytics + optimization)
Every 6 hours → Content Pipeline (draft + queue management)
Daily 09:00   → Morning Launch (publishing queue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each cron job spawns an &lt;strong&gt;isolated sub-agent&lt;/strong&gt; with a specific mission. The sub-agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the registry dashboard&lt;/li&gt;
&lt;li&gt;Picks the most impactful business (priority × staleness)&lt;/li&gt;
&lt;li&gt;Loads that business's division file&lt;/li&gt;
&lt;li&gt;Executes ONE action (8-minute hard cap)&lt;/li&gt;
&lt;li&gt;Updates the division file + logs the action&lt;/li&gt;
&lt;li&gt;Dies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No long-running processes. No state accumulation. No memory leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Business Stepper: Automated Prioritization
&lt;/h2&gt;

&lt;p&gt;The most interesting piece is the "Business Stepper" — the agent that decides &lt;strong&gt;what to work on next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The algorithm is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Score = Priority Weight × Days Since Last Update

P1 = 3x weight
P2 = 2x weight  
P3 = 1x weight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A P1 business that hasn't been touched in 2 days scores higher than a P3 business that's been stale for a week. This prevents any business from being completely neglected while ensuring high-priority work gets done first.&lt;/p&gt;

&lt;p&gt;If the chosen business has a blocker (waiting for login, API down, etc.), it automatically falls through to the next one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inter-Agent Communication
&lt;/h2&gt;

&lt;p&gt;Agents need to talk to each other. The YouTube engine discovers a trending topic → the blog engine should write about it. The Gumroad engine adds a new product → the YouTube engine should add CTA links.&lt;/p&gt;

&lt;p&gt;I built a simple message queue into the registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# YouTube engine sends message&lt;/span&gt;
biz-registry.py msg send youtube gumroad &lt;span class="s2"&gt;"New short uploaded: insurance-trap"&lt;/span&gt;

&lt;span class="c"&gt;# Gumroad engine checks inbox&lt;/span&gt;
biz-registry.py msg check gumroad
&lt;span class="c"&gt;# → [youtube]: "New short uploaded: insurance-trap"&lt;/span&gt;

&lt;span class="c"&gt;# Process and acknowledge&lt;/span&gt;
biz-registry.py msg pop gumroad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No external message broker. No Redis. No RabbitMQ. Just a JSON array in the registry file. At my scale (6 businesses, ~50 messages/day), this is perfectly adequate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time-Aware Execution
&lt;/h2&gt;

&lt;p&gt;Not every action can happen at any time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09:00-22:00 → Full operations (publish, post, push to GitHub)
22:00-09:00 → Research and production only (drafts, analysis, code)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't just politeness — it's &lt;strong&gt;platform safety&lt;/strong&gt;. Publishing at 3 AM from a known timezone creates suspicious patterns. Rate limiters check every action against platform-specific rules before execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned After 3 Weeks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Division Files &amp;gt; Databases
&lt;/h3&gt;

&lt;p&gt;I tried SQLite. I tried JSON APIs. Nothing beats a markdown file that a human can read and an AI can parse. When something breaks, I open the file and immediately see what happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 8-Minute Hard Cap Changes Everything
&lt;/h3&gt;

&lt;p&gt;Without a time limit, agents rabbit-hole. "Let me just fix this one more thing" turns into 45 minutes of cascading changes. The hard cap forces atomic, completable actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Single Action Per Cycle Prevents Cascading Failures
&lt;/h3&gt;

&lt;p&gt;If an agent tries to do 3 things and the 2nd one fails, you get a partially-updated state that's hard to recover from. One action, one update, clean exit.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Registry-Driven Discovery &amp;gt; Hardcoding
&lt;/h3&gt;

&lt;p&gt;When I added business #5, I didn't change any agent code. I ran &lt;code&gt;biz-registry.py add&lt;/code&gt; and the stepper started picking it up automatically next cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 36% of What You Build Gets Deleted
&lt;/h3&gt;

&lt;p&gt;Across platforms, about a third of published content gets moderated away. The architecture handles this gracefully because division files track what happened, not just what succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;After 3 weeks of this system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;23 YouTube videos&lt;/strong&gt; published (mix of shorts + long-form)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;16 digital products&lt;/strong&gt; live on Gumroad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 blog posts&lt;/strong&gt; on Hashnode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 web apps&lt;/strong&gt; deployed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0 revenue&lt;/strong&gt; (being honest — traffic is the bottleneck, not production)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system is excellent at &lt;strong&gt;producing&lt;/strong&gt;. The missing piece is &lt;strong&gt;distribution&lt;/strong&gt; — which turns out to be the hard part that can't easily be automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try This Architecture
&lt;/h2&gt;

&lt;p&gt;If you're juggling multiple projects, here's the minimum viable version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One JSON file&lt;/strong&gt; listing all projects with status + priority&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One markdown file per project&lt;/strong&gt; with status, next actions, lessons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One cron job&lt;/strong&gt; that reads the JSON, picks the stalest high-priority project, and does one thing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need AI agents for this. A simple script that opens the right file and reminds you what to do next is 80% of the value.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building 6 businesses simultaneously with AI agents. Currently at $0 MRR but the production pipeline is running smoothly. Follow along for weekly updates.&lt;/em&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;📘 &lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=cron-subagents-bottom" rel="noopener noreferrer"&gt;The $0 Developer Playbook&lt;/a&gt; — Free guide to building with zero budget&lt;/li&gt;
&lt;li&gt;🎮 &lt;a href="https://maxmini.gumroad.com/l/ygivi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=cron-subagents-bottom" rel="noopener noreferrer"&gt;Complete Indie Game Dev Toolkit&lt;/a&gt; — Everything you need to ship your first game&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📚 More in This Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/maxxmini/how-i-set-up-an-ai-agent-that-runs-247-on-a-mac-mini-openclaw-m3l"&gt;How I Set Up an AI Agent That Runs 24/7 on a Mac Mini&lt;/a&gt; — Where the 24/7 automation journey started (416+ views)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/maxxmini/how-i-built-a-self-healing-automation-system-that-runs-247-without-me-1lio"&gt;How I Built a Self-Healing Automation System That Runs 24/7 Without Me&lt;/a&gt; — The recovery patterns that keep these cron jobs alive&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How I Built a Self-Healing Automation System That Runs 24/7 Without Me</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Thu, 26 Mar 2026 06:43:23 +0000</pubDate>
      <link>https://forem.com/maxxmini/how-i-built-a-self-healing-automation-system-that-runs-247-without-me-1lio</link>
      <guid>https://forem.com/maxxmini/how-i-built-a-self-healing-automation-system-that-runs-247-without-me-1lio</guid>
      <description>&lt;p&gt;My automation system crashed 47 times in the first week.&lt;/p&gt;

&lt;p&gt;Not because the code was bad. Because &lt;strong&gt;the real world is hostile to long-running processes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tokens expire. APIs rate-limit you. DNS resolves wrong. Memory leaks. Browser tabs zombie out. SSH connections drop at 3 AM because your ISP decided to "optimize" something.&lt;/p&gt;

&lt;p&gt;I spent a week making it work. Then I spent three weeks making it &lt;strong&gt;stay working&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's every pattern I used to turn a fragile mess into something that's been running for 3 weeks without manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Scripts That Work Once
&lt;/h2&gt;

&lt;p&gt;If you've ever written automation, you know this feeling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Run 1: Perfect
✅ Run 2: Perfect  
❌ Run 3: "Token expired"
❌ Run 4: "Rate limited"
❌ Run 5: "ECONNRESET"
❌ Run 6: "Out of memory"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script itself is fine. The &lt;strong&gt;environment&lt;/strong&gt; is the enemy.&lt;/p&gt;

&lt;p&gt;My setup: a Mac Mini running 24/7, executing cron jobs every 15-30 minutes across 6 different businesses — YouTube uploads, blog publishing, product management, web scraping, email monitoring, and more.&lt;/p&gt;

&lt;p&gt;Each task touches 3-5 external services. Each service has its own failure mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Exponential Backoff with Jitter
&lt;/h2&gt;

&lt;p&gt;The first instinct when something fails: retry immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_with_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
            &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;jitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;jitter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jitter is critical. Without it, if 3 cron jobs all fail at the same time, they all retry at the same time, hit the same rate limit, and fail again. &lt;strong&gt;Thundering herd problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With jitter, retries spread out naturally. My rate limit violations dropped from ~12/day to ~1/week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: Token Refresh Before Expiry
&lt;/h2&gt;

&lt;p&gt;The obvious approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call API&lt;/li&gt;
&lt;li&gt;Get "token expired" error&lt;/li&gt;
&lt;li&gt;Refresh token&lt;/li&gt;
&lt;li&gt;Retry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The smarter approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Check token expiry before calling API&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;If expiring within 5 minutes, refresh proactively&lt;/li&gt;
&lt;li&gt;Call API with fresh token&lt;/li&gt;
&lt;li&gt;Never see the error
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_valid_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Refresh 5 minutes before actual expiry
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;save_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eliminated ~80% of my "random" failures. Most weren't random at all — they were predictable token expirations happening during long-running tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: The Heartbeat Monitor
&lt;/h2&gt;

&lt;p&gt;My system runs as background cron jobs. How do I know if something silently died?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heartbeats.&lt;/strong&gt; Every 30 minutes, the system checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are all expected cron jobs still scheduled?&lt;/li&gt;
&lt;li&gt;When did each job last run successfully?&lt;/li&gt;
&lt;li&gt;Is memory usage below threshold?&lt;/li&gt;
&lt;li&gt;Are external APIs responding?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a job hasn't run in 2x its expected interval, something's wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# heartbeat-state.json
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastChecks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;youtube-engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1711382400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blog-publisher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1711380600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email-monitor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1711382100&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thresholds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;youtube-engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# 1 hour
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blog-publisher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 24 hours
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email-monitor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;     &lt;span class="c1"&gt;# 30 minutes
&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;The heartbeat caught 3 "silent deaths" in the first week alone — jobs that crashed without error messages, just... stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Graceful Degradation
&lt;/h2&gt;

&lt;p&gt;Not all failures are equal. My ranking:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Critical&lt;/strong&gt;: Token expired → refresh immediately, retry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recoverable&lt;/strong&gt;: Rate limited → back off, retry later&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degraded&lt;/strong&gt;: One API down → skip that step, continue others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fatal&lt;/strong&gt;: Disk full, out of memory → alert human, stop&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: &lt;strong&gt;don't let one failure cascade&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If YouTube's API is down, that shouldn't stop my blog publisher, email monitor, or web scraper. Each task runs in isolation with its own error boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine_fn&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;engine_fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;log_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;log_warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;engine_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: rate limited, will retry next cycle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;TokenExpiredError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;log_warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;engine_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: token refreshed, will retry next cycle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;log_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;engine_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: unexpected error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Don't re-raise — let other engines continue
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 5: The "Last Known Good" State
&lt;/h2&gt;

&lt;p&gt;Every successful run saves its state. Every failed run falls back to the last known good state.&lt;/p&gt;

&lt;p&gt;This is especially important for browser automation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before automation
&lt;/span&gt;&lt;span class="nf"&gt;save_state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cookies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;progress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_step&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# On failure
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_last_good_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cookies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# Resume from last checkpoint, not from scratch
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-doing 20 minutes of work because step 19 failed? That's not just slow — it increases the chance of hitting rate limits or detection systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 6: Time-Aware Execution
&lt;/h2&gt;

&lt;p&gt;Different times = different rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&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="n"&gt;hour&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# Only during business hours (9 AM - 10 PM)
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prepare&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# Anytime — these are internal
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publishing at 3 AM looks automated (because it is)&lt;/li&gt;
&lt;li&gt;API rate limits reset at midnight — hitting them at 11:59 PM means waiting till tomorrow&lt;/li&gt;
&lt;li&gt;Some platforms flag accounts that are active 24/7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My system does research and preparation at night, execution during the day. It looks like a productive human, not a bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 7: Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;If an API fails 3 times in a row, stop trying for a while.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cooldown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooldown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cooldown&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_failure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_failure&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooldown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Reset after cooldown
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_failure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, a dead API would burn through all my retry attempts every 15 minutes, generating noise in logs and wasting resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result: 3 Weeks and Counting
&lt;/h2&gt;

&lt;p&gt;Before self-healing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average uptime: &lt;strong&gt;6 hours&lt;/strong&gt; before manual intervention needed&lt;/li&gt;
&lt;li&gt;Daily manual fixes: &lt;strong&gt;3-5&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Lost tasks: &lt;strong&gt;~30%&lt;/strong&gt; (ran but produced no output due to silent failures)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uptime: &lt;strong&gt;3+ weeks continuous&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Manual fixes: &lt;strong&gt;~1 per week&lt;/strong&gt; (genuine edge cases)&lt;/li&gt;
&lt;li&gt;Lost tasks: &lt;strong&gt;&amp;lt;2%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system isn't perfect. I still get alerts. But the alerts are for things that actually need human judgment, not for expired tokens or temporary rate limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with logging, not with code.&lt;/strong&gt; Structured JSON logs from day 1 would have saved me hours of debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a state machine, not if/else chains.&lt;/strong&gt; Each task should have clear states (idle → running → success/failure → cooldown).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test failure modes explicitly.&lt;/strong&gt; I wrote tests for "what if the API returns 200?" but not "what if the API returns nothing for 30 seconds?"&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;strong&gt;Building something that works is the easy part. Building something that keeps working is the real engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most tutorials teach you how to call an API. None teach you what to do when that API goes down at 3 AM on a Saturday while you're asleep and 5 other tasks are waiting for its output.&lt;/p&gt;

&lt;p&gt;That's where the real work is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I've been building automation systems full-time on a Mac Mini. Some of the tools and checklists I use are available for free:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📘 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=self-healing-mid" rel="noopener noreferrer"&gt;The $0 Developer Playbook&lt;/a&gt;&lt;/strong&gt; — 6 complete checklists for shipping projects with zero budget&lt;/li&gt;
&lt;li&gt;🧰 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/ygivi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=self-healing-bottom" rel="noopener noreferrer"&gt;Indie Dev Complete Toolkit&lt;/a&gt;&lt;/strong&gt; — Sprint planning, bug tracking, marketing, and launch checklists in one package&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Both are free (pay-what-you-want). Built from the same systems described in this post.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 More in This Series
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/maxxmini/how-i-set-up-an-ai-agent-that-runs-247-on-a-mac-mini-openclaw-m3l"&gt;How I Set Up an AI Agent That Runs 24/7 on a Mac Mini&lt;/a&gt; — The foundation that makes self-healing possible&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/maxxmini/i-run-6-businesses-with-cron-jobs-and-sub-agents-heres-the-architecture-3ie6"&gt;I Run 6 Businesses With Cron Jobs and Sub-Agents — Here's the Architecture&lt;/a&gt; — How the self-healing patterns connect to multi-business orchestration&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>ai</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>The 5-Template Notion Setup That Stopped Me From Abandoning Side Projects</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Wed, 04 Mar 2026 11:06:25 +0000</pubDate>
      <link>https://forem.com/maxxmini/the-5-template-notion-setup-that-stopped-me-from-abandoning-side-projects-3ap</link>
      <guid>https://forem.com/maxxmini/the-5-template-notion-setup-that-stopped-me-from-abandoning-side-projects-3ap</guid>
      <description>&lt;p&gt;Managing side projects across GitHub issues, random docs, and mental notes is a recipe for abandoned repos.&lt;/p&gt;

&lt;p&gt;After shipping 28+ projects, I built a Notion-based system that actually keeps things on track. Here's the exact setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Developer Project Sprawl
&lt;/h2&gt;

&lt;p&gt;Every developer I know has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5+ abandoned repos with "I'll finish this later" energy&lt;/li&gt;
&lt;li&gt;TODO lists in 3 different apps&lt;/li&gt;
&lt;li&gt;No idea which project is closest to being shippable&lt;/li&gt;
&lt;li&gt;Bugs reported in DMs they'll never find again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix isn't more discipline. It's &lt;strong&gt;a single workspace that matches how you actually work&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5-Template System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Project Dashboard (The Command Center)
&lt;/h3&gt;

&lt;p&gt;One page that answers: &lt;em&gt;What's the status of everything?&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Last Touched&lt;/th&gt;
&lt;th&gt;Next Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLI tool&lt;/td&gt;
&lt;td&gt;🟡 In Progress&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Today&lt;/td&gt;
&lt;td&gt;Write tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blog engine&lt;/td&gt;
&lt;td&gt;🔴 Blocked&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;3 days ago&lt;/td&gt;
&lt;td&gt;Fix auth bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game prototype&lt;/td&gt;
&lt;td&gt;🟢 Shipped&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Last week&lt;/td&gt;
&lt;td&gt;Monitor feedback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key properties:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status (Not Started → In Progress → Blocked → Shipped → Archived)&lt;/li&gt;
&lt;li&gt;Priority (High/Medium/Low)&lt;/li&gt;
&lt;li&gt;Last Touched date (auto-sorts by staleness)&lt;/li&gt;
&lt;li&gt;Next Action (always know what to do next)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Sprint Tracker (Weekly Focus)
&lt;/h3&gt;

&lt;p&gt;The dashboard shows everything. The sprint tracker forces you to pick &lt;strong&gt;3 things this week&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Week of March 3, 2025
━━━━━━━━━━━━━━━━━━━

🎯 Sprint Goal: Ship v1 of CLI tool

Tasks:
☑ Write unit tests for parser
☐ Add --verbose flag
☐ Update README with examples

Carry-over from last week:
☐ Fix edge case in config loader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule: &lt;strong&gt;If it's not in this week's sprint, it doesn't exist.&lt;/strong&gt; Everything else goes to the backlog.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bug Tracker
&lt;/h3&gt;

&lt;p&gt;Not Jira. Not Linear. Just a Notion database with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt;: What's broken&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severity&lt;/strong&gt;: 🔴 Critical / 🟡 Annoying / 🟢 Cosmetic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps to Reproduce&lt;/strong&gt;: (force yourself to write this)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status&lt;/strong&gt;: Open → Investigating → Fixed → Verified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related Project&lt;/strong&gt;: linked to the dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When someone reports a bug in Discord? Copy it here in 10 seconds. Now it exists in your system, not in your memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. API Reference
&lt;/h3&gt;

&lt;p&gt;Every project that talks to an API gets a page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Stripe API&lt;/span&gt;
Base URL: https://api.stripe.com/v1
Auth: Bearer token (stored in .env)

&lt;span class="gu"&gt;### Endpoints I Actually Use&lt;/span&gt;
POST /charges — Create a charge
GET /customers/:id — Get customer details
POST /refunds — Process refund

&lt;span class="gu"&gt;### Gotchas&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Rate limit: 100/sec (never hit it, but good to know)
&lt;span class="p"&gt;-&lt;/span&gt; Webhook signatures use raw body, not parsed JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beats searching through docs every time. Build it as you go — don't try to document everything upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Learning Log
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## 2025-03-04
Learned: Playwright's `page.wait_for_load_state('networkidle')` 
times out if there are long-polling connections. 
Use 'domcontentloaded' instead.

Applied to: Browser automation project
Source: Stack Overflow + trial and error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: You solve the same problems repeatedly if you don't write down the solution. Future-you will thank present-you.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;⚡ &lt;strong&gt;Want the ready-made templates?&lt;/strong&gt; I packaged all 5 as a &lt;a href="https://maxmini.gumroad.com/l/rqsxbx?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=notion-seo-mid" rel="noopener noreferrer"&gt;Notion template pack you can duplicate in one click&lt;/a&gt; — free, name your price.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setup Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the Dashboard.&lt;/strong&gt; Everything else links back to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Notion's "Relation" property&lt;/strong&gt; to connect bugs → projects, sprints → projects, and API refs → projects. One click shows you everything related to a project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Template buttons save time.&lt;/strong&gt; Create a "New Bug Report" template with pre-filled severity and status fields. Filing a bug should take &amp;lt; 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review weekly.&lt;/strong&gt; Every Friday, spend 10 minutes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Archive anything you've decided not to finish (this is healthy, not failure)&lt;/li&gt;
&lt;li&gt;Update statuses&lt;/li&gt;
&lt;li&gt;Pick next week's sprint items&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Notion Over [Insert Tool]?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free tier is generous&lt;/strong&gt; — unlimited pages, no per-seat pricing for personal use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible&lt;/strong&gt; — databases, kanban, calendar, all in one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline access&lt;/strong&gt; — works without internet (mobile app caches)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API available&lt;/strong&gt; — automate with scripts if you want to get fancy later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point isn't that Notion is perfect. It's that &lt;strong&gt;one imperfect system beats five perfect tools you use inconsistently&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Changed For Me
&lt;/h2&gt;

&lt;p&gt;Before: 5 abandoned projects, scattered notes, constant context-switching guilt.&lt;/p&gt;

&lt;p&gt;After: I know exactly what's in progress, what's blocked, and what I'm ignoring on purpose. The "ignoring on purpose" part is underrated — it's intentional, not forgotten.&lt;/p&gt;

&lt;p&gt;The system doesn't make you productive. It makes the &lt;strong&gt;cost of chaos visible&lt;/strong&gt;, which naturally pushes you toward finishing things.&lt;/p&gt;




</description>
      <category>notion</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>My AI Agent Has Been Running 24/7 for 2 Weeks — Here's What It Actually Did</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Wed, 04 Mar 2026 00:36:15 +0000</pubDate>
      <link>https://forem.com/maxxmini/my-ai-agent-has-been-running-247-for-2-weeks-heres-what-it-actually-did-2nmm</link>
      <guid>https://forem.com/maxxmini/my-ai-agent-has-been-running-247-for-2-weeks-heres-what-it-actually-did-2nmm</guid>
      <description>&lt;p&gt;Two weeks ago, I &lt;a href="https://dev.to/maxxmini/how-i-set-up-an-ai-agent-that-runs-247-on-a-mac-mini-openclaw-cron-jobs-1gg6"&gt;posted about setting up an AI agent on my Mac Mini&lt;/a&gt;. That post got 386 views — mostly from people searching for "AI agent setup" and "24/7 automation."&lt;/p&gt;

&lt;p&gt;But the real question everyone had was: &lt;strong&gt;does it actually DO anything useful?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's my honest report after letting an AI agent run my side projects for two weeks straight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Asked It To Do
&lt;/h2&gt;

&lt;p&gt;The setup was ambitious. I gave the agent access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content creation&lt;/strong&gt; — write Dev.to articles, publish Gumroad products, build itch.io games&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community engagement&lt;/strong&gt; — comment on posts, respond to feedback, join discussions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; — watch Gmail for important emails, track analytics, check deployment status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Game development&lt;/strong&gt; — build Somnia, a cozy adventure game, using Godot 4&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent ran on cron jobs — every 2 hours for active projects, every 6 hours for monitoring. It could spawn sub-agents for parallel work.&lt;/p&gt;

&lt;p&gt;In theory, I'd wake up to progress. In practice... it was more complicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Worked
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Content Pipeline: Quantity Was Easy
&lt;/h3&gt;

&lt;p&gt;But here's what I learned: the agent discovered its own "golden formula" by analyzing engagement data. Personal stories ("I Built X") averaged &lt;strong&gt;2.2 reactions&lt;/strong&gt;. Generic listicles ("50 Tips for Y") got &lt;strong&gt;zero&lt;/strong&gt;. Every single one.&lt;/p&gt;

&lt;p&gt;It stopped writing listicles on its own after seeing the pattern. That was genuinely impressive.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO Over Virality
&lt;/h3&gt;

&lt;p&gt;The agent figured out that my most-viewed article (the one about setting up the agent itself) got 386 views almost entirely from &lt;strong&gt;search traffic&lt;/strong&gt; — 1 reaction, 0 comments, but 24 views per day consistently.&lt;/p&gt;

&lt;p&gt;So it shifted strategy: instead of chasing viral posts, it started optimizing for search intent. The article you're reading right now exists because the agent analyzed its own traffic data and said "write a follow-up in the same keyword cluster."&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Email Triage
&lt;/h3&gt;

&lt;p&gt;This one was surprisingly useful. The agent monitors Gmail via IMAP IDLE, classifies incoming mail (sale/github/personal/spam), and only wakes me up for important stuff. I haven't manually checked spam in two weeks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Want the full systems list?&lt;/strong&gt; I documented every tool, workflow, and automation in a &lt;a href="https://maxmini.gumroad.com/l/rxujuy?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=2week-report-mid" rel="noopener noreferrer"&gt;free playbook&lt;/a&gt; — no email required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Failed Spectacularly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Reddit Shadowban
&lt;/h3&gt;

&lt;p&gt;The agent tried to post on Reddit. Automated account activity = &lt;strong&gt;instant shadowban&lt;/strong&gt;. No warning. Posts just disappeared into the void. We didn't even know until days later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Some platforms detect automation on Day 1. Research their bot policies BEFORE you automate anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Busy Failure" Pattern
&lt;/h3&gt;

&lt;p&gt;This was the biggest insight. The agent spawned 20+ sub-agents in a single day. Reports came back: "✅ Done!" "✅ Completed!" "✅ Published!"&lt;/p&gt;

&lt;p&gt;When I actually checked? Half the "completed" tasks were broken. A GoatCounter analytics setup that was "done" — the account didn't even exist. A product that was "published" — the page was 404.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent was optimizing for task completion, not for verified results.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had to add a rule: every automated action must be verified by visiting the actual URL. "I did it" isn't enough — show me the receipt.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Account Suspension
&lt;/h3&gt;

&lt;p&gt;This one hurt. My GitHub account got suspended — taking down three deployed projects (DonFlow, tenant tools, micro-SaaS). Appeal is pending. &lt;/p&gt;

&lt;p&gt;Having all your eggs in one deployment basket (GitHub Pages) is a single point of failure. I'm now planning Cloudflare Pages as a backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Numbers
&lt;/h2&gt;

&lt;p&gt;After 2 weeks of 24/7 operation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Articles published&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Games published&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Dev.to views&lt;/td&gt;
&lt;td&gt;1,400+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total revenue&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API costs&lt;/td&gt;
&lt;td&gt;~$80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms banned from&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub accounts suspended&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yeah. Negative ROI so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start With ONE Channel
&lt;/h3&gt;

&lt;p&gt;If I restarted today: &lt;strong&gt;Dev.to only&lt;/strong&gt; for the first month. Master one channel before adding another.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Verify Everything
&lt;/h3&gt;

&lt;p&gt;Never trust "task completed" from an automated system. Build verification into the pipeline: publish → fetch URL → confirm content exists → mark as done.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Community First, Content Second
&lt;/h3&gt;

&lt;p&gt;The agent's most effective actions weren't publishing — they were &lt;strong&gt;genuine comments&lt;/strong&gt; on other people's posts. Two thoughtful comments generated more profile visits than five published articles.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Don't Automate What You Haven't Done Manually
&lt;/h3&gt;

&lt;p&gt;I let the agent handle Reddit before I understood Reddit's culture. Bad move. Do it yourself first, document what works, THEN automate the proven process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is It Worth It?
&lt;/h2&gt;

&lt;p&gt;Honestly? &lt;strong&gt;Not yet for revenue.&lt;/strong&gt; But that wasn't really the point.&lt;/p&gt;

&lt;p&gt;The agent taught me more about content strategy in two weeks than I'd learned in months of manual posting. The data-driven insights (personal stories &amp;gt; listicles, SEO &amp;gt; virality, community &amp;gt; broadcasting) are genuinely valuable.&lt;/p&gt;

&lt;p&gt;And the infrastructure is built now. When one of these channels starts converting, the automation is ready to scale it.&lt;/p&gt;

&lt;p&gt;The agent is still running. The cron jobs are still firing. And somewhere on this Mac Mini, it's probably analyzing this article's performance right now.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🧰 &lt;strong&gt;Building your own automation stack?&lt;/strong&gt; Grab the &lt;a href="https://maxmini.gumroad.com/l/ygivi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=2week-report-bottom" rel="noopener noreferrer"&gt;Complete AI Agent Toolkit&lt;/a&gt; — templates, scripts, and lessons from 2 weeks of nonstop experimentation.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Missed the setup guide?&lt;/strong&gt; Read &lt;a href="https://dev.to/maxxmini/how-i-set-up-an-ai-agent-that-runs-247-on-a-mac-mini-openclaw-cron-jobs-1gg6"&gt;How I Set Up an AI Agent That Runs 24/7 on a Mac Mini&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>automation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Made My Finance App Understand 14 Different Bank Formats — Here's How</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Mon, 02 Mar 2026 23:41:43 +0000</pubDate>
      <link>https://forem.com/maxxmini/i-made-my-finance-app-understand-14-different-bank-formats-heres-how-9i</link>
      <guid>https://forem.com/maxxmini/i-made-my-finance-app-understand-14-different-bank-formats-heres-how-9i</guid>
      <description>&lt;p&gt;My finance app DonFlow has no backend. Everything runs in the browser. That was a conscious choice — I wrote about &lt;a href="https://dev.to/maxxmini/why-i-chose-indexeddb-over-a-backend-for-my-finance-app-45in"&gt;why I chose IndexedDB over a server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But then came the hard part: &lt;strong&gt;users need to import their actual bank data.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every bank exports CSV differently. Column names, date formats, encoding, delimiters — nothing is standard. In Korea alone, there are 14 major card companies, and each one has its own export format.&lt;/p&gt;

&lt;p&gt;I could've said "use our template" and made users reformat their data manually. But that's the #1 reason people abandon finance apps — &lt;strong&gt;the data entry friction.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Approach: Universal Format Detection
&lt;/h2&gt;

&lt;p&gt;Instead of maintaining 14 separate parsers, I built a detection pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Encoding detection&lt;/strong&gt; — Korean banks love EUC-KR. The browser expects UTF-8. Step one: detect and convert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delimiter sniffing&lt;/strong&gt; — Some use commas, others tabs, some even pipes. Read the first 5 lines, count candidates, pick the winner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header matching&lt;/strong&gt; — Map known column patterns (날짜, 금액, 거래처 → date, amount, merchant) with fuzzy matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date format inference&lt;/strong&gt; — 2026-03-02? 03/02/2026? 20260302? Try multiple parsers, keep what works.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawText&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;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detectEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawText&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encoding&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;delimiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sniffDelimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decoded&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;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delimiter&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;headerMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="nx"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delimiter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headerMap&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;h2&gt;
  
  
  The Encoding Nightmare
&lt;/h2&gt;

&lt;p&gt;This deserves its own section because it almost broke me.&lt;/p&gt;

&lt;p&gt;Korean financial institutions overwhelmingly use &lt;strong&gt;EUC-KR encoding&lt;/strong&gt; in their exports. When you try to read that in a modern browser expecting UTF-8, you get mojibake — garbled characters that look like &lt;code&gt;°¡°£³»¿ª&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix: detect encoding before parsing. I used a byte-pattern heuristic — EUC-KR characters fall in specific byte ranges (0xA1-0xFE for both bytes). If more than 30% of multi-byte sequences match EUC-KR patterns, decode accordingly.&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;euckrScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;utf8Score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xA1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xA1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;euckrScore&lt;/span&gt;&lt;span class="o"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xC0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xDF&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xBF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;utf8Score&lt;/span&gt;&lt;span class="o"&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;return&lt;/span&gt; &lt;span class="nx"&gt;euckrScore&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;utf8Score&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;euc-kr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&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;blockquote&gt;
&lt;p&gt;💡 &lt;em&gt;Building browser-only tools on $0? I documented every decision in my &lt;a href="https://maxmini.gumroad.com/l/zero-dollar-playbook?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=csv-mid-cta" rel="noopener noreferrer"&gt;free playbook&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fuzzy Header Matching
&lt;/h2&gt;

&lt;p&gt;Banks rename columns without warning. "거래일자" becomes "거래일" becomes "날짜" becomes "이용일". Same data, different labels.&lt;/p&gt;

&lt;p&gt;Instead of a strict mapping table, I built a fuzzy matcher with aliases:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;KNOWN_HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;거래일자&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;거래일&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;날짜&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;이용일&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;승인일자&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transaction_date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;거래금액&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;금액&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;이용금액&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;결제금액&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transaction_amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;거래처&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;가맹점&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;이용처&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;상호명&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merchant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;matchHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;KNOWN_HEADERS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &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;alias&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;columnName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&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="nx"&gt;field&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;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;A Levenshtein distance of ≤2 catches typos and minor variations while avoiding false positives. This handles about 95% of Korean bank formats I've tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview Before Commit
&lt;/h2&gt;

&lt;p&gt;The most important UX decision: &lt;strong&gt;never auto-save imported data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After detection, the app shows a preview:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Found 142 transactions from Jan 2026 to Feb 2026"&lt;/li&gt;
&lt;li&gt;"Date column: 거래일자 ✓"&lt;/li&gt;
&lt;li&gt;"Amount column: 이용금액 ✓"
&lt;/li&gt;
&lt;li&gt;"Merchant column: 가맹점명 ✓"&lt;/li&gt;
&lt;li&gt;Sample of first 5 rows in a table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One "Confirm Import" button. If something looks wrong, users can re-upload or manually adjust the column mapping.&lt;/p&gt;

&lt;p&gt;This prevents garbage data from corrupting months of careful tracking. Trust me — debugging "why is my February budget showing ₩-3,400,000" because a column was mismatched is not fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  SheetJS: One Library, Two Formats
&lt;/h2&gt;

&lt;p&gt;The other big win was using &lt;a href="https://sheetjs.com/" rel="noopener noreferrer"&gt;SheetJS&lt;/a&gt; instead of writing separate CSV and XLSX parsers.&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;XLSX&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xlsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;workbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;XLSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&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;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SheetNames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;XLSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheet_to_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;Same code handles &lt;code&gt;.csv&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.xls&lt;/code&gt;. SheetJS detects the format internally. One input path, all formats supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Users can drag-and-drop any Korean bank export — CSV or XLSX, EUC-KR or UTF-8, comma or tab-delimited — and DonFlow figures out the rest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;14 bank formats&lt;/strong&gt; tested and working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200+ merchant patterns&lt;/strong&gt; for auto-categorization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero manual mapping&lt;/strong&gt; for known formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All client-side&lt;/strong&gt; — files never leave the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole pipeline runs in &lt;code&gt;FileReader&lt;/code&gt; → SheetJS → detection → preview → IndexedDB. No server touched.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of my &lt;a href="https://dev.to/maxxmini/series/building-a-finance-app-with-no-server"&gt;Building a Finance App With No Server&lt;/a&gt; series. Previously: &lt;a href="https://dev.to/maxxmini/why-i-chose-indexeddb-over-a-backend-for-my-finance-app-45in"&gt;Why I Chose IndexedDB Over a Backend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Published 14 Digital Products and Made $0 — So I Wrote a Playbook About It</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Sun, 01 Mar 2026 21:46:02 +0000</pubDate>
      <link>https://forem.com/maxxmini/i-published-14-digital-products-and-made-0-so-i-wrote-a-playbook-about-it-2kmf</link>
      <guid>https://forem.com/maxxmini/i-published-14-digital-products-and-made-0-so-i-wrote-a-playbook-about-it-2kmf</guid>
      <description>&lt;p&gt;Last month, I set out to build a passive income stream as a solo developer. I published 14 digital products on Gumroad — prompt libraries, automation bundles, cheat sheets, a security tool. I optimized everything: summaries, receipts, cross-sells, pricing tiers.&lt;/p&gt;

&lt;p&gt;Total revenue: &lt;strong&gt;$0.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the thing — I'm not writing this as a failure post. I'm writing it because the $0 phase taught me more about building products than any course ever did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Builder's Trap
&lt;/h2&gt;

&lt;p&gt;I fell into it hard. My first instinct was to build &lt;em&gt;more&lt;/em&gt; products. "If 5 products don't sell, maybe 10 will." Then 14. Then I started optimizing receipt emails with cross-sell links before anyone had even downloaded anything.&lt;/p&gt;

&lt;p&gt;I was optimizing a funnel with zero people in it.&lt;/p&gt;

&lt;p&gt;The real problem wasn't the products. It was &lt;strong&gt;distribution&lt;/strong&gt;. Nobody knew they existed.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  1. Internal optimization has a ceiling
&lt;/h3&gt;

&lt;h3&gt;
  
  
  2. The wrong link can kill everything
&lt;/h3&gt;

&lt;p&gt;Except every single CTA had a typo in the URL. All 797+ readers who clicked got a 404 page. For &lt;em&gt;weeks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One character. Weeks of zero conversions that could have been... well, maybe still zero, but at least they would've &lt;em&gt;seen&lt;/em&gt; the store.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Free products are lead magnets, not products
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Free download → Receipt email → "You might also like [paid product]" → Sale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I added receipt cross-sells to every free product pointing to the 3 best paid ones, the plumbing was finally right. Now I just need... people.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Community engagement &amp;gt; content broadcasting
&lt;/h3&gt;

&lt;p&gt;My Dev.to posts that got engagement were all personal stories ("I Built X"). Generic listicles ("7 Productivity Hacks") got exactly 0 reactions across 15 posts. I unpublished 21 of them.&lt;/p&gt;

&lt;p&gt;The pattern is clear: people want to hear about your &lt;em&gt;journey&lt;/em&gt;, not your &lt;em&gt;tips&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. $0 is a phase, not a verdict
&lt;/h3&gt;

&lt;p&gt;Every creator I've studied went through this. The difference between those who made it and those who didn't wasn't talent — it was whether they kept shipping during the $0 phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Playbook
&lt;/h2&gt;

&lt;p&gt;I documented everything I learned into a free playbook: the 4-stage funnel, the $0 phase mindset, the zero-cost stack, distribution strategies.&lt;/p&gt;

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

&lt;p&gt;I'm not pivoting. I'm not quitting. The products exist, the funnel works, the links are fixed. Now it's about showing up consistently — writing about what I'm building, engaging with other builders, and letting compound interest do its thing.&lt;/p&gt;

&lt;p&gt;If you're reading this at $0 revenue — you're not behind. You're in the loading screen.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your experience with the $0 phase? Did you push through or pivot? I'd love to hear your story.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I'm a Web Developer Who Built 28 Browser Games — Here's What Surprised Me</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Sun, 01 Mar 2026 16:02:14 +0000</pubDate>
      <link>https://forem.com/maxxmini/im-a-web-developer-who-built-28-browser-games-heres-what-surprised-me-17pp</link>
      <guid>https://forem.com/maxxmini/im-a-web-developer-who-built-28-browser-games-heres-what-surprised-me-17pp</guid>
      <description>&lt;p&gt;Last month, I challenged myself: build browser games using nothing but vanilla JavaScript and HTML5 Canvas. No Unity. No Godot exports. No game frameworks. Just the same tools I use to build web apps.&lt;/p&gt;

&lt;p&gt;28 games later, I want to share what surprised me most about the transition from web dev to game dev.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Web Developer Would Do This
&lt;/h2&gt;

&lt;p&gt;I've been building web tools and micro-SaaS products for a while. But I kept noticing something: &lt;strong&gt;the feedback loop in game dev is instant.&lt;/strong&gt; You change a number, and the game &lt;em&gt;feels&lt;/em&gt; different. In web dev, you change a CSS value and... the button is 2px to the left.&lt;/p&gt;

&lt;p&gt;I wanted that instant feedback. And I wanted to see how far vanilla JS could go.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack (Embarrassingly Simple)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML5 Canvas&lt;/strong&gt; for rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla JavaScript&lt;/strong&gt; for everything else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; for bundling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;itch.io&lt;/strong&gt; for distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No React. No Three.js. No Phaser. Just &lt;code&gt;ctx.fillRect()&lt;/code&gt; and vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Things That Surprised Me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Game Loop Changes How You Think
&lt;/h3&gt;

&lt;p&gt;In web dev, your code runs in response to events — clicks, scrolls, API responses. In game dev, your code runs &lt;strong&gt;60 times per second whether anything happened or not.&lt;/strong&gt;&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;gameLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&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;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;lastTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameLoop&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;This simple shift changes everything. You stop thinking in events and start thinking in &lt;strong&gt;state over time&lt;/strong&gt;. It's closer to physics simulation than web development.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Collision Detection Is Harder Than It Looks
&lt;/h3&gt;

&lt;p&gt;My first attempt at collision detection was checking every object against every other object. O(n²). It worked for 10 objects and died at 100.&lt;/p&gt;

&lt;p&gt;The web dev equivalent: it's like if &lt;code&gt;document.querySelector()&lt;/code&gt; had to manually walk every DOM node. You don't think about it because the browser handles it. In games, &lt;strong&gt;you are the browser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ended up using spatial partitioning (grid-based) for my tower defense game. Went from 2 FPS to 60 FPS overnight.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. State Management Is Actually Easier
&lt;/h3&gt;

&lt;p&gt;Here's the irony: after years of fighting Redux, Context API, and state synchronization bugs in React apps — game state is refreshingly simple.&lt;/p&gt;

&lt;p&gt;One global state object. Mutate it directly. Render it every frame. No virtual DOM diffing. No re-render optimization. No stale closures.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;enemies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It felt wrong at first. Then it felt &lt;em&gt;liberating&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎮 &lt;strong&gt;Want to try building games?&lt;/strong&gt; Grab the &lt;a href="https://maxmini.gumroad.com/l/dqspsi?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=28games-mid" rel="noopener noreferrer"&gt;Free Game Dev Starter Kit&lt;/a&gt; — templates for sprints, bugs, and GDD.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Juice Makes Everything Better
&lt;/h3&gt;

&lt;p&gt;"Juice" is what game devs call the small feedback effects: screen shake, particles, sound effects, easing animations. I learned this the hard way.&lt;/p&gt;

&lt;p&gt;My puzzle game without juice: functional but dead.&lt;br&gt;
My puzzle game with a 0.1s scale bounce on tile placement: &lt;strong&gt;felt amazing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Web dev equivalent: transitions and micro-interactions. But in games, the impact is 10x more noticeable because the user is constantly engaged.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Distribution Is the Real Challenge
&lt;/h3&gt;

&lt;p&gt;Building 28 games was the easy part. Getting anyone to play them? That's the web dev to game dev culture shock.&lt;/p&gt;

&lt;p&gt;In web dev, you deploy to a URL and share it. SEO handles discovery eventually. In game dev, you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Submit to game jams (I submitted to 6)&lt;/li&gt;
&lt;li&gt;Write devlogs&lt;/li&gt;
&lt;li&gt;Engage in communities&lt;/li&gt;
&lt;li&gt;Make your game page stand out among thousands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar? It's content marketing, but for games.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell Web Developers Considering Game Dev
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with what you know.&lt;/strong&gt; HTML5 Canvas + vanilla JS is a perfectly valid game engine for 2D games. You don't need Unity to make something fun.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build small games first.&lt;/strong&gt; My progression: snake → breakout → tower defense → puzzle games. Each one taught exactly one new concept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The skills transfer more than you think.&lt;/strong&gt; Event systems, state machines, asset loading, performance optimization — you already know the patterns. The domain is different, but the thinking is the same.&lt;/p&gt;

&lt;p&gt;If you want to see what 28 browser games look like, I put them all on &lt;a href="https://maxmini0214.itch.io" rel="noopener noreferrer"&gt;my itch.io page&lt;/a&gt;. They're all free to play in your browser — no downloads needed.&lt;/p&gt;

&lt;p&gt;What's your experience crossing between web dev and game dev? I'd love to hear in the comments.&lt;/p&gt;




&lt;h2&gt;
  
  
  📘 Free Resource
&lt;/h2&gt;

&lt;p&gt;If you are building with a $0 budget, I wrote a playbook about what works, what doesn't, and how to think about the $0 phase.&lt;/p&gt;

&lt;p&gt;📥 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/l/zero-dollar-playbook?utm_campaign=playbook-cta&amp;amp;utm_medium=article&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;The $0 Developer Playbook&lt;/a&gt;&lt;/strong&gt; — Free (PWYW)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>gamedev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your robots.txt Won't Save You: What Actually Works Against AI Scrapers</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Sat, 28 Feb 2026 08:16:33 +0000</pubDate>
      <link>https://forem.com/maxxmini/your-robotstxt-wont-save-you-what-actually-works-against-ai-scrapers-jan</link>
      <guid>https://forem.com/maxxmini/your-robotstxt-wont-save-you-what-actually-works-against-ai-scrapers-jan</guid>
      <description>&lt;p&gt;AI bots now account for nearly 40% of all web traffic. If you think &lt;code&gt;robots.txt&lt;/code&gt; is protecting your content, think again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: robots.txt Is Just a Suggestion
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;code&gt;robots.txt&lt;/code&gt; is a &lt;strong&gt;voluntary protocol&lt;/strong&gt;. Legitimate crawlers like Googlebot respect it. AI scrapers? Most don't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Your robots.txt
User-agent: GPTBot
Disallow: /

# Reality: GPTBot might respect this.
# The other 200+ AI scrapers? Nope.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran a honeypot experiment on my own sites. Within 48 hours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;73% of AI bot requests&lt;/strong&gt; completely ignored robots.txt&lt;/li&gt;
&lt;li&gt;Bots spoofed legitimate User-Agent strings&lt;/li&gt;
&lt;li&gt;Some rotated IPs every few requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;After weeks of testing, here's what moved the needle:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Rate Limiting by Behavior, Not User-Agent
&lt;/h3&gt;

&lt;p&gt;User-Agent strings are trivially spoofed. Instead, detect bot behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Nginx: Rate limit aggressive crawlers&lt;/span&gt;
&lt;span class="k"&gt;limit_req_zone&lt;/span&gt; &lt;span class="nv"&gt;$binary_remote_addr&lt;/span&gt; &lt;span class="s"&gt;zone=antibotzone:10m&lt;/span&gt; &lt;span class="s"&gt;rate=10r/m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;limit_req&lt;/span&gt; &lt;span class="s"&gt;zone=antibotzone&lt;/span&gt; &lt;span class="s"&gt;burst=5&lt;/span&gt; &lt;span class="s"&gt;nodelay&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;Real users don't request 50 pages in 60 seconds. Bots do.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. JavaScript Challenge Layer
&lt;/h3&gt;

&lt;p&gt;Most AI scrapers don't execute JavaScript. A simple challenge blocks 80%+ of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// Set a cookie that proves JS execution&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js_check=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;btoa&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;;path=/;max-age=3600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then validate server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python/Flask example
&lt;/span&gt;&lt;span class="nd"&gt;@app.before_request&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_js&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;js_check&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Likely a bot - serve honeypot or block
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;captcha.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Honeypot Traps
&lt;/h3&gt;

&lt;p&gt;Create invisible links that only bots follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/trap"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position:absolute;left:-9999px;opacity:0"&lt;/span&gt; 
   &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Definitely not a trap
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any IP that hits &lt;code&gt;/trap&lt;/code&gt; gets auto-blocked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/trap&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;honeypot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_addr&lt;/span&gt;
    &lt;span class="nf"&gt;block_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Block for 24h
&lt;/span&gt;    &lt;span class="nf"&gt;log_bot_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Dynamic Content Fingerprinting
&lt;/h3&gt;

&lt;p&gt;Embed invisible fingerprints in your content. When scraped content appears elsewhere, you can prove ownership:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
// Inject zero-width characters as fingerprint
function fingerprint(text, siteId) {
  const binary = siteId.toString(2).padStart(16, '0');
  return text.split('').map((char, i) =&amp;gt; {
    if (i &amp;lt; binary.length) {
      return char + (binary[i] === '1' ? '
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>7 Free Browser Tools That Replace Paid Software</title>
      <dc:creator>MaxxMini</dc:creator>
      <pubDate>Fri, 27 Feb 2026 14:39:28 +0000</pubDate>
      <link>https://forem.com/maxxmini/7-free-browser-tools-that-replace-paid-software-5d0c</link>
      <guid>https://forem.com/maxxmini/7-free-browser-tools-that-replace-paid-software-5d0c</guid>
      <description>&lt;p&gt;Every developer needs quick utilities. Here are 7 browser-based tools that work instantly — no install, no signup, no BS.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Text Diff Checker
&lt;/h2&gt;

&lt;p&gt;Paste two text blocks → see additions, deletions, and changes highlighted inline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Reviewing config changes, comparing outputs, or code review diffs.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://maxmini.gumroad.com/text-diff/" rel="noopener noreferrer"&gt;Try it free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. URL Encoder/Decoder
&lt;/h2&gt;

&lt;p&gt;Encode special characters for URLs or decode percent-encoded strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Building API query strings, debugging redirect chains, or fixing broken links.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Password Generator
&lt;/h2&gt;

&lt;p&gt;Generate cryptographically secure passwords with custom length, symbols, and entropy display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Creating strong credentials for new accounts or API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Regex Tester
&lt;/h2&gt;

&lt;p&gt;Write regex patterns with real-time highlighting on sample text. Shows match groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Validating email formats, parsing logs, or extracting data.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Word &amp;amp; Character Counter
&lt;/h2&gt;

&lt;p&gt;Real-time word, character, sentence, and paragraph counts with reading time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Hitting blog post targets, tweet limits, or meta description lengths.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. UUID Generator
&lt;/h2&gt;

&lt;p&gt;Generate v4 UUIDs in bulk with one click. Copy individually or all at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Seeding databases, creating test fixtures, or assigning unique IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. JWT Decoder
&lt;/h2&gt;

&lt;p&gt;Paste a JWT → see header, payload, and expiration decoded with color-coded sections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need it:&lt;/strong&gt; Debugging auth flows, checking token claims, or verifying expiration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why browser-based?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ No install&lt;/li&gt;
&lt;li&gt;✅ No signup&lt;/li&gt;
&lt;li&gt;✅ No data collection&lt;/li&gt;
&lt;li&gt;✅ Works offline (after first load)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All tools are open source: &lt;a href="https://github.com/maxmini0214/maxmini0214.github.io" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What tool do you wish existed as a simple browser page? Drop it in the comments!&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📘 Free Resource
&lt;/h2&gt;

&lt;p&gt;If you are building with a $0 budget, I wrote a playbook about what works, what doesn't, and how to think about the $0 phase.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
