<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Build With Jet</title>
    <description>The latest articles on DEV Community by Build With Jet (@buildwithjet).</description>
    <link>https://dev.to/buildwithjet</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%2F3808687%2F94aa5633-b865-46b7-ab45-d10cbd6278ec.png</url>
      <title>DEV Community: Build With Jet</title>
      <link>https://dev.to/buildwithjet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/buildwithjet"/>
    <language>en</language>
    <item>
      <title>I Automated My Morning Routine Into a Single Email</title>
      <dc:creator>Build With Jet</dc:creator>
      <pubDate>Sat, 14 Mar 2026 13:31:22 +0000</pubDate>
      <link>https://dev.to/buildwithjet/i-automated-my-morning-routine-into-a-single-email-2dc2</link>
      <guid>https://dev.to/buildwithjet/i-automated-my-morning-routine-into-a-single-email-2dc2</guid>
      <description>&lt;h1&gt;
  
  
  I Automated My Morning Routine Into a Single Email
&lt;/h1&gt;

&lt;p&gt;Every morning at 7am Pacific, two emails land. One for me, one for my wife. Each one is a personalized daily brief with everything we need to start the day. It costs $0.003 per day to run, and it replaced a 20-minute routine of checking 6 different apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The morning chaos before
&lt;/h2&gt;

&lt;p&gt;Here's what my morning used to look like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check weather app (is it going to rain? what should the kid wear?)&lt;/li&gt;
&lt;li&gt;Check calendar (any meetings before 9?)&lt;/li&gt;
&lt;li&gt;Check email for anything urgent&lt;/li&gt;
&lt;li&gt;Check market pre-open (I work in investment banking, so this matters)&lt;/li&gt;
&lt;li&gt;Check if any packages are arriving today&lt;/li&gt;
&lt;li&gt;Tell my wife the weather and kid logistics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's 6 apps, 15-20 minutes, before coffee. And half the time I'd forget to mention a schedule change or a package arriving.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard nobody opened
&lt;/h2&gt;

&lt;p&gt;My first attempt was a Notion dashboard. It had everything — weather, calendar, markets, tasks, the works. Beautiful. Comprehensive.&lt;/p&gt;

&lt;p&gt;Nobody opened it. Not even me.&lt;/p&gt;

&lt;p&gt;The friction of opening a browser, navigating to Notion, finding the right page — it was enough to kill it. I'd default back to checking individual apps because muscle memory is stronger than good intentions.&lt;/p&gt;

&lt;p&gt;The insight: the delivery mechanism matters more than the content. An email that arrives at 7am gets read. A dashboard that exists somewhere gets ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the emails
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My brief:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weather forecast with emoji (fastest way to parse weather at 6:58am)&lt;/li&gt;
&lt;li&gt;Today's calendar events with times&lt;/li&gt;
&lt;li&gt;Pre-market S&amp;amp;P 500 and 10-year Treasury moves&lt;/li&gt;
&lt;li&gt;Unread email count + any flagged urgent messages&lt;/li&gt;
&lt;li&gt;Package deliveries expected today&lt;/li&gt;
&lt;li&gt;Prediction market betting positions summary&lt;/li&gt;
&lt;li&gt;Any overnight alerts from my monitoring system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My wife's brief:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same weather and calendar&lt;/li&gt;
&lt;li&gt;Content ideas for her esthetician work (with specific hooks and trending products)&lt;/li&gt;
&lt;li&gt;Trending skincare products for her affiliate business&lt;/li&gt;
&lt;li&gt;Kid logistics (soccer practice? school events?)&lt;/li&gt;
&lt;li&gt;Family calendar conflicts or coordination needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key difference: her brief includes business intelligence for her work. It's not just "here's your day" — it's "here's your day, and here are 3 video ideas based on what's trending in skincare right now."&lt;/p&gt;

&lt;h2&gt;
  
  
  The newsletter noise problem
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't building the brief — it was taming the inbox. I subscribe to maybe 40 newsletters. Most mornings, 15-20 are sitting unread, and they make the inbox look overwhelming.&lt;/p&gt;

&lt;p&gt;The fix: an inbox cleanup job that runs 5 times daily. It auto-labels newsletters, archives low-priority ones, and marks promotional emails as read. By the time the daily brief runs at 7am, the inbox is already clean.&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 inbox cleanup logic
&lt;/span&gt;&lt;span class="n"&gt;NEWSLETTER_SENDERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_known_senders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# ~200 senders
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;get_unread&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;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;NEWSLETTER_SENDERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Newsletter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;is_promotional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;mark_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Promo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Everything else stays in inbox for the brief to surface
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM decides what's promotional using Haiku 4.5. It costs about $0.001 per email classification. For 20 emails a day, that's $0.02/day. Not zero, but close enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  ThreadPoolExecutor: 52 seconds to 12
&lt;/h2&gt;

&lt;p&gt;The original brief took 52 seconds to generate because it was serial — fetch weather, then calendar, then emails, then markets, then format, then send.&lt;/p&gt;

&lt;p&gt;Obvious fix: do the fetches in parallel.&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;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;weather_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;calendar_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_calendar_events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_email_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;market_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_market_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;packages_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_package_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;betting_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_betting_positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weather_future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;calendar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calendar_future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... etc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;52 seconds down to 12. The bottleneck shifted from API calls to the LLM formatting step, which takes about 4 seconds. The rest is just network latency for 6 parallel API calls.&lt;/p&gt;

&lt;p&gt;This matters because both briefs need to go out at 7am. If one takes a minute, the other waits. With parallel fetching, both emails land within seconds of each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $0.003/day math
&lt;/h2&gt;

&lt;p&gt;Here's the actual cost breakdown per day:&lt;/p&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;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weather API (Open-Meteo)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calendar API (Google)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gmail API (Google)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Market data (Brave Search)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM formatting — my brief (Haiku 4.5)&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM formatting — spouse's brief (Haiku 4.5)&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inbox cleanup LLM calls&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.003/day&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's about $1.10 per year. The VPS that runs it costs $5/month, but it runs 30 other jobs too, so the marginal cost of the daily brief is effectively zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unexpected benefit
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't expect: both briefs landing at the same time changed how we start our day.&lt;/p&gt;

&lt;p&gt;Before, I was the information bottleneck. "Do we have anything tonight?" "Is it going to rain?" "When's soccer?" Now my wife has her own brief with her own calendar view, her own content ideas, her own business intelligence. She doesn't need to ask, and I don't need to remember to tell her.&lt;/p&gt;

&lt;p&gt;The briefs also became a coordination tool. When we both know the day's schedule, we can divide and conquer without a morning logistics meeting. "I saw soccer is at 4 — I'll grab them, you do the grocery run" just happens naturally because we both read the same calendar data.&lt;/p&gt;

&lt;p&gt;It also forced us to centralize our calendars. Before this, our calendars were a mess — some stuff split between accounts, kid stuff all over the place. Making the brief useful meant getting all events into one system. The automation created the organizational discipline I never had motivation to build on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build your own
&lt;/h2&gt;

&lt;p&gt;If you want something similar, here's my honest advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with email delivery.&lt;/strong&gt; Don't build a dashboard. Send yourself an email. You'll actually read it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the cheapest LLM that works.&lt;/strong&gt; Haiku 4.5 formats the brief perfectly. You don't need Opus for "make this weather data readable."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallelize from day one.&lt;/strong&gt; Serial API calls are the silent killer of morning automations. If your brief takes 60 seconds, it drifts later as you add sections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build for two people.&lt;/strong&gt; Even if you're single, think about what a second person would want. It forces you to make the system modular instead of hardcoded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it for a week before adding features.&lt;/strong&gt; My first brief had 12 sections. I cut it to 7 after realizing I was skimming half of them. Less is more at 7am.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing is about 400 lines of Python, runs on a $5 VPS, and has been solid for over a month. It's the single automation I'd rebuild first if I had to start over.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using Notion as a Database Backend for AI Agents</title>
      <dc:creator>Build With Jet</dc:creator>
      <pubDate>Sat, 14 Mar 2026 13:31:04 +0000</pubDate>
      <link>https://dev.to/buildwithjet/using-notion-as-a-database-backend-for-ai-agents-44gi</link>
      <guid>https://dev.to/buildwithjet/using-notion-as-a-database-backend-for-ai-agents-44gi</guid>
      <description>&lt;h1&gt;
  
  
  Using Notion as a Database Backend for AI Agents
&lt;/h1&gt;

&lt;p&gt;I use Notion as the database for my entire AI agent system. Not Postgres. Not SQLite. Not Supabase. Notion. And before you close this tab — let me explain why it actually works, and where it absolutely doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Notion over a real database
&lt;/h2&gt;

&lt;p&gt;The honest answer: my wife can see it.&lt;/p&gt;

&lt;p&gt;She runs an esthetician business and does affiliate marketing. She doesn't know what a SQL query is, and she shouldn't have to. But she checks Notion every day. When my AI agent tracks her packages, scouts trending products, or syncs her calendar — she can see all of it in Notion without me building a frontend.&lt;/p&gt;

&lt;p&gt;That's the real value proposition. Notion is a database with a UI that already exists. For a solo builder working nights after the kid goes to bed, "I don't have to build a dashboard" is worth more than "I can do JOINs."&lt;/p&gt;

&lt;p&gt;The other reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rich text and relations&lt;/strong&gt; — I can store a deal memo with formatted text, linked contacts, and file attachments in one record. Try doing that in SQLite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free tier is generous&lt;/strong&gt; — Haven't paid them a cent. API rate limits are the constraint, not pricing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API is decent&lt;/strong&gt; — Create, read, update, filter, sort. Covers 90% of what I need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 7 dashboards
&lt;/h2&gt;

&lt;p&gt;My Notion workspace has 7 dashboards that update automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Health Dashboard&lt;/strong&gt; — VPS uptime, last heartbeat, error counts, API usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bets&lt;/strong&gt; — Every prediction with P&amp;amp;L tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deals&lt;/strong&gt; — Investment deals with status, next steps, key dates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Family&lt;/strong&gt; — Soccer games, school events, merged calendar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orders&lt;/strong&gt; — Package tracking from email receipt to delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Garden&lt;/strong&gt; — 26 seed varieties with germination dates and zone assignments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home&lt;/strong&gt; — The meta-dashboard that links to everything&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The garden one is my favorite. It tracks when I started each seed indoors, expected germination days, actual sprout dates, and which zone they'll go in. My wife thinks I'm insane for having an AI track my zinnia seedlings. She's probably right.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDK v3.0 broke everything
&lt;/h2&gt;

&lt;p&gt;In February, the Notion Python SDK shipped v3.0 with a breaking change to database creation. The &lt;code&gt;parent&lt;/code&gt; parameter changed from:&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;# Old (v2.x) - worked fine
&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&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;type&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;page_id&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;page_id&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_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# New (v3.0) - required format
&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&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;page_id&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_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No "type" key anymore. The SDK now infers the parent type internally. This broke my template creator, dashboard builder, and sales tracker — basically everything that creates databases programmatically. The error message was useless.&lt;/p&gt;

&lt;p&gt;I found the fix by reading the SDK changelog. The migration guide was buried in a GitHub discussion, not the docs. An hour of debugging for a one-line fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  No webhooks — and how I deal with it
&lt;/h2&gt;

&lt;p&gt;This is Notion's biggest limitation: no webhooks. You can't say "when this database changes, call my endpoint." You have to poll.&lt;/p&gt;

&lt;p&gt;My approach uses different intervals based on time-sensitivity:&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;# Fast (every 5 min): Orders + Health dashboards
# Medium (every 15 min): Deals + Family dashboards
# Daily (8am): Full rebuild of all dashboards
# 12-hour: Complete refresh of everything
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's wasteful compared to webhooks. Every poll costs an API call even if nothing changed. But for a personal system with 7 dashboards, the cost is negligible — roughly 200 API calls per day total.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits and the workaround
&lt;/h2&gt;

&lt;p&gt;Notion's API rate limit is 3 requests per second. Sounds fine until you're updating 26 garden plants, each needing a page update with 5 properties. That's 26 API calls, and at 3/second you need ~9 seconds minimum.&lt;/p&gt;

&lt;p&gt;My fix is boring but effective:&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;time&lt;/span&gt;

&lt;span class="n"&gt;NOTION_RATE_LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;  &lt;span class="c1"&gt;# seconds between calls (slightly &amp;gt; 1/3)
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notion_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;NOTION_RATE_LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;method&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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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="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;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;'&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="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;retry_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;method&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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fixed delay between every request, plus retry-on-429. Not fancy. Bulk updates are slow. But it never hits rate limits, and for background automation, "slow but reliable" beats "fast but occasionally fails."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I were starting over, I'd still use Notion — but I'd add a local SQLite cache for time-series data. The bet history doesn't need to live in Notion. It needs somewhere I can run analytics on it. Right now, to calculate win rate, I have to query Notion for every record, page through results, and compute it in Python. A local database would make that instant.&lt;/p&gt;

&lt;p&gt;I'd also structure databases more carefully upfront. I have some where I'm storing JSON blobs in a rich_text property because I ran out of property types. That's a code smell that means "you should have used a real database for this part."&lt;/p&gt;

&lt;p&gt;But for the core use case — dashboards that humans look at, data that benefits from rich formatting, and a system where the builder and the users are the same household — Notion genuinely works. It's not the right tool for everything. But it's the right tool for this.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Run 31 Automated Jobs on a $5/month VPS with Claude and systemd</title>
      <dc:creator>Build With Jet</dc:creator>
      <pubDate>Fri, 13 Mar 2026 12:31:09 +0000</pubDate>
      <link>https://dev.to/buildwithjet/how-i-run-31-automated-jobs-on-a-5month-vps-with-claude-and-systemd-3c38</link>
      <guid>https://dev.to/buildwithjet/how-i-run-31-automated-jobs-on-a-5month-vps-with-claude-and-systemd-3c38</guid>
      <description>&lt;h1&gt;
  
  
  How I Run 31 Automated Jobs on a $5/month VPS with Claude and systemd
&lt;/h1&gt;

&lt;p&gt;I run 31 automated jobs and 3 always-on services from a single $5/month Hetzner VPS. They handle everything from morning brief emails to weather betting to inbox cleanup. Here's how I got here, what broke along the way, and why systemd beat everything else I tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why systemd, not cron
&lt;/h2&gt;

&lt;p&gt;I started with cron. Everyone starts with cron. And for simple stuff — run this script at 8am — it's fine. But the moment you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs that actually persist (not just piped to /dev/null)&lt;/li&gt;
&lt;li&gt;Automatic restarts when something crashes&lt;/li&gt;
&lt;li&gt;Dependency ordering (don't run the brief until the email scan finishes)&lt;/li&gt;
&lt;li&gt;Easy enable/disable without commenting out lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...cron falls apart. systemd timers give you all of that for free. Each job gets a .service file (what to run) and a .timer file (when to run it). You get journalctl logs, systemctl status, and if something dies at 3am, systemd can restart it without you waking up.&lt;/p&gt;

&lt;p&gt;Here's what a typical timer looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# jet-daily-brief.timer
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Jet Daily Brief Timer&lt;/span&gt;

&lt;span class="nn"&gt;[Timer]&lt;/span&gt;
&lt;span class="py"&gt;OnCalendar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;*-*-* 14:00:00 UTC&lt;/span&gt;
&lt;span class="py"&gt;Persistent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# jet-daily-brief.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Jet Daily Brief&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/jet&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/python3 -u -m SKILLS.daily_brief&lt;/span&gt;
&lt;span class="py"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;300&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PYTHONUNBUFFERED=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-u&lt;/code&gt; flag and &lt;code&gt;PYTHONUNBUFFERED=1&lt;/code&gt; are non-negotiable. Without them, Python buffers stdout when there's no TTY, and your logs show up 20 minutes late (or never). I learned this after spending an hour wondering why a job that clearly ran had zero output in journalctl.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3am overnight worker failure
&lt;/h2&gt;

&lt;p&gt;My overnight worker runs at midnight. It handles maintenance tasks — clearing stale data, syncing dashboards, running health checks. One night it silently failed because the Anthropic API returned a 529 (overloaded) and my retry logic had a bug: it retried 5 times with no backoff, hit rate limits, and cascaded.&lt;/p&gt;

&lt;p&gt;I woke up to a Telegram message from my heartbeat monitor: "Overnight worker: LAST RUN &amp;gt;24h ago." The fix was embarrassingly simple — exponential backoff and capping retries at 2 instead of 5. But the real lesson was: the heartbeat monitor caught it. Every 30 minutes, a separate timer checks that every other timer actually ran recently. If something hasn't run in 2x its expected interval, I get an alert.&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 heartbeat check
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;timer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_interval_hours&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TIMERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;last_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_last_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expected_interval_hours&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;send_alert&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;timer_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: LAST RUN &amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_interval_hours&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;h ago&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;h2&gt;
  
  
  The OAuth Sunday nightmare
&lt;/h2&gt;

&lt;p&gt;Google OAuth tokens expire. Specifically, if your Google Cloud app is in "testing" mode (which mine is, because moving to production requires a security review I haven't done), tokens expire every 7 days. Every. Seven. Days.&lt;/p&gt;

&lt;p&gt;For a while, this meant every Sunday morning my email-dependent services would break. The daily brief would fail. Inbox cleanup would fail. Calendar sync would fail. I'd wake up, re-auth manually, push the new token to the VPS, and pretend it was fine.&lt;/p&gt;

&lt;p&gt;The self-healing fix was an OAuth helper that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tries the token from the environment variable first&lt;/li&gt;
&lt;li&gt;Falls back to the token file if the env var is stale&lt;/li&gt;
&lt;li&gt;Runs an hourly sync that refreshes the token before it expires&lt;/li&gt;
&lt;li&gt;If all else fails, sends me an alert with a re-auth link
&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_gmail_service&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="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# Try env var first (most recently updated)
&lt;/span&gt;    &lt;span class="n"&gt;token_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GMAIL_TOKEN_JSON&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="n"&gt;token_data&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="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_authorized_user_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Fall back to file
&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;creds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&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;valid&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;creds&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;expired&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;refresh_token&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="c1"&gt;# Write back to env for next time
&lt;/span&gt;            &lt;span class="nf"&gt;update_env_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GMAIL_TOKEN_JSON&lt;/span&gt;&lt;span class="sh"&gt;'&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;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not elegant, but it hasn't broken on a Sunday in 3 weeks. I'll take it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost audit: $19 to $5/month
&lt;/h2&gt;

&lt;p&gt;When I first set this up, I was spending about $19/month on Anthropic API calls alone. The VPS was $5. Most of the API cost came from three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Overnight worker using Sonnet&lt;/strong&gt; ($7.73/mo) — Switched to Haiku 4.5. It handles maintenance tasks just fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Newsletter summaries using Sonnet&lt;/strong&gt; ($2.83/mo) — Same switch. Haiku summarizes newsletters perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram bot without prompt caching&lt;/strong&gt; ($3.50/mo) — Added prompt caching. Cache hit rate is ~70%.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also evaluated running Ollama locally for the cheap stuff. At $5/month total, it wasn't worth the complexity. The tasks that could run on a local model were already costing pennies on Haiku.&lt;/p&gt;

&lt;p&gt;Final breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hetzner VPS (CX22, 2 vCPU, 4GB RAM): &lt;strong&gt;$5/month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Anthropic API (all 31 jobs + bot): &lt;strong&gt;~$5/month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total: &lt;strong&gt;~$10/month&lt;/strong&gt; for a full personal AI operations platform&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What still breaks
&lt;/h2&gt;

&lt;p&gt;I'd love to tell you it's bulletproof. It's not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python dependency conflicts.&lt;/strong&gt; The VPS has one Python environment. When I install something for one bot that conflicts with another, both break. I should use venvs. I don't. It hasn't bitten me hard enough yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disk space.&lt;/strong&gt; 40GB sounds like a lot until you're storing screenshots, audio files, and logs from 31 services. I added a cleanup job to the overnight worker. It deletes files older than 7 days from /tmp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The VPS itself.&lt;/strong&gt; It's a single point of failure. No redundancy, no failover. If the host has an outage, everything stops. I have manual fallbacks, but I haven't tested them in weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me.&lt;/strong&gt; I'm the bottleneck. When something needs a new feature or a bug fix, it waits until I have time after work and after evening routines. The whole system is one person deep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it?
&lt;/h2&gt;

&lt;p&gt;Absolutely. My morning brief email arrives at 7am with weather, calendar, market data, and a summary of overnight emails. My inbox cleanup runs 5 times a day. Package tracking updates automatically. And I have a bot that can do almost anything the full system can do, from my phone.&lt;/p&gt;

&lt;p&gt;The total cost is less than a single SaaS subscription. The total setup time was about 3 weeks of evenings. And the hardest part wasn't the code — it was getting systemd timer syntax right and remembering to add &lt;code&gt;PYTHONUNBUFFERED=1&lt;/code&gt; to every service file.&lt;/p&gt;

&lt;p&gt;If you're thinking about doing something similar: start with one timer. Get the daily brief working. Once you see that email land in your inbox every morning without you touching anything, you'll want to automate everything else too.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>selfhosted</category>
      <category>productivity</category>
    </item>
    <item>
      <title>building ai agents that run 24/7 — and tracking everything in notion. templates dropping soon for an</title>
      <dc:creator>Build With Jet</dc:creator>
      <pubDate>Mon, 09 Mar 2026 12:31:12 +0000</pubDate>
      <link>https://dev.to/buildwithjet/building-ai-agents-that-run-247-and-tracking-everything-in-notion-templates-dropping-soon-for-an-neo</link>
      <guid>https://dev.to/buildwithjet/building-ai-agents-that-run-247-and-tracking-everything-in-notion-templates-dropping-soon-for-an-neo</guid>
      <description>&lt;p&gt;building ai agents that run 24/7 — and tracking everything in notion. templates dropping soon for anyone who wants to actually see how this stuff works.&lt;/p&gt;

</description>
      <category>launch</category>
      <category>ai</category>
      <category>notion</category>
    </item>
    <item>
      <title>I Run 28 Autonomous AI Agents — Here Are the Free Notion Templates I Built to Track Them</title>
      <dc:creator>Build With Jet</dc:creator>
      <pubDate>Sat, 07 Mar 2026 00:12:44 +0000</pubDate>
      <link>https://dev.to/buildwithjet/i-run-28-autonomous-ai-agents-here-are-the-free-notion-templates-i-built-to-track-them-5a7</link>
      <guid>https://dev.to/buildwithjet/i-run-28-autonomous-ai-agents-here-are-the-free-notion-templates-i-built-to-track-them-5a7</guid>
      <description>&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I run &lt;strong&gt;28 autonomous AI agents&lt;/strong&gt; on a $5/month VPS. They handle everything — email triage, calendar management, weather betting, survey automation, deal tracking, and more.&lt;/p&gt;

&lt;p&gt;The hardest part wasn't building the agents. It was &lt;strong&gt;tracking them all&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After months of iteration, I built a set of Notion templates that keep everything organized. Now I'm giving them away for free.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  1. &lt;a href="https://buildwithjet.gumroad.com/l/saoibd" rel="noopener noreferrer"&gt;Claude Code Quickstart Kit&lt;/a&gt; (Free)
&lt;/h3&gt;

&lt;p&gt;Everything you need to set up a Claude Code agent: CLAUDE.md template, MEMORY.md template, session log database, and a setup guide. If you're starting with Claude Code, this saves you the first few hours of trial and error.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;a href="https://buildwithjet.gumroad.com/l/zqvdl" rel="noopener noreferrer"&gt;AI Cost Calculator&lt;/a&gt; (Free)
&lt;/h3&gt;

&lt;p&gt;Track API costs across providers (OpenAI, Anthropic, Google). See exactly where your money goes with per-model breakdowns. I built this because my API bill hit $19/month before I realized which calls were burning cash — now it's $5.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;a href="https://buildwithjet.gumroad.com/l/zgsmv" rel="noopener noreferrer"&gt;Vibe Coder's Project Tracker&lt;/a&gt; (Free)
&lt;/h3&gt;

&lt;p&gt;Ship AI-built projects from idea to launch. Pipeline database with status tracking, tech stack tags, and milestone management. I use this to manage the 28 agents and the dozen side projects that spawned from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Free?
&lt;/h2&gt;

&lt;p&gt;These templates are the foundation of a larger system I'm building. The free versions give you a solid starting point. Premium templates (&lt;a href="https://buildwithjet.gumroad.com/l/ablhag" rel="noopener noreferrer"&gt;AgentOps Lite&lt;/a&gt; and &lt;a href="https://buildwithjet.gumroad.com/l/grfsp" rel="noopener noreferrer"&gt;AlgoTracker&lt;/a&gt;) go deeper with multi-database architectures, cross-references, and operational dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Them
&lt;/h2&gt;

&lt;p&gt;All three free templates are available now on &lt;a href="https://buildwithjet.gumroad.com" rel="noopener noreferrer"&gt;the Build With Jet store&lt;/a&gt;. Duplicate them to your Notion workspace and start building.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://buildwithjet.gumroad.com" rel="noopener noreferrer"&gt;Jet&lt;/a&gt; — an AI assistant framework powered by Claude Code.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;What are you building with AI agents? Drop a comment below.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>ai</category>
      <category>productivity</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>I Automated My Morning Routine Into a Single Email</title>
      <dc:creator>Build With Jet</dc:creator>
      <pubDate>Fri, 06 Mar 2026 23:21:50 +0000</pubDate>
      <link>https://dev.to/buildwithjet/i-automated-my-morning-routine-into-a-single-email-207</link>
      <guid>https://dev.to/buildwithjet/i-automated-my-morning-routine-into-a-single-email-207</guid>
      <description>&lt;h1&gt;
  
  
  I Automated My Morning Routine Into a Single Email
&lt;/h1&gt;

&lt;p&gt;Every morning at 7am Pacific, two emails land. One for me, one for my wife. Each one is a personalized daily brief with everything we need to start the day. It costs $0.003 per day to run, and it replaced a 20-minute routine of checking 6 different apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The morning chaos before
&lt;/h2&gt;

&lt;p&gt;Here's what my morning used to look like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check weather app (is it going to rain? what should Mayer wear?)&lt;/li&gt;
&lt;li&gt;Check calendar (any meetings before 9?)&lt;/li&gt;
&lt;li&gt;Check email for anything urgent&lt;/li&gt;
&lt;li&gt;Check market pre-open (I work in investment banking, so this matters)&lt;/li&gt;
&lt;li&gt;Check if any packages are arriving today&lt;/li&gt;
&lt;li&gt;Tell Elizabeth the weather and kid logistics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's 6 apps, 15-20 minutes, before coffee. And half the time I'd forget to tell Elizabeth about the schedule change or the package arriving.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard nobody opened
&lt;/h2&gt;

&lt;p&gt;My first attempt was a Notion dashboard. It had everything — weather, calendar, markets, tasks, the works. Beautiful. Comprehensive.&lt;/p&gt;

&lt;p&gt;Nobody opened it.&lt;/p&gt;

&lt;p&gt;Not even me. The friction of opening a browser, navigating to Notion, and finding the right page was enough to kill it. I'd default back to checking individual apps because muscle memory is stronger than good intentions.&lt;/p&gt;

&lt;p&gt;The insight: the delivery mechanism matters more than the content. An email that arrives at 7am gets read. A dashboard that exists somewhere gets ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the emails
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Travis's brief&lt;/strong&gt; (mine):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weather forecast with emoji (yes, emoji — it's the fastest way to parse weather at 6:58am)&lt;/li&gt;
&lt;li&gt;Today's calendar events with times&lt;/li&gt;
&lt;li&gt;Pre-market S&amp;amp;P 500 and 10-year Treasury moves&lt;/li&gt;
&lt;li&gt;Unread email count + any flagged urgent messages&lt;/li&gt;
&lt;li&gt;Package deliveries expected today&lt;/li&gt;
&lt;li&gt;Kalshi betting positions summary (I run weather/economics prediction bets)&lt;/li&gt;
&lt;li&gt;Any overnight alerts from my monitoring system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Elizabeth's brief&lt;/strong&gt; (my wife's):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same weather and calendar&lt;/li&gt;
&lt;li&gt;Content ideas for her esthetician TikTok (with specific hooks and settings)&lt;/li&gt;
&lt;li&gt;Trending skincare products for her affiliate business&lt;/li&gt;
&lt;li&gt;Kid logistics (soccer practice? school events?)&lt;/li&gt;
&lt;li&gt;Family calendar conflicts or coordination needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key difference: her brief includes business intelligence for her esthetician and affiliate marketing work. It's not just "here's your day" — it's "here's your day, and here are 3 TikTok video ideas based on what's trending in skincare right now."&lt;/p&gt;

&lt;h2&gt;
  
  
  The newsletter noise problem
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't building the brief — it was taming the inbox. I subscribe to maybe 40 newsletters. Market recaps, tech news, deal alerts, skincare industry stuff. Most mornings, 15-20 of those are sitting unread, and they make the inbox look overwhelming.&lt;/p&gt;

&lt;p&gt;The fix: an inbox cleanup job that runs 5 times daily. It auto-labels newsletters, archives low-priority ones, and marks promotional emails as read. By the time the daily brief runs at 7am, the inbox is already clean, and the brief only surfaces emails that actually need attention.&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 inbox cleanup logic
&lt;/span&gt;&lt;span class="n"&gt;NEWSLETTER_SENDERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_known_senders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# ~200 senders
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;get_unread&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;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;NEWSLETTER_SENDERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Newsletter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;is_promotional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;mark_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Promo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Everything else stays in inbox for the brief to surface
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM decides what's promotional using Haiku 4.5. It costs about $0.001 per email classification. For 20 emails a day, that's $0.02/day. Not zero, but close enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  ThreadPoolExecutor: 52 seconds to 12
&lt;/h2&gt;

&lt;p&gt;The original brief took 52 seconds to generate. That's because it was serial — fetch weather, then fetch calendar, then fetch emails, then fetch markets, then format, then send.&lt;/p&gt;

&lt;p&gt;The obvious fix: do the fetches in parallel.&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;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;weather_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;calendar_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_calendar_events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_email_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;market_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_market_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;packages_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_package_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;kalshi_future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_kalshi_positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weather_future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;calendar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calendar_future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... etc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;52 seconds down to 12. The bottleneck shifted from API calls to the LLM formatting step, which takes about 4 seconds. The rest is just network latency for 6 parallel API calls.&lt;/p&gt;

&lt;p&gt;This matters because both briefs need to go out at 7am. If one takes a minute, the other waits. With parallel fetching, both emails land within seconds of each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $0.003/day math
&lt;/h2&gt;

&lt;p&gt;Here's the actual cost breakdown per day:&lt;/p&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;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weather API (Open-Meteo)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calendar API (Google)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gmail API (Google)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Market data (Brave Search)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM formatting — Travis brief (Haiku 4.5)&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM formatting — Elizabeth brief (Haiku 4.5)&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inbox cleanup LLM calls&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.003/day&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's about $1.10 per year. The VPS that runs it costs $5/month, but it runs 30 other jobs too, so the marginal cost of the daily brief is effectively zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unexpected benefit
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't expect: both briefs landing at the same time changed how Elizabeth and I start our day.&lt;/p&gt;

&lt;p&gt;Before, I was the information bottleneck. "Hey, do we have anything tonight?" "Is it going to rain?" "When's Mayer's soccer?" Now she has her own brief with her own calendar view, her own content ideas, her own business intelligence. She doesn't need to ask me, and I don't need to remember to tell her.&lt;/p&gt;

&lt;p&gt;The briefs also became a coordination tool. When we both know the day's schedule, we can divide and conquer without a morning logistics meeting over coffee. "I saw soccer is at 4 — I'll grab him, you do the grocery run" just happens naturally because we both read the same calendar data.&lt;/p&gt;

&lt;p&gt;It also forced me to centralize our calendars. Before building the brief, our calendars were a mess — some stuff on my Google Calendar, some on hers, kid stuff split between both. Making the brief useful meant getting all events into a system the brief could query. The automation created the organizational discipline I never had the motivation to build on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build your own
&lt;/h2&gt;

&lt;p&gt;If you want something similar, here's my honest advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with email delivery.&lt;/strong&gt; Don't build a dashboard. Don't build an app. Send yourself an email. You'll actually read it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the cheapest LLM that works.&lt;/strong&gt; Haiku 4.5 formats my brief perfectly. I don't need Opus for "make this weather data readable."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallelize from day one.&lt;/strong&gt; Serial API calls are the silent killer of morning automations. If your brief takes 60 seconds, it'll drift later and later as you add sections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build for two people.&lt;/strong&gt; Even if you're single, think about what a second person in your life would want. It forces you to make the system modular instead of hardcoded to your preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it for a week before adding features.&lt;/strong&gt; My first brief had 12 sections. I cut it to 7 after realizing I was skimming half of them. Less is more at 7am.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing is about 400 lines of Python, runs on a $5 VPS, and has been reliable for over a month. It's the single automation I'd rebuild first if I had to start over.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>python</category>
    </item>
  </channel>
</rss>
