<?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: Domonique Luchin</title>
    <description>The latest articles on Forem by Domonique Luchin (@domoniqueluchin).</description>
    <link>https://forem.com/domoniqueluchin</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%2F3816446%2F43258dea-c4c5-4bc1-95b1-9e05955201f1.png</url>
      <title>Forem: Domonique Luchin</title>
      <link>https://forem.com/domoniqueluchin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/domoniqueluchin"/>
    <language>en</language>
    <item>
      <title>Building a self-healing cron system with pg_cron and Supabase edge functions</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Mon, 11 May 2026 10:00:02 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/building-a-self-healing-cron-system-with-pgcron-and-supabase-edge-functions-5420</link>
      <guid>https://forem.com/domoniqueluchin/building-a-self-healing-cron-system-with-pgcron-and-supabase-edge-functions-5420</guid>
      <description>&lt;p&gt;I run 6 AI businesses from a single VPS. When your entire operation depends on automated tasks running perfectly, you learn to build systems that fix themselves before you wake up to angry customers.&lt;/p&gt;

&lt;p&gt;Here's how I built a cron system that monitors itself and recovers from failures automatically using pg_cron and Supabase Edge Functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I needed this
&lt;/h2&gt;

&lt;p&gt;My Load Bearing Empire processes thousands of AI agent calls daily. Lead scoring runs every 15 minutes. Data sync happens hourly. Payment processing triggers every 30 minutes. &lt;/p&gt;

&lt;p&gt;A single failed cron job costs me real money. I've been burned by silent failures too many times.&lt;/p&gt;

&lt;p&gt;Most developers rely on external monitoring services. I prefer owning my infrastructure. This system costs me $0 in additional subscriptions and runs entirely within Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;Three components work together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;pg_cron&lt;/strong&gt; schedules and executes jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge Functions&lt;/strong&gt; handle the actual business logic
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health monitoring table&lt;/strong&gt; tracks job status and triggers recovery&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: every cron job reports its status to a central monitoring table. If a job fails or doesn't report in, the system automatically retries and alerts me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the foundation
&lt;/h2&gt;

&lt;p&gt;First, enable pg_cron in your Supabase project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Run this in your SQL editor&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;pg_cron&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the monitoring table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_run&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_success&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'running'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;error_message&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Index for fast lookups&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_cron_health_job_name&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_cron_health_last_run&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a self-reporting Edge Function
&lt;/h2&gt;

&lt;p&gt;Here's an Edge Function that reports its own health status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// supabase/functions/process-leads/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.168.0/http/server.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&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;https://esm.sh/@supabase/supabase-js@2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;jobName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-leads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&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="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="c1"&gt;// Update status to running&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron_health&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="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;retry_count&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Your actual business logic here&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processLeads&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Report success&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron_health&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="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job_name&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;processed&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="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Report failure&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron_health&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="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;retry_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCurrentRetryCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;))&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job_name&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;h2&gt;
  
  
  The self-healing mechanism
&lt;/h2&gt;

&lt;p&gt;This monitoring function runs every 5 minutes and handles recovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create the health check function&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;check_cron_health&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="n"&gt;job_record&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;function_url&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;-- Find jobs that haven't reported success in their expected interval&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;job_record&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; 
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&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;last_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;-- Jobs that should run every 15 minutes but haven't succeeded in 20 minutes&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%leads%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'20 minutes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt;
      &lt;span class="c1"&gt;-- Jobs that should run hourly but haven't succeeded in 75 minutes  &lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%sync%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'75 minutes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="n"&gt;LOOP&lt;/span&gt;
    &lt;span class="c1"&gt;-- Build the Edge Function URL&lt;/span&gt;
    &lt;span class="n"&gt;function_url&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://your-project.supabase.co/functions/v1/'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;job_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Trigger retry via HTTP request&lt;/span&gt;
    &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_post&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;function_url&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="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"Authorization": "Bearer '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.service_role_key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'"}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Log the retry attempt&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&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;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'_retry'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'retry_triggered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retry_count&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;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scheduling everything
&lt;/h2&gt;

&lt;p&gt;Now wire it all together with pg_cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Schedule your business logic&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'process-leads'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*/15 * * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s1"&gt;'SELECT net.http_post(&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;https://your-project.supabase.co/functions/v1/process-leads&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;{"Authorization": "Bearer service_role_key"}&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="se"&gt;''''&lt;/span&gt;&lt;span class="s1"&gt;)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Schedule the health monitor&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'health-check'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*/5 * * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SELECT check_cron_health()'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Clean up old health records weekly&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cleanup-health'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0 2 * * 0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s1"&gt;'DELETE FROM cron_health WHERE created_at &amp;lt; NOW() - INTERVAL &lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;30 days&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring dashboard
&lt;/h2&gt;

&lt;p&gt;Query this to see your system health:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Current status of all jobs&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;minutes_since_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;retry_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;error_message&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%retry%'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;last_run&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real results
&lt;/h2&gt;

&lt;p&gt;Since implementing this system 3 months ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero silent failures&lt;/li&gt;
&lt;li&gt;4 automatic recoveries from network timeouts&lt;/li&gt;
&lt;li&gt;99.8% job success rate&lt;/li&gt;
&lt;li&gt;2 minutes average recovery time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get infrastructure that fixes itself. Your cron jobs report their health. Failed jobs retry automatically. You sleep better knowing your systems won't fail silently.&lt;/p&gt;

&lt;p&gt;Build systems that work without you watching them.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why I Own My Telecom Stack Instead of Paying $200/Month for VoIP SaaS</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Fri, 08 May 2026 10:00:02 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/why-i-own-my-telecom-stack-instead-of-paying-200month-for-voip-saas-13ce</link>
      <guid>https://forem.com/domoniqueluchin/why-i-own-my-telecom-stack-instead-of-paying-200month-for-voip-saas-13ce</guid>
      <description>&lt;p&gt;Business phone systems are a tax on not knowing how VoIP works.&lt;/p&gt;

&lt;p&gt;I stopped paying it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Market Charges
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Grasshopper: $49/month for 1 number, 3 extensions&lt;/li&gt;
&lt;li&gt;RingCentral: $99/month per user&lt;/li&gt;
&lt;li&gt;Dialpad: $75/month per user&lt;/li&gt;
&lt;li&gt;Google Voice for Business: $30/month per user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For six business lines with AI voice agents, I would spend $200 to $500 every month depending on the plan.&lt;/p&gt;

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

&lt;p&gt;$29.70 per month. Six lines. AI on every one.&lt;/p&gt;

&lt;p&gt;Breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VoIP.ms DIDs: 6 x $4.95 = $29.70/month&lt;/li&gt;
&lt;li&gt;Asterisk: free, runs on a VPS I already own&lt;/li&gt;
&lt;li&gt;VAPI: usage-based, low volume in early stage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Own vs What I Rent
&lt;/h2&gt;

&lt;p&gt;With SaaS I rent access. If they raise prices, change terms, or shut down, I lose the system.&lt;/p&gt;

&lt;p&gt;With Asterisk + VoIP.ms I own the PBX configuration, own the SIP trunk relationship, and can move providers without rebuilding anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Infrastructure Sovereignty Principle
&lt;/h2&gt;

&lt;p&gt;I apply five rules to every service in my stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multi-model abstracted — no single vendor lock-in&lt;/li&gt;
&lt;li&gt;Logged to Supabase in real time&lt;/li&gt;
&lt;li&gt;Self-hosted equivalent exists for every managed service&lt;/li&gt;
&lt;li&gt;Contingency defined before going live&lt;/li&gt;
&lt;li&gt;All data, prompts, and outputs are mine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Telecom is not special. The same logic applies. Own the stack or pay the tax forever.&lt;/p&gt;

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

&lt;p&gt;Asterisk has a learning curve. VoIP.ms requires KYC. PJSIP configuration takes a few hours to get right.&lt;/p&gt;

&lt;p&gt;After that, it runs without touching it. That is the trade. A few hard hours once, or $200 every month forever.&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>voip</category>
      <category>devops</category>
      <category>entrepreneurship</category>
    </item>
    <item>
      <title>From Gulf Coast Pipe Racks to AI Agent Orchestration: The Engineer's Path</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Thu, 07 May 2026 10:00:02 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/from-gulf-coast-pipe-racks-to-ai-agent-orchestration-the-engineers-path-120p</link>
      <guid>https://forem.com/domoniqueluchin/from-gulf-coast-pipe-racks-to-ai-agent-orchestration-the-engineers-path-120p</guid>
      <description>&lt;p&gt;I spent six years designing pipe racks and equipment platforms for Gulf Coast oil and gas facilities.&lt;/p&gt;

&lt;p&gt;Now I also build AI agent systems that run six businesses.&lt;/p&gt;

&lt;p&gt;These two things are not as different as they sound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structural Engineering as a Mental Model
&lt;/h2&gt;

&lt;p&gt;Structural engineering teaches you to think in systems. Every element has a load path. Every load path has a failure mode. You design for the failure mode before you design for the load.&lt;/p&gt;

&lt;p&gt;AI agent architecture works the same way.&lt;/p&gt;

&lt;p&gt;Every agent has an input path. Every input path has a failure mode — hallucination, timeout, bad routing, context loss. You design the guardrails before you design the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Engineering Gave Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tolerance for specification-heavy documentation&lt;/li&gt;
&lt;li&gt;Comfort with systems that have real consequences if they fail&lt;/li&gt;
&lt;li&gt;Habit of verifying assumptions before building&lt;/li&gt;
&lt;li&gt;Understanding that complexity compounds — keep it simple at the component level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All four apply directly to building production AI systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Translation
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engineering Concept&lt;/th&gt;
&lt;th&gt;AI Equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load path&lt;/td&gt;
&lt;td&gt;Data flow between agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode&lt;/td&gt;
&lt;td&gt;Hallucination or routing error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code reference (AISC, ACI)&lt;/td&gt;
&lt;td&gt;LLM system prompt constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shop drawings&lt;/td&gt;
&lt;td&gt;Agent tool schemas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFI&lt;/td&gt;
&lt;td&gt;Human approval gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Punch list&lt;/td&gt;
&lt;td&gt;Audit agent output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Path
&lt;/h2&gt;

&lt;p&gt;I did not leave engineering to build software. I layered software on top of engineering.&lt;/p&gt;

&lt;p&gt;The job funds the empire build. The empire builds the exit. The engineering knowledge makes both more defensible.&lt;/p&gt;

&lt;p&gt;You do not need to choose between technical depth and building businesses. The technical depth is the competitive advantage.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>ai</category>
      <category>career</category>
      <category>python</category>
    </item>
    <item>
      <title>Self-Hosted vs Managed: The Real Cost Breakdown After 6 Months</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Wed, 06 May 2026 10:00:02 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/self-hosted-vs-managed-the-real-cost-breakdown-after-6-months-564c</link>
      <guid>https://forem.com/domoniqueluchin/self-hosted-vs-managed-the-real-cost-breakdown-after-6-months-564c</guid>
      <description>&lt;p&gt;I run six businesses on a single $24/month VPS.&lt;/p&gt;

&lt;p&gt;Here is what that actually costs compared to the managed alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Stack vs the SaaS Equivalent
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;What I Use&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;th&gt;SaaS Equivalent&lt;/th&gt;
&lt;th&gt;SaaS Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Phone system&lt;/td&gt;
&lt;td&gt;Asterisk + VoIP.ms&lt;/td&gt;
&lt;td&gt;$29.70&lt;/td&gt;
&lt;td&gt;RingCentral&lt;/td&gt;
&lt;td&gt;$99/user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Supabase (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;PlanetScale&lt;/td&gt;
&lt;td&gt;$39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI agents&lt;/td&gt;
&lt;td&gt;VAPI + Claude API&lt;/td&gt;
&lt;td&gt;Usage&lt;/td&gt;
&lt;td&gt;Bland AI&lt;/td&gt;
&lt;td&gt;$150+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content pipeline&lt;/td&gt;
&lt;td&gt;Custom pg_cron + Edge Functions&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Buffer Pro&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lead scraping&lt;/td&gt;
&lt;td&gt;CrawlOS on VPS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;BatchLeads&lt;/td&gt;
&lt;td&gt;$299&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credit dispute automation&lt;/td&gt;
&lt;td&gt;Custom FCRA engine&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;DisputeBee&lt;/td&gt;
&lt;td&gt;$69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Vultr VPS (4 vCPU / 8 GB)&lt;/td&gt;
&lt;td&gt;$24&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;My total: ~$75/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SaaS equivalent: ~$800/month&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Costs of Self-Hosting
&lt;/h2&gt;

&lt;p&gt;Being honest here. Self-hosting is not free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time to build: 200+ hours over 6 months&lt;/li&gt;
&lt;li&gt;Time to maintain: 3 to 5 hours per week&lt;/li&gt;
&lt;li&gt;Debugging cost: High at first, drops significantly after 60 days&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Break-Even
&lt;/h2&gt;

&lt;p&gt;At $725/month savings, the build time pays off in the first month at a $50/hour rate.&lt;/p&gt;

&lt;p&gt;After month one, it is pure margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who This Makes Sense For
&lt;/h2&gt;

&lt;p&gt;If you can build it, own it. If you cannot build it and cannot learn to, pay for it.&lt;/p&gt;

&lt;p&gt;The math only works if the self-hosted version actually runs reliably. A broken self-hosted stack is worse than SaaS — you pay in time and still do not get the output.&lt;/p&gt;

&lt;p&gt;Build it right the first time. Then own it forever.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>selfhosted</category>
      <category>cloud</category>
      <category>entrepreneurship</category>
    </item>
    <item>
      <title>County Record Scraping: How CrawlOS Finds 125 Leads Per Night Across 14 Texas Counties</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 10:00:02 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/county-record-scraping-how-crawlos-finds-125-leads-per-night-across-14-texas-counties-naf</link>
      <guid>https://forem.com/domoniqueluchin/county-record-scraping-how-crawlos-finds-125-leads-per-night-across-14-texas-counties-naf</guid>
      <description>&lt;p&gt;Every night while I sleep, a Playwright scraper hits 14 Texas county deed record portals and logs 125 qualified leads to my CRM.&lt;/p&gt;

&lt;p&gt;Here is how CrawlOS works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Paid Data
&lt;/h2&gt;

&lt;p&gt;Real estate lead services charge $200 to $500 per month for the same public data that county appraisal districts publish for free.&lt;/p&gt;

&lt;p&gt;I built a scraper instead.&lt;/p&gt;

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

&lt;p&gt;CrawlOS is a Python + Playwright system running on lb-telecom-01 (my Vultr VPS in Dallas).&lt;/p&gt;

&lt;p&gt;It hits the public-facing portal for each county, extracts recent deed filings, filters by acquisition criteria, and inserts qualifying records into a Supabase table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 14 Counties
&lt;/h2&gt;

&lt;p&gt;Harris, Fort Bend, Montgomery, Brazoria, Galveston, Chambers, Liberty, Waller, Austin, Colorado, Wharton, Matagorda, Jackson, and Victoria.&lt;/p&gt;

&lt;p&gt;All Gulf Coast and surrounding markets — the territory I know from six years of O&amp;amp;G structural work in the region.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Filter Logic
&lt;/h2&gt;

&lt;p&gt;Not every deed transfer is a lead. The scraper applies filters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transfer type: warranty deed, not quitclaim&lt;/li&gt;
&lt;li&gt;Price range: within acquisition parameters&lt;/li&gt;
&lt;li&gt;Property type: residential or light commercial&lt;/li&gt;
&lt;li&gt;Recency: filed within the last 30 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything passing all four filters gets inserted. Everything else is discarded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Output
&lt;/h2&gt;

&lt;p&gt;125 leads per night on average. Each record includes parcel ID, grantor/grantee, transfer amount, address, and filing date.&lt;/p&gt;

&lt;p&gt;Load Bearing Capital works the list. Petroleum Noir runs a parallel filter on mineral rights transfers from the same pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The data is public. It has always been public. The advantage is not access — it is automation.&lt;/p&gt;

&lt;p&gt;Anyone can pull Harris County deed records manually. Almost nobody does it every night across 14 counties without lifting a finger.&lt;/p&gt;

</description>
      <category>python</category>
      <category>scraping</category>
      <category>automation</category>
      <category>realestate</category>
    </item>
    <item>
      <title>Building Load Bearing Empire From Your Phone During Off Hours</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:30:29 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-2fpc</link>
      <guid>https://forem.com/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-2fpc</guid>
      <description>&lt;p&gt;I do not have a home office setup. I work from my phone.&lt;/p&gt;

&lt;p&gt;Here is how I run six AI businesses after a full day of structural engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint
&lt;/h2&gt;

&lt;p&gt;I work 8 to 10 hours a day as a Lead Structural Engineer. When I get home I am tired. I do not have the energy to sit at a desk for three more hours.&lt;/p&gt;

&lt;p&gt;So I do not. I work from my couch, from my bed, during lunch breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Possible
&lt;/h2&gt;

&lt;p&gt;Automation handles the volume. I handle the decisions.&lt;/p&gt;

&lt;p&gt;My systems run nightly jobs, generate leads, draft content, and answer phones without me. I review outputs, approve or reject, and move on.&lt;/p&gt;

&lt;p&gt;The decision layer is the only part that requires my brain. I have compressed that to 30 to 60 minutes per night.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude on mobile — architecture, writing, strategy, debugging&lt;/li&gt;
&lt;li&gt;Supabase dashboard — checking table data, reviewing queue status&lt;/li&gt;
&lt;li&gt;Termius — SSH into lb-telecom-01 when something needs a direct fix&lt;/li&gt;
&lt;li&gt;Vercel — deployment monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check JARVIS memory table for overnight activity&lt;/li&gt;
&lt;li&gt;Review content queue — approve or reject pending posts&lt;/li&gt;
&lt;li&gt;Check lead pipeline — flag anything needing manual follow-up&lt;/li&gt;
&lt;li&gt;One new build decision per night — what gets worked on next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. Thirty minutes. Everything else runs itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Skill
&lt;/h2&gt;

&lt;p&gt;The skill is not technical. It is designing systems that do not require you.&lt;/p&gt;

&lt;p&gt;Every time I build something, I ask: what happens when I do not touch this for two weeks? If the answer is "it breaks," I have not built a system. I have built a job.&lt;/p&gt;

&lt;p&gt;Build systems, not jobs.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>entrepreneurship</category>
      <category>ai</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Building Load Bearing Empire From Your Phone During Off Hours</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:30:29 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-2i5h</link>
      <guid>https://forem.com/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-2i5h</guid>
      <description>&lt;p&gt;I do not have a home office setup. I work from my phone.&lt;/p&gt;

&lt;p&gt;Here is how I run six AI businesses after a full day of structural engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint
&lt;/h2&gt;

&lt;p&gt;I work 8 to 10 hours a day as a Lead Structural Engineer. When I get home I am tired. I do not have the energy to sit at a desk for three more hours.&lt;/p&gt;

&lt;p&gt;So I do not. I work from my couch, from my bed, during lunch breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Possible
&lt;/h2&gt;

&lt;p&gt;Automation handles the volume. I handle the decisions.&lt;/p&gt;

&lt;p&gt;My systems run nightly jobs, generate leads, draft content, and answer phones without me. I review outputs, approve or reject, and move on.&lt;/p&gt;

&lt;p&gt;The decision layer is the only part that requires my brain. I have compressed that to 30 to 60 minutes per night.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude on mobile — architecture, writing, strategy, debugging&lt;/li&gt;
&lt;li&gt;Supabase dashboard — checking table data, reviewing queue status&lt;/li&gt;
&lt;li&gt;Termius — SSH into lb-telecom-01 when something needs a direct fix&lt;/li&gt;
&lt;li&gt;Vercel — deployment monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check JARVIS memory table for overnight activity&lt;/li&gt;
&lt;li&gt;Review content queue — approve or reject pending posts&lt;/li&gt;
&lt;li&gt;Check lead pipeline — flag anything needing manual follow-up&lt;/li&gt;
&lt;li&gt;One new build decision per night — what gets worked on next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. Thirty minutes. Everything else runs itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Skill
&lt;/h2&gt;

&lt;p&gt;The skill is not technical. It is designing systems that do not require you.&lt;/p&gt;

&lt;p&gt;Every time I build something, I ask: what happens when I do not touch this for two weeks? If the answer is "it breaks," I have not built a system. I have built a job.&lt;/p&gt;

&lt;p&gt;Build systems, not jobs.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>entrepreneurship</category>
      <category>ai</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Building Load Bearing Empire From Your Phone During Off Hours</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:30:29 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-1d7k</link>
      <guid>https://forem.com/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-1d7k</guid>
      <description>&lt;p&gt;I do not have a home office setup. I work from my phone.&lt;/p&gt;

&lt;p&gt;Here is how I run six AI businesses after a full day of structural engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint
&lt;/h2&gt;

&lt;p&gt;I work 8 to 10 hours a day as a Lead Structural Engineer. When I get home I am tired. I do not have the energy to sit at a desk for three more hours.&lt;/p&gt;

&lt;p&gt;So I do not. I work from my couch, from my bed, during lunch breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Possible
&lt;/h2&gt;

&lt;p&gt;Automation handles the volume. I handle the decisions.&lt;/p&gt;

&lt;p&gt;My systems run nightly jobs, generate leads, draft content, and answer phones without me. I review outputs, approve or reject, and move on.&lt;/p&gt;

&lt;p&gt;The decision layer is the only part that requires my brain. I have compressed that to 30 to 60 minutes per night.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude on mobile — architecture, writing, strategy, debugging&lt;/li&gt;
&lt;li&gt;Supabase dashboard — checking table data, reviewing queue status&lt;/li&gt;
&lt;li&gt;Termius — SSH into lb-telecom-01 when something needs a direct fix&lt;/li&gt;
&lt;li&gt;Vercel — deployment monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check JARVIS memory table for overnight activity&lt;/li&gt;
&lt;li&gt;Review content queue — approve or reject pending posts&lt;/li&gt;
&lt;li&gt;Check lead pipeline — flag anything needing manual follow-up&lt;/li&gt;
&lt;li&gt;One new build decision per night — what gets worked on next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. Thirty minutes. Everything else runs itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Skill
&lt;/h2&gt;

&lt;p&gt;The skill is not technical. It is designing systems that do not require you.&lt;/p&gt;

&lt;p&gt;Every time I build something, I ask: what happens when I do not touch this for two weeks? If the answer is "it breaks," I have not built a system. I have built a job.&lt;/p&gt;

&lt;p&gt;Build systems, not jobs.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>entrepreneurship</category>
      <category>ai</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The pg_cron Bug That Silently Killed My Content Pipeline for Weeks</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:30:27 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/the-pgcron-bug-that-silently-killed-my-content-pipeline-for-weeks-2o91</link>
      <guid>https://forem.com/domoniqueluchin/the-pgcron-bug-that-silently-killed-my-content-pipeline-for-weeks-2o91</guid>
      <description>&lt;p&gt;For weeks my content pipeline was running. Zero posts were going out. No errors in the logs.&lt;/p&gt;

&lt;p&gt;Here is what happened.&lt;/p&gt;

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

&lt;p&gt;I use pg_cron on Supabase to schedule content jobs. A cron job fires every hour, calls an edge function, and the function posts queued articles to dev.to.&lt;/p&gt;

&lt;p&gt;Simple enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;Articles were sitting in the queue with status &lt;code&gt;ready&lt;/code&gt;. The cron job showed successful execution in &lt;code&gt;cron.job_run_details&lt;/code&gt;. The edge function showed 200 responses.&lt;/p&gt;

&lt;p&gt;Nothing was posting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;The edge function was calling the dev.to API with an Authorization header formatted as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: api_key YOUR_KEY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dev.to expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header name was wrong. The API returned a 401. The edge function caught the error, logged it to a column I was not monitoring, and returned 200 to pg_cron anyway.&lt;/p&gt;

&lt;p&gt;The job looked healthy. The posts never went out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Correct the header. Add explicit error surfacing so a failed post sets status to &lt;code&gt;error&lt;/code&gt; and writes the response body to &lt;code&gt;error_message&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;build_content_queue&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Changed Going Forward
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Never trust a 200 from your own wrapper. Inspect the downstream response code.&lt;/li&gt;
&lt;li&gt;Write failures visibly — status column, not just a log.&lt;/li&gt;
&lt;li&gt;Add a daily alert if zero posts go out in a 24-hour window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Silent failures are the worst kind. They look like everything is working until you check the actual output.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>supabase</category>
      <category>debugging</category>
      <category>devops</category>
    </item>
    <item>
      <title>How StructCalc AI Helped Me Land a Structural Engineering Role</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:29:51 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/how-structcalc-ai-helped-me-land-a-structural-engineering-role-33ji</link>
      <guid>https://forem.com/domoniqueluchin/how-structcalc-ai-helped-me-land-a-structural-engineering-role-33ji</guid>
      <description>&lt;p&gt;StructCalc AI is a structural engineering calculation platform I built from scratch.&lt;/p&gt;

&lt;p&gt;It is also the project that made my ESI interview concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Is
&lt;/h2&gt;

&lt;p&gt;StructCalc AI is a full-stack application with 25 pages and 15 calculation engines covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AISC 360-22 steel design&lt;/li&gt;
&lt;li&gt;ACI 318-19 concrete design&lt;/li&gt;
&lt;li&gt;ASCE 7-22 load combinations and wind/seismic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frontend: React + Vite deployed on Vercel. Backend: FastAPI on Railway. Database: Supabase.&lt;/p&gt;

&lt;p&gt;Live at structcalc-ai.vercel.app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;I was tired of paying for structural software licenses I only used occasionally. I wanted a web-based tool I could open on any device.&lt;/p&gt;

&lt;p&gt;So I built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;19 verified API endpoints. Calculations for beam bending, column buckling, connection design, load takedowns, and more. The AI layer walks you through inputs and flags code non-compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Helped in the Interview
&lt;/h2&gt;

&lt;p&gt;When ESI asked about my technical background, I did not just list experience. I pointed to a live production application that implemented the same code references I use daily.&lt;/p&gt;

&lt;p&gt;Building the tool forced me to re-derive every equation from the specification. You cannot fake that level of understanding in an interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Point
&lt;/h2&gt;

&lt;p&gt;If you are an engineer trying to stand out — build something. Not a resume bullet. A working application.&lt;/p&gt;

&lt;p&gt;The market is full of engineers with 10 years of experience and nothing to show outside a PDF. Build one real thing and you separate yourself immediately.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>ai</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>CLAWKILLER: My 8-Agent LangGraph Autonomous Ops Platform</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:29:15 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/clawkiller-my-8-agent-langgraph-autonomous-ops-platform-1iol</link>
      <guid>https://forem.com/domoniqueluchin/clawkiller-my-8-agent-langgraph-autonomous-ops-platform-1iol</guid>
      <description>&lt;p&gt;CLAWKILLER is the name I gave my AI orchestration system.&lt;/p&gt;

&lt;p&gt;It runs six businesses from a single server. Here is the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;I could not afford to context-switch between six companies during a full-time engineering job. I needed a system that could intake a goal, plan execution, use tools, and complete the task without me sitting there.&lt;/p&gt;

&lt;p&gt;LangGraph gave me the graph-based agent orchestration I needed. Supabase gave me persistent memory. Claude and GPT-4o gave me the reasoning layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 8 Agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SUPERVISOR&lt;/strong&gt; — Command parser and router. Every task enters here first. Routes to the appropriate specialist agent based on intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BROWSER&lt;/strong&gt; — Playwright-powered web agent. Account creation, form fills, web scraping, social posting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CONTENT&lt;/strong&gt; — Writes articles, captions, scripts. Pulls from session logs and build notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;COMMS&lt;/strong&gt; — Drafts emails, SMS, and Slack messages. Handles follow-ups and outreach sequences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DATA&lt;/strong&gt; — Queries Supabase, runs reports, pulls lead pipeline status across all six companies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SCRAPER&lt;/strong&gt; — Nightly lead generation. Hits county deed records, filters by acquisition criteria, logs to CRM tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SCHEDULER&lt;/strong&gt; — Creates calendar events, queues time-sensitive tasks, manages approval gates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AUDIT&lt;/strong&gt; — Reviews completed tasks for errors, logs anomalies, surfaces issues requiring human review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory Architecture
&lt;/h2&gt;

&lt;p&gt;Every agent writes state to Supabase after each step. Session continuity does not depend on keeping a single process alive.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;jarvis_memory&lt;/code&gt; table stores intent, execution history, tool results, and status. Any agent can query it to pick up where another left off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control Plane vs Intelligence Layer
&lt;/h2&gt;

&lt;p&gt;CLAWKILLER is the control plane. Claude and GPT-4o are the intelligence layer.&lt;/p&gt;

&lt;p&gt;CLAWKILLER decides what runs and when. The LLMs decide how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;p&gt;Schema live on Supabase (project: whxtjboruayowkqyjvcq). Services running on lb-telecom-01 ports 8000, 3001, and 3000. Claude Code installed for dispatch via the claude_code_queue table.&lt;/p&gt;

&lt;p&gt;This is not a demo project. It runs production workloads every night.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>langgraph</category>
    </item>
    <item>
      <title>How I Built a Self-Hosted PBX With Asterisk and VAPI for 6 Business Phone Lines</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:28:39 +0000</pubDate>
      <link>https://forem.com/domoniqueluchin/how-i-built-a-self-hosted-pbx-with-asterisk-and-vapi-for-6-business-phone-lines-596f</link>
      <guid>https://forem.com/domoniqueluchin/how-i-built-a-self-hosted-pbx-with-asterisk-and-vapi-for-6-business-phone-lines-596f</guid>
      <description>&lt;p&gt;Six businesses. Six phone numbers. One server. Zero monthly phone system fees.&lt;/p&gt;

&lt;p&gt;Here is exactly how I built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Every business needs a phone line. Business phone systems charge $30 to $80 per line per month. For six lines that is $180 to $480 every month just to receive calls.&lt;/p&gt;

&lt;p&gt;I refused to pay that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asterisk 18.10&lt;/strong&gt; — open-source PBX running on lb-telecom-01 (Vultr Dallas VPS at 45.32.198.24)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VoIP.ms&lt;/strong&gt; — SIP trunk provider, KYC approved, six 832-area-code DIDs at $4.95/month each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VAPI&lt;/strong&gt; — AI voice agent layer sitting in front of Asterisk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total monthly cost: $29.70 for six lines. Not per line. Total.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DIDs
&lt;/h2&gt;

&lt;p&gt;Six numbers provisioned on Flat Rate through dallas1.voip.ms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1315 — Load Bearing Capital&lt;/li&gt;
&lt;li&gt;1316 — Load Bearing Demo&lt;/li&gt;
&lt;li&gt;1317 — Quiet Hours Valet&lt;/li&gt;
&lt;li&gt;1318 — Luchin Credit Repair&lt;/li&gt;
&lt;li&gt;1319 — Petroleum Noir&lt;/li&gt;
&lt;li&gt;1321 — Load Bearing Detailing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How VAPI Connects
&lt;/h2&gt;

&lt;p&gt;VAPI sits as the public-facing layer. Calls come in, VAPI answers with a brand-specific AI voice agent, qualifies the caller, and routes or logs based on intent.&lt;/p&gt;

&lt;p&gt;Asteisk handles the SIP registration and trunk. VAPI handles the intelligence.&lt;/p&gt;

&lt;p&gt;The PJSIP trunk is registered to account 528502 on VoIP.ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Replaced
&lt;/h2&gt;

&lt;p&gt;Grasshoppa: $49/month. RingCentral: $99/month. Dialpad: $75/month.&lt;/p&gt;

&lt;p&gt;I pay $29.70 and own the entire stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build It Yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Spin a VPS (Vultr or Linode, 1 vCPU / 1 GB RAM is enough)&lt;/li&gt;
&lt;li&gt;Install Asterisk 18 from source&lt;/li&gt;
&lt;li&gt;Create a VoIP.ms account, complete KYC, provision DIDs on Flat Rate&lt;/li&gt;
&lt;li&gt;Configure PJSIP trunk in Asterisk pointing to your VoIP.ms account&lt;/li&gt;
&lt;li&gt;Register your VAPI agents to the SIP endpoint&lt;/li&gt;
&lt;li&gt;Test inbound call flow per DID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want AI on the phones, VAPI is the fastest path. If you want pure routing only, Asterisk dialplan handles it natively.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>selfhosted</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
