<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>allanninal.dev/build</title>
  <subtitle>Diagram-first design walkthroughs of automations, DevOps, and cloud infrastructure by Allan Niñal. Open to builds, collabs, and design reviews.</subtitle>
  <link href="https://www.allanninal.dev/build/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://www.allanninal.dev/build/" rel="alternate" type="text/html"/>
  <updated>2026-07-07T09:00:00Z</updated>
  <id>https://www.allanninal.dev/build/</id>
  <author>
    <name>Allan Niñal</name>
  </author>
  <rights>&#169; 2026 Allan Niñal</rights>

  <entry>
    <title>An upsell recommender on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/upsell-recommender-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/upsell-recommender-on-aws/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; the order webhook, the pick where a model proposes and catalogue rules dispose, a one-tap add link, and take-up tracking, plus the guardrail that it sends one offer per order, caps how often anyone is nudged, and says nothing when nothing fits. Part 1 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>How an order triggers a suggestion</title>
    <link href="https://www.allanninal.dev/build/how-an-order-triggers-a-suggestion/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-order-triggers-a-suggestion/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">How a placed order becomes a candidate for a suggestion &amp;mdash; the store webhook, signature checks, one-order-one-offer dedup, the opt-out and per-customer frequency-cap gate, and gathering what was bought and the customer&amp;rsquo;s history before anything is picked. Part 2 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>How the add-ons get picked</title>
    <link href="https://www.allanninal.dev/build/how-the-add-ons-get-picked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-add-ons-get-picked/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">How the add-on gets chosen &amp;mdash; the single Bedrock call that proposes a ranked shortlist, and the deterministic catalogue rules that dispose of it: in stock, genuinely complementary, sensibly priced, not already owned. The top survivor wins, and if the shortlist empties, no offer is sent. Part 3 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>How the offer gets sent</title>
    <link href="https://www.allanninal.dev/build/how-the-offer-gets-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-offer-gets-sent/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">How the offer reaches the customer &amp;mdash; the short, deliberate delay before sending, the one nudge composed from the chosen add-on, and the one-tap add link: a signed token on a Lambda Function URL that records the tap and redirects straight into the store&amp;rsquo;s add-to-cart. Part 4 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>How a take-up gets tracked</title>
    <link href="https://www.allanninal.dev/build/how-a-take-up-gets-tracked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-take-up-gets-tracked/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">How a take-up gets measured &amp;mdash; the one-tap link recording the tap inside an attribution window, the per-customer frequency cap that keeps the next nudge honest, and the scheduled sweep that closes the window so every offer ends up counted as taken, ignored, or never sent. Part 5 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>What the upsell recommender costs</title>
    <link href="https://www.allanninal.dev/build/what-the-upsell-recommender-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-upsell-recommender-costs/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.60 for roughly 150 orders, where the money actually goes (one Bedrock call per order is the biggest line here, then the nudge), and what the bill looks like at ten times the volume. Part 6 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the upsell recommender architecture</title>
    <link href="https://www.allanninal.dev/build/upsell-recommender-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/upsell-recommender-engineering-reference/</id>
    <published>2026-07-07T09:00:00Z</published>
    <updated>2026-07-07T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the two Function URLs, the EventBridge send and sweep, DynamoDB tables and key schema, how the pick stays idempotent, SNS and SES, IAM scope, the Bedrock model id, and the region. Part 7 of 7 in the Upsell recommender series.</summary>
  </entry>
  <entry>
    <title>A warranty registration handler on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/warranty-registration-handler-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/warranty-registration-handler-on-aws/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; the QR-or-form submission, verification against order records, the term-and-expiry maths, the stored record, and the scheduled reminders, plus the guardrail that a serial registers exactly once and duplicates and out-of-window purchases are turned away. Part 1 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>How a registration gets submitted</title>
    <link href="https://www.allanninal.dev/build/how-a-registration-gets-submitted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-registration-gets-submitted/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">How a registration gets submitted &amp;mdash; the QR sticker or short web form, the single Lambda Function URL that receives the post, the anti-abuse checks, and how a raw submission becomes a clean, claimed job carrying the serial and proof of purchase. Part 2 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>How a purchase gets verified</title>
    <link href="https://www.allanninal.dev/build/how-a-purchase-gets-verified/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-purchase-gets-verified/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">How a purchase gets verified &amp;mdash; matching the serial and proof of purchase against the business&amp;rsquo;s order records, rejecting serials that were never sold, duplicates already registered, and purchases outside the eligibility window, before a warranty is ever created. Part 3 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>How a warranty record gets stored</title>
    <link href="https://www.allanninal.dev/build/how-a-warranty-record-gets-stored/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-warranty-record-gets-stored/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">How a warranty record gets stored &amp;mdash; computing the term and the exact expiry date from the product and purchase, claiming the serial with a conditional write so it registers exactly once, and writing a queryable record with the expiry indexed for the reminder sweep. Part 4 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>How expiry reminders get scheduled</title>
    <link href="https://www.allanninal.dev/build/how-expiry-reminders-get-scheduled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-expiry-reminders-get-scheduled/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">How expiry reminders get scheduled &amp;mdash; the EventBridge Scheduler sweep that reads the indexed expiry dates, sends maintenance nudges partway through the term and a heads-up before the cover lapses, each exactly once and honouring opt-out. Part 5 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>What the warranty registration handler costs</title>
    <link href="https://www.allanninal.dev/build/what-the-warranty-registration-handler-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-warranty-registration-handler-costs/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $1.80 for roughly 200 registrations, where the money actually goes (a small Bedrock call and the reminder messages), and what the bill looks like at ten times the volume. Part 6 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the warranty registration handler architecture</title>
    <link href="https://www.allanninal.dev/build/warranty-registration-handler-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/warranty-registration-handler-engineering-reference/</id>
    <published>2026-07-06T09:00:00Z</published>
    <updated>2026-07-06T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the registration Function URL, the serial ledger and warranty tables with the expiry GSI, the reminder scheduler, SES and SNS, the Bedrock model id, IAM scope, and the region. Part 7 of 7 in the Warranty registration handler series.</summary>
  </entry>
  <entry>
    <title>A booking deposit collector on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/booking-deposit-collector-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/booking-deposit-collector-on-aws/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; the booking intake that holds a slot, the deposit-request composer, the signature-verified payment webhook that confirms exactly once, the scheduled sweep that releases unpaid holds, and settlement on the day, plus the guardrail that it never double-books. Part 1 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>How a booking requests a deposit</title>
    <link href="https://www.allanninal.dev/build/how-a-booking-requests-a-deposit/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-booking-requests-a-deposit/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">How a booking becomes a held slot and a deposit request &amp;mdash; the intake Function URL, the conditional write that holds one slot without ever double-booking, the deposit deadline, and the single Bedrock call that phrases the payment-link message. Part 2 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>How a deposit payment gets confirmed</title>
    <link href="https://www.allanninal.dev/build/how-a-deposit-payment-gets-confirmed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-deposit-payment-gets-confirmed/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">How a paid deposit becomes a confirmed booking &amp;mdash; the payment provider&amp;rsquo;s webhook on its own Function URL, verified by signature, treated idempotently, and flipping the booking to CONFIRMED exactly once with a DynamoDB conditional write even when the event arrives twice. Part 3 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>How an unpaid hold gets released</title>
    <link href="https://www.allanninal.dev/build/how-an-unpaid-hold-gets-released/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-unpaid-hold-gets-released/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">How an unpaid hold gets released &amp;mdash; the EventBridge Scheduler sweep that finds bookings whose deposit deadline has passed, sends one reminder before the deadline, and releases the hold so the slot is free to re-sell, exactly once and never twice. Part 4 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>How a no-show gets handled</title>
    <link href="https://www.allanninal.dev/build/how-a-no-show-gets-handled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-no-show-gets-handled/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">How a no-show gets handled &amp;mdash; on the day, the confirmed deposit is either applied to the final bill for a customer who turns up or forfeited for one who doesn&amp;rsquo;t, decided by deterministic policy rather than by a model, with the outcome recorded and a human handling any dispute. Part 5 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>What the booking deposit collector costs</title>
    <link href="https://www.allanninal.dev/build/what-the-booking-deposit-collector-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-booking-deposit-collector-costs/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.40 for roughly 120 bookings, where the money actually goes (the deposit-request texts and the small Bedrock calls that phrase them), why the payment processor&amp;rsquo;s fee isn&amp;rsquo;t on this bill, and what it all looks like at ten times the volume. Part 6 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the booking deposit collector architecture</title>
    <link href="https://www.allanninal.dev/build/booking-deposit-collector-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/booking-deposit-collector-engineering-reference/</id>
    <published>2026-07-05T09:00:00Z</published>
    <updated>2026-07-05T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the two Function URLs, the Lambda inventory, the DynamoDB tables and key schema, how the slot hold and the payment confirmation are each made exactly-once, the SQS and DLQ, IAM scope, observability, and the single region. Part 7 of 7 in the Booking deposit collector series.</summary>
  </entry>
  <entry>
    <title>A referral tracker on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/referral-tracker-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/referral-tracker-on-aws/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; minting a unique referral link, logging the click behind a redirect, attributing a sign-up on a last-touch window, crediting both sides exactly once on conversion, and the fraud screen that blocks self-referrals, dupes, and codes shared into the void. Part 1 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>How a referral link gets minted</title>
    <link href="https://www.allanninal.dev/build/how-a-referral-link-gets-minted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-referral-link-gets-minted/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">How a referral link is created &amp;mdash; a customer asks, the system mints a unique short code, one Bedrock call writes a shareable invite in the business&amp;rsquo;s voice, and the code, the referrer, and the reward terms are recorded before the link ever goes out. Part 2 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>How a referral gets attributed</title>
    <link href="https://www.allanninal.dev/build/how-a-referral-gets-attributed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-referral-gets-attributed/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">How a referral is attributed &amp;mdash; the redirect that logs each click, the last-touch window that decides which referrer gets the credit at sign-up, and why a click and a sign-up alone earn nothing until a real conversion follows. Part 3 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>How a reward gets issued</title>
    <link href="https://www.allanninal.dev/build/how-a-reward-gets-issued/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reward-gets-issued/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">How a reward is issued &amp;mdash; the conversion webhook that confirms a genuine first purchase, the conditional write that pays both sides exactly once, and the single Bedrock call that writes the two thank-you notes while code does the actual crediting. Part 4 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>How referral fraud gets caught</title>
    <link href="https://www.allanninal.dev/build/how-referral-fraud-gets-caught/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-referral-fraud-gets-caught/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">How referral fraud is caught &amp;mdash; self-referrals matched on identity, the same person converting twice collapsed to one reward, and velocity checks that hold a code spraying clicks or sign-ups for a human, all before anyone is paid. Part 5 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>What the referral tracker costs</title>
    <link href="https://www.allanninal.dev/build/what-the-referral-tracker-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-referral-tracker-costs/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $1.70 &amp;mdash; the cheapest system here &amp;mdash; where the money actually goes (mostly a fixed Secrets Manager line, with redirects and writes rounding to cents), and what the bill looks like at ten times the volume. Part 6 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the referral tracker architecture</title>
    <link href="https://www.allanninal.dev/build/referral-tracker-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/referral-tracker-engineering-reference/</id>
    <published>2026-07-04T09:00:00Z</published>
    <updated>2026-07-04T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the redirect-and-webhook Function URL, the DynamoDB tables and key schema, how exactly-once crediting is enforced, the last-touch window and the expiry sweep, IAM scope, and the region. Part 7 of 7 in the Referral tracker series.</summary>
  </entry>
  <entry>
    <title>A review request sender on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/review-request-sender-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/review-request-sender-on-aws/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; the completion webhook, a scheduled one-per-job ask, a sentiment gate that splits happy from unhappy, a personalised review link for the glad and a private feedback form for the rest, plus the guardrail that it asks each customer once and never nags. Part 1 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>How a completed job triggers a request</title>
    <link href="https://www.allanninal.dev/build/how-a-completed-job-triggers-a-request/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-completed-job-triggers-a-request/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">How a finished job becomes a scheduled ask &amp;mdash; the completion webhook, signature checks, the one-request-per-job guard, the opt-out list, and capturing whatever signal the customer already left before anything is scheduled. Part 2 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>How the ask gets timed</title>
    <link href="https://www.allanninal.dev/build/how-the-ask-gets-timed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-ask-gets-timed/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">How the ask gets timed &amp;mdash; a per-request one-off EventBridge Scheduler schedule set to a sensible delay, snapped inside opening hours, re-checked at fire time for opt-out and once-only, and rescheduled rather than sent if the moment has drifted into quiet hours. Part 3 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>How the review ask gets written</title>
    <link href="https://www.allanninal.dev/build/how-the-review-ask-gets-written/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-review-ask-gets-written/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">How the review ask gets written &amp;mdash; the single Bedrock call handed only the facts it may use, a review-link token injected by code, the one-message rule, and why the model writes the words but never decides whether, when, or to whom to send. Part 4 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>How unhappy customers get filtered</title>
    <link href="https://www.allanninal.dev/build/how-unhappy-customers-get-filtered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-unhappy-customers-get-filtered/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">How unhappy customers get filtered &amp;mdash; the sentiment gate that grades whatever signal exists, sends the glad to a public review link and the unhappy to a private feedback form, and escalates a real complaint to a person &amp;mdash; so a bad experience is caught in private, never aired as a one-star. Part 5 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>What the review request sender costs</title>
    <link href="https://www.allanninal.dev/build/what-the-review-request-sender-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-review-request-sender-costs/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $1.90 for roughly 150 completed jobs, where the money actually goes (two small Bedrock calls per job and the messages), and what the bill looks like at ten times the volume. Part 6 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the review request sender architecture</title>
    <link href="https://www.allanninal.dev/build/review-request-sender-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/review-request-sender-engineering-reference/</id>
    <published>2026-07-03T09:00:00Z</published>
    <updated>2026-07-03T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the completion Function URL, the per-request EventBridge schedules, DynamoDB tables and key schema, the two Bedrock calls, SES and SNS, IAM scope, and the region. Part 7 of 7 in the Review request sender series.</summary>
  </entry>
  <entry>
    <title>A back-in-stock notifier on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/back-in-stock-notifier-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/back-in-stock-notifier-on-aws/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; the notify-me capture, restock detection from the inventory webhook, a FIFO notifier capped to the units that came in, short-lived reserve links, and an expiry sweep &amp;mdash; plus the guarantee that it notifies in order and never oversells. Part 1 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>How a back-in-stock request gets captured</title>
    <link href="https://www.allanninal.dev/build/how-a-back-in-stock-request-gets-captured/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-back-in-stock-request-gets-captured/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">How a &amp;ldquo;notify me&amp;rdquo; tap becomes a place in the queue &amp;mdash; the capture Function URL, validating the request, one active request per person per SKU, the opt-out check, and appending it to the per-SKU waiting list in join order. Part 2 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>How a restock gets detected</title>
    <link href="https://www.allanninal.dev/build/how-a-restock-gets-detected/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-restock-gets-detected/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">How a restock is detected &amp;mdash; the store&amp;rsquo;s inventory webhook, verifying it, spotting the genuine below-to-above-threshold transition, working out how many units came in, and firing exactly one notify batch per restock without flapping or double-firing. Part 3 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>How the waiting list gets notified</title>
    <link href="https://www.allanninal.dev/build/how-the-waiting-list-gets-notified/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-waiting-list-gets-notified/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">How the waiting list gets notified &amp;mdash; reading the per-SKU queue oldest-first, capping the batch to the units available so it never oversells, one Bedrock call to phrase the notice, a personalised reserve link each, and marking every recipient notified once per restock. Part 4 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>How a reservation gets held</title>
    <link href="https://www.allanninal.dev/build/how-a-reservation-gets-held/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reservation-gets-held/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">How a reservation is held &amp;mdash; the reserve link that holds a unit with a conditional write that can&amp;rsquo;t oversell, the short window it&amp;rsquo;s live for, and the EventBridge Scheduler sweep that expires unclaimed holds and offers the freed unit to the next person in line. Part 5 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>What the back-in-stock notifier costs</title>
    <link href="https://www.allanninal.dev/build/what-the-back-in-stock-notifier-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-back-in-stock-notifier-costs/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.10 for roughly 120 notify-me requests, where the money actually goes (outbound SMS and one Bedrock call per restock), and what the bill looks like at ten times the volume. Part 6 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the back-in-stock notifier architecture</title>
    <link href="https://www.allanninal.dev/build/back-in-stock-notifier-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/back-in-stock-notifier-engineering-reference/</id>
    <published>2026-07-02T09:00:00Z</published>
    <updated>2026-07-02T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the three Function URLs, the SQS-buffered notifier, the DynamoDB tables and key schema, the reservation holds and TTL, the expiry schedule, IAM scope, the Bedrock model id, and the region. Part 7 of 7 in the Back-in-stock notifier series.</summary>
  </entry>
  <entry>
    <title>A missed-call text-back on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/missed-call-text-back-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/missed-call-text-back-on-aws/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; the missed-call webhook, caller matching, a one-text-back composer, two-way reply routing, and a no-reply escalation sweep, plus the guardrail that it sends exactly one text and never spams. Part 1 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>How a missed call gets caught</title>
    <link href="https://www.allanninal.dev/build/how-a-missed-call-gets-caught/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-missed-call-gets-caught/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">How a missed call becomes a job &amp;mdash; the provider webhook, signature checks, the one-text-back dedup window, quiet hours and STOP opt-out, and matching the caller&amp;rsquo;s number to a known customer before anything is sent. Part 2 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>How a text-back gets composed</title>
    <link href="https://www.allanninal.dev/build/how-a-text-back-gets-composed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-text-back-gets-composed/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">How a text-back gets composed &amp;mdash; the single Bedrock call handed only the facts it may use, the booking link and FAQ, the one-message-per-call rule, and why the model never decides whether or to whom to send. Part 3 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>How a reply gets routed</title>
    <link href="https://www.allanninal.dev/build/how-a-reply-gets-routed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reply-gets-routed/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">How a reply gets routed &amp;mdash; inbound texts threaded two-way to the right person or queue, matched back to the original missed call, with urgent or unmatched messages escalated to a human rather than auto-answered. Part 4 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>How an unanswered call-back gets escalated</title>
    <link href="https://www.allanninal.dev/build/how-an-unanswered-callback-gets-escalated/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-unanswered-callback-gets-escalated/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">How an unanswered call-back gets escalated &amp;mdash; the EventBridge Scheduler sweep that finds text-backs with no reply after a set window, and hands the caller to a person to ring back rather than letting the thread go cold. Part 5 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>What the missed-call text-back costs</title>
    <link href="https://www.allanninal.dev/build/what-the-missed-call-text-back-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-missed-call-text-back-costs/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.20 for roughly 150 missed calls, where the money actually goes (outbound SMS and one Bedrock call), and what the bill looks like at ten times the volume. Part 6 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the missed-call text-back architecture</title>
    <link href="https://www.allanninal.dev/build/missed-call-text-back-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/missed-call-text-back-engineering-reference/</id>
    <published>2026-07-01T09:00:00Z</published>
    <updated>2026-07-01T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the webhook Function URL, the EventBridge escalation sweep, DynamoDB tables and key schema, SNS and SES, IAM scope, the Bedrock model id, and the region. Part 7 of 7 in the Missed-call text-back series.</summary>
  </entry>
  <entry>
    <title>A chargeback responder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/chargeback-responder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/chargeback-responder-on-aws/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; a dispute intake, an evidence-and-packet engine, and a filing desk that beats the deadline &amp;mdash; plus the four kinds of evidence every rebuttal is built from and the one rule underneath it all: never fabricate. Part 1 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>How a dispute gets caught</title>
    <link href="https://www.allanninal.dev/build/how-a-dispute-gets-caught/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-dispute-gets-caught/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">How a dispute is caught the moment it happens: a signed processor webhook hits a Lambda Function URL, the signature is verified, the event is de-duplicated, the bank&amp;rsquo;s evidence deadline is recorded, and a one-off deadline watch is armed &amp;mdash; all before any evidence is gathered. Part 2 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>How the evidence gets gathered</title>
    <link href="https://www.allanninal.dev/build/how-the-evidence-gets-gathered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-evidence-gets-gathered/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">How the evidence gets gathered: one Lambda pulls the four things a bank actually weighs &amp;mdash; the order record, proof of delivery and tracking, the customer&amp;rsquo;s own messages, and the agreed policy &amp;mdash; each from its real source, into an S3 evidence bucket and a DynamoDB index, recording honestly where a piece is missing. Part 3 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>How an evidence packet gets built</title>
    <link href="https://www.allanninal.dev/build/how-an-evidence-packet-gets-built/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-evidence-packet-gets-built/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">How an evidence packet gets built: Bedrock reads the gathered evidence and the reason code, ties each real piece to the specific claim the bank is testing, renders a structured packet PDF, and gives an honest winnability read &amp;mdash; recommending you accept the loss when the evidence isn&amp;rsquo;t there. Part 4 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>How a response gets filed on time</title>
    <link href="https://www.allanninal.dev/build/how-a-response-gets-filed-on-time/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-response-gets-filed-on-time/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">How a response gets filed on time: the deadline watch is the spine of this post &amp;mdash; a one-off EventBridge Scheduler job fires a safe margin before the bank&amp;rsquo;s due date, the owner&amp;rsquo;s File/Hold/Accept choice is honoured, and if nothing has been filed the deadline guard files the assembled packet rather than ever let a case lapse. Part 5 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>What the chargeback responder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-chargeback-responder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-chargeback-responder-costs/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">What the chargeback responder costs: a line-by-line breakdown that sums to about $2.40/month at ~25 disputes, dominated by the Bedrock packet-assembly call, plus what it looks like at ten times the volume &amp;mdash; and why a single recovered order pays for a year of it. Part 6 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the chargeback responder architecture</title>
    <link href="https://www.allanninal.dev/build/chargeback-responder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/chargeback-responder-engineering-reference/</id>
    <published>2026-06-30T09:00:00Z</published>
    <updated>2026-06-30T09:00:00Z</updated>
    <summary type="text">The engineering reference: the full AWS topology, the Lambda inventory with runtimes, the EventBridge rule and Scheduler deadline-watch config, the three DynamoDB tables and their key schemas, the S3 evidence bucket, the SES and Function URL setup, IAM scopes, the Bedrock model id, and the region. Part 7 of 7 in the Chargeback responder series.</summary>
  </entry>
  <entry>
    <title>A delivery route planner on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/delivery-route-planner-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/delivery-route-planner-on-aws/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; gather the stops, optimise the order, build the manifest, send the ETAs &amp;mdash; plus the guardrail that the route is a proposal and plain code, not a model, does the optimising. Part 1 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>How the day’s stops get gathered</title>
    <link href="https://www.allanninal.dev/build/how-the-days-stops-get-gathered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-days-stops-get-gathered/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">How the day&amp;rsquo;s stops become a clean, geocoded list &amp;mdash; reading the orders mirror, turning addresses into coordinates with a cache, and validating time windows and capacity before any routing happens. Part 2 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>How a route gets optimized</title>
    <link href="https://www.allanninal.dev/build/how-a-route-gets-optimized/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-route-gets-optimized/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">How a list of stops becomes a sensible order &amp;mdash; the distance matrix, a nearest-neighbour seed, 2-opt to uncross the route, and time windows and capacity enforced as hard constraints. A worked example, and an honest note on what the heuristic does and doesn&amp;rsquo;t promise. Part 3 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>How a driver manifest gets built</title>
    <link href="https://www.allanninal.dev/build/how-a-driver-manifest-gets-built/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-driver-manifest-gets-built/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">How the optimised route becomes something a driver can actually use &amp;mdash; an ordered manifest with addresses, access notes, and time windows, rendered to a PDF in S3 and sent as a link, with the driver free to re-order or skip. Part 4 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>How customer ETAs get sent</title>
    <link href="https://www.allanninal.dev/build/how-customer-etas-get-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-customer-etas-get-sent/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">How each customer gets a real ETA window and how it stays honest &amp;mdash; one Bedrock-drafted message per stop, driver check-offs flowing back through EventBridge, and the remaining ETAs recomputed from where the van actually is. Part 5 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>What the delivery route planner costs</title>
    <link href="https://www.allanninal.dev/build/what-the-delivery-route-planner-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-delivery-route-planner-costs/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $3.10 for roughly 1,000 stops, where the money actually goes &amp;mdash; the geocoding and matrix API is the main variable &amp;mdash; and what the bill looks like at higher volume. Part 6 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the delivery route planner architecture</title>
    <link href="https://www.allanninal.dev/build/delivery-route-planner-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/delivery-route-planner-engineering-reference/</id>
    <published>2026-06-29T09:00:00Z</published>
    <updated>2026-06-29T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the EventBridge Scheduler build and the check-off events, DynamoDB tables and key schemas, S3, SES and SNS, IAM scope, the region, and the routing and geocoding service. Part 7 of 7 in the Delivery route planner series.</summary>
  </entry>
  <entry>
    <title>A return and RMA handler on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/return-rma-handler-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/return-rma-handler-on-aws/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; a Step Functions state machine that runs request, eligibility, RMA and label, await inbound, inspect, and decide &amp;mdash; plus the rule that a human approves every refund or replacement. Part 1 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>How a return request gets checked</title>
    <link href="https://www.allanninal.dev/build/how-a-return-request-gets-checked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-return-request-gets-checked/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">How a return request is checked against policy &amp;mdash; the order lookup, the plain-code checks for return window, non-returnable flags, condition, and prior returns &amp;mdash; and why a fail is declined with the exact reason rather than guessed. Part 2 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>How an RMA and label get issued</title>
    <link href="https://www.allanninal.dev/build/how-an-rma-and-label-get-issued/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-rma-and-label-get-issued/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">How an approved request becomes an RMA number and a prepaid label &amp;mdash; the unique RMA, the carrier label API call keyed from Secrets Manager, the label stored in S3, and the one email that tells the customer exactly what to do. Part 3 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>How an inbound parcel gets tracked</title>
    <link href="https://www.allanninal.dev/build/how-an-inbound-parcel-gets-tracked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-inbound-parcel-gets-tracked/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">How the inbound parcel is tracked while the state machine waits &amp;mdash; the paused execution holding a task token, the carrier webhook that resumes it on a real scan, and the scheduled sweep that catches parcels that never come back. Part 4 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>How a refund or replacement gets decided</title>
    <link href="https://www.allanninal.dev/build/how-a-refund-or-replacement-gets-decided/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-refund-or-replacement-gets-decided/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">How a refund or a replacement gets decided &amp;mdash; the inspection that records the real condition, the plain-code decision that proposes refund or replace per policy with the reason attached, and the human who approves before anything moves. Part 5 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>What the return and RMA handler costs</title>
    <link href="https://www.allanninal.dev/build/what-the-return-rma-handler-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-return-rma-handler-costs/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost of about $2.90 for roughly 120 returns, where the money actually goes, why the Step Functions transitions and the carrier label API are the only things that scale, and what the bill looks like at ten times the volume. Part 6 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the return and RMA handler architecture</title>
    <link href="https://www.allanninal.dev/build/return-rma-handler-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/return-rma-handler-engineering-reference/</id>
    <published>2026-06-28T09:00:00Z</published>
    <updated>2026-06-28T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Step Functions state machine and its states, the Lambda inventory, the EventBridge rules and the sweep schedule, the DynamoDB returns table and key schema, S3, SES, IAM scope, and the region. Part 7 of 7 in the Return and RMA handler series.</summary>
  </entry>
  <entry>
    <title>A stock count reconciler on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/stock-count-reconciler-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/stock-count-reconciler-on-aws/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; a count capture, a variance-and-cause engine, and an approval-and-write-back piece, plus the four likely causes every flagged gap is labelled with. Part 1 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>How a count gets captured</title>
    <link href="https://www.allanninal.dev/build/how-a-count-gets-captured/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-count-gets-captured/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">How a count comes in &amp;mdash; staff submit a scan or a sheet, it lands in S3, an event fires, and the capture Lambda normalises it into clean counted lines tied to a count session, ready to reconcile. Part 2 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>How a variance gets found</title>
    <link href="https://www.allanninal.dev/build/how-a-variance-gets-found/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-variance-gets-found/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">How a variance gets found &amp;mdash; the engine joins each counted line to the system quantity, computes the gap in units, percent, and value, and decides whether it crosses the materiality tolerance. Plain code, no model. Part 3 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>How a likely cause gets suggested</title>
    <link href="https://www.allanninal.dev/build/how-a-likely-cause-gets-suggested/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-likely-cause-gets-suggested/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">How a likely cause gets suggested &amp;mdash; for each material variance the system builds a small evidence bundle and asks one Bedrock call to pick a cause from a fixed list, with a one-line rationale. The model labels; it never moves stock. Part 4 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>How an adjustment gets approved</title>
    <link href="https://www.allanninal.dev/build/how-an-adjustment-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-adjustment-gets-approved/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">How an adjustment gets approved &amp;mdash; material variances are queued, a manager gets an email with the gap and the suggested cause and two buttons, and only an approved adjustment is posted back to inventory with a full audit trail. Part 5 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>What the stock count reconciler costs</title>
    <link href="https://www.allanninal.dev/build/what-the-stock-count-reconciler-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-stock-count-reconciler-costs/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The variance arithmetic is free; the cost is one small Bedrock call per variance line and a single Secrets Manager secret. Costs at three volumes, and what doesn&amp;rsquo;t cost anything. Part 6 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the stock count reconciler architecture</title>
    <link href="https://www.allanninal.dev/build/stock-count-reconciler-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/stock-count-reconciler-engineering-reference/</id>
    <published>2026-06-27T09:00:00Z</published>
    <updated>2026-06-27T09:00:00Z</updated>
    <summary type="text">The same system drawn purely for engineers &amp;mdash; the full architecture diagram, every Lambda and Function URL, the EventBridge rules and schedules, the DynamoDB tables and key schemas, S3, SES, IAM scopes, the Bedrock model id, and the region. Part 7 of 7 in the Stock count reconciler series.</summary>
  </entry>
  <entry>
    <title>A recurring invoice generator on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/recurring-invoice-generator-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/recurring-invoice-generator-on-aws/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; contracts and retainers in, a per-contract scheduler, a builder that does the arithmetic in plain code, a PDF render, a send, and a ledger, plus the clean hand-off of anything overdue to a separate chaser. Part 1 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>How a billing schedule gets set up</title>
    <link href="https://www.allanninal.dev/build/how-a-billing-schedule-gets-set-up/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-billing-schedule-gets-set-up/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">How a contract or retainer becomes a live billing schedule &amp;mdash; the cycle (monthly, quarterly, per-milestone), the anchor date, the proration on signup, and the per-contract EventBridge Scheduler entry that fires on each due date. Part 2 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>How an invoice gets built</title>
    <link href="https://www.allanninal.dev/build/how-an-invoice-gets-built/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-invoice-gets-built/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">How the builder turns a due contract into a finished invoice &amp;mdash; pulling the line items, assigning the next number in sequence, assembling the document, and writing it to the ledger &amp;mdash; all in plain, deterministic code with no model anywhere near a figure. Part 3 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>How proration and tax get applied</title>
    <link href="https://www.allanninal.dev/build/how-proration-and-tax-get-applied/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-proration-and-tax-get-applied/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">A worked example, in numbers &amp;mdash; a mid-cycle upgrade prorated by calendar days, VAT applied per jurisdiction, the rounding rule stated, and the exact workings written to the ledger. The arithmetic is plain code; no model touches the money. Part 4 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>How an invoice gets sent and handed off</title>
    <link href="https://www.allanninal.dev/build/how-an-invoice-gets-sent-and-handed-off/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-invoice-gets-sent-and-handed-off/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">How a built invoice gets rendered to a PDF, emailed to the customer, and recorded &amp;mdash; and how an invoice that passes its due date unpaid is handed to a separate chaser through a queue, rather than nagged from here. Part 5 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>What the recurring invoice generator costs</title>
    <link href="https://www.allanninal.dev/build/what-the-recurring-invoice-generator-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-recurring-invoice-generator-costs/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.00 for roughly 120 invoices, where the money actually goes (mostly Secrets Manager), and what the bill looks like at ten times the volume. Part 6 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the recurring invoice generator architecture</title>
    <link href="https://www.allanninal.dev/build/recurring-invoice-generator-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/recurring-invoice-generator-engineering-reference/</id>
    <published>2026-06-26T09:00:00Z</published>
    <updated>2026-06-26T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the Lambda inventory, the per-contract EventBridge Scheduler design, the DynamoDB contracts and ledger tables with their key schemas, the versioned S3 PDF bucket, SES, IAM scope, and the region. Part 7 of 7 in the Recurring invoice generator series.</summary>
  </entry>
  <entry>
    <title>A job status notifier on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/job-status-notifier-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/job-status-notifier-on-aws/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; a job board outside, a stage watcher, a message composer, and a sender inside, plus the daily sweep and the guardrail that it never invents a stage. Part 1 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>How a job stage gets updated</title>
    <link href="https://www.allanninal.dev/build/how-a-job-stage-gets-updated/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-job-stage-gets-updated/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">How a move on the board becomes a trusted stage change &amp;mdash; the poll, the comparison against the last recorded stage, the forward-only and known-stage checks, and the single event that fires. Part 2 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>How a customer update gets composed</title>
    <link href="https://www.allanninal.dev/build/how-a-customer-update-gets-composed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-customer-update-gets-composed/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">How one short, honest update gets written &amp;mdash; the facts handed in (stage, ETA, photo), the single Bedrock call, the shop&amp;rsquo;s voice, and why the model can never invent a stage or an ETA. Part 3 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>How photos and ETAs get attached</title>
    <link href="https://www.allanninal.dev/build/how-photos-and-etas-get-attached/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-photos-and-etas-get-attached/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">How a photo of the work and a real ETA get attached &amp;mdash; the photo dropped at the bench landing in S3 and linked to the job, and the ETA built from per-stage durations with an honest buffer. Part 4 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>How a quiet stage gets chased</title>
    <link href="https://www.allanninal.dev/build/how-a-quiet-stage-gets-chased/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-quiet-stage-gets-chased/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">How a job that has sat too long in one stage gets chased &amp;mdash; the daily sweep, the per-stage dwell thresholds, the holding update to the customer and the nudge to the shop, and the quiet-hours and dedupe guards. Part 5 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>What the job status notifier costs</title>
    <link href="https://www.allanninal.dev/build/what-the-job-status-notifier-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-job-status-notifier-costs/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.30 for roughly 200 jobs, where the money actually goes, why SMS is the line that moves most, and what the bill looks like at ten times the volume. Part 6 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the job status notifier architecture</title>
    <link href="https://www.allanninal.dev/build/job-status-notifier-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/job-status-notifier-engineering-reference/</id>
    <published>2026-06-25T09:00:00Z</published>
    <updated>2026-06-25T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; Lambda inventory, the EventBridge bus and schedules, DynamoDB tables and keys, the S3 photos bucket, SES and SNS, IAM scope, the Bedrock model id, and the region. Part 7 of 7 in the Job status notifier series.</summary>
  </entry>
  <entry>
    <title>A product image cleaner on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/product-image-cleaner-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/product-image-cleaner-on-aws/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; a photo lands in a folder, an S3 upload event starts the pipeline, a container Lambda removes the background, plain image ops cut the channel variants, and the finished set is written back, with a review lane for doubtful cutouts. Part 1 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>How a raw photo gets ingested</title>
    <link href="https://www.allanninal.dev/build/how-a-raw-photo-gets-ingested/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-raw-photo-gets-ingested/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">How a raw phone photo becomes a job &amp;mdash; the watched folder and email lane that land it in S3, the upload event that triggers the pipeline, and the normalisation that turns a 3,000-pixel HEIC into a clean, oriented working file. Part 2 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>How a background gets removed</title>
    <link href="https://www.allanninal.dev/build/how-a-background-gets-removed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-background-gets-removed/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">How the background gets removed &amp;mdash; why it runs in a container Lambda on arm64 with extra memory, what rembg and U^2-Net actually do, and the confidence check that sends a doubtful cutout to review instead of onward. Part 3 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>How channel variants get made</title>
    <link href="https://www.allanninal.dev/build/how-channel-variants-get-made/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-channel-variants-get-made/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">How the channel variants get made &amp;mdash; auto-cropping to the product, padding to a safe area, resizing to each channel&amp;rsquo;s exact spec, compositing onto white, and the watermark rule that keeps marketplace images clean. Part 4 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>How a cleaned image gets published</title>
    <link href="https://www.allanninal.dev/build/how-a-cleaned-image-gets-published/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-cleaned-image-gets-published/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">How a cleaned image gets published &amp;mdash; writing the finished set back to S3 under per-channel prefixes, the manifest that records what was made, the notification that says it&amp;rsquo;s ready, and why nothing overwrites the original. Part 5 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>What the product image cleaner costs</title>
    <link href="https://www.allanninal.dev/build/what-the-product-image-cleaner-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-product-image-cleaner-costs/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $3.40 for roughly 400 images, why the background-removal container Lambda is the biggest variable line, and what the bill looks like at ten times the volume. Part 6 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the product image cleaner architecture</title>
    <link href="https://www.allanninal.dev/build/product-image-cleaner-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/product-image-cleaner-engineering-reference/</id>
    <published>2026-06-24T09:00:00Z</published>
    <updated>2026-06-24T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; the zip and container Lambda inventory with memory and runtime, the S3 buckets and event notifications, the DynamoDB job table and key schema, IAM scope, and the region. Part 7 of 7 in the Product image cleaner series.</summary>
  </entry>
  <entry>
    <title>A lead enricher on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/lead-enricher-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/lead-enricher-on-aws/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; intake, normalise, enrich, dedupe, and a junk filter in front of the CRM &amp;mdash; and the one rule that shapes it: clean and complete, never score or route, never overwrite. Part 1 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>How a raw lead gets normalized</title>
    <link href="https://www.allanninal.dev/build/how-a-raw-lead-gets-normalized/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-raw-lead-gets-normalized/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">A deterministic normaliser fixes name casing, formats the phone to E.164 and the email to a canonical form, and resolves the country &amp;mdash; with one small Bedrock call only for the genuinely ambiguous bits. Part 2 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>How a lead gets enriched</title>
    <link href="https://www.allanninal.dev/build/how-a-lead-gets-enriched/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-lead-gets-enriched/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">From a clean email domain, a firmographic lookup adds the company, its size, industry, and location &amp;mdash; with the API key in Secrets Manager, the result cached, and every field traceable back to its source. Part 3 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>How a duplicate gets caught</title>
    <link href="https://www.allanninal.dev/build/how-a-duplicate-gets-caught/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-duplicate-gets-caught/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">An identity index mirrors the CRM on normalised keys; an exact-key check catches obvious repeats and a fuzzy pass catches the rest, with Bedrock judging only the genuinely ambiguous near-matches. Part 4 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>How junk gets filtered out</title>
    <link href="https://www.allanninal.dev/build/how-junk-gets-filtered-out/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-junk-gets-filtered-out/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">A layered junk filter &amp;mdash; cheap deterministic checks first, a bounded model judgement only for the borderline cases &amp;mdash; drops obvious spam before anything is written, and logs every drop so nothing vanishes silently. Part 5 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>What the lead enricher costs</title>
    <link href="https://www.allanninal.dev/build/what-the-lead-enricher-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-lead-enricher-costs/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">A line-by-line cost breakdown at about 1,000 leads a month summing to roughly $2.50, where the enrichment API is the main variable, plus what changes at 10&amp;times; the volume. Part 6 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the lead enricher architecture</title>
    <link href="https://www.allanninal.dev/build/lead-enricher-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/lead-enricher-engineering-reference/</id>
    <published>2026-06-23T09:00:00Z</published>
    <updated>2026-06-23T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers: the Lambda inventory, the schedules, the DynamoDB dedupe index and its key schema, the S3 and SES setup, IAM scoping, the Bedrock model id, and the region. Part 7 of 7 in the Lead enricher series.</summary>
  </entry>
  <entry>
    <title>A gift card ledger on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/gift-card-ledger-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/gift-card-ledger-on-aws/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; an issue piece, a redeem piece, and the scheduled expiry and reconciliation &amp;mdash; plus the one idea everything rests on: an atomic conditional write that makes a balance impossible to spend twice. Part 1 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>How a gift card gets issued</title>
    <link href="https://www.allanninal.dev/build/how-a-gift-card-gets-issued/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-gift-card-gets-issued/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">Issuing mints a unique, hard-to-guess code and an opening balance in integer cents, writes the card with a strict "only if it does not already exist" condition, and appends the first immutable row to the ledger. One card, one code, one opening balance. Part 2 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>How a balance gets redeemed</title>
    <link href="https://www.allanninal.dev/build/how-a-balance-gets-redeemed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-balance-gets-redeemed/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">The centrepiece. A redemption is a single atomic DynamoDB conditional write &amp;mdash; subtract the amount only if the balance still covers it &amp;mdash; so the same balance can never be spent twice from two tills at once, and a balance can never go negative. Part 3 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>How fraud caps stop abuse</title>
    <link href="https://www.allanninal.dev/build/how-fraud-caps-stop-abuse/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-fraud-caps-stop-abuse/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">Velocity and value caps that stop a leaked code being drained &amp;mdash; a per-redemption ceiling, a daily limit, and a count of redemptions in a window &amp;mdash; all checked in the same atomic write as the balance, so a flagged card freezes rather than leaks. Part 4 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>How a card expires and reconciles</title>
    <link href="https://www.allanninal.dev/build/how-a-card-expires-and-reconciles/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-card-expires-and-reconciles/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">A nightly expiry sweep retires dormant cards per policy and books the leftover balance as breakage; a monthly reconciliation re-derives every balance from the ledger and ties issued, redeemed, expired, and outstanding liability into one figure the owner can trust. Part 5 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>What the gift card ledger costs</title>
    <link href="https://www.allanninal.dev/build/what-the-gift-card-ledger-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-gift-card-ledger-costs/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">A line-by-line cost breakdown that sums to about $2.10/month at roughly 300 cards issued and 800 redemptions &amp;mdash; where the atomic redemption costs a fraction of a cent &amp;mdash; plus what the bill looks like at ten times the volume. Part 6 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the gift card ledger architecture</title>
    <link href="https://www.allanninal.dev/build/gift-card-ledger-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/gift-card-ledger-engineering-reference/</id>
    <published>2026-06-22T09:00:00Z</published>
    <updated>2026-06-22T09:00:00Z</updated>
    <summary type="text">The same system drawn purely for engineers: the Lambda inventory, the two schedules, the DynamoDB tables with their key schema and the exact conditional-write and transaction design, S3 and SES, IAM scopes, and the single region. Part 7 of 7 in the Gift card ledger series.</summary>
  </entry>
  <entry>
    <title>A dunning recovery system on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/dunning-recovery-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/dunning-recovery-on-aws/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; a failed-charge intake, a retry engine, and an access-and-mail piece &amp;mdash; plus the five states every dunning subscription moves through. Part 1 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>How a failed charge gets caught</title>
    <link href="https://www.allanninal.dev/build/how-a-failed-charge-gets-caught/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-failed-charge-gets-caught/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">How a failed charge is caught the moment it happens: a signed payment-processor webhook hits a Lambda Function URL, the signature is verified, the event is de-duplicated, and the subscription enters dunning &amp;mdash; all before any retry is scheduled. Part 2 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>How a retry schedule gets decided</title>
    <link href="https://www.allanninal.dev/build/how-a-retry-schedule-gets-decided/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-retry-schedule-gets-decided/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">How a retry schedule gets decided: a plain-Python backoff over several days that skips weekends, written as one-off EventBridge Scheduler rules, with a hard idempotency guard so a charge is never retried twice. Part 3 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>How a dunning email gets sent</title>
    <link href="https://www.allanninal.dev/build/how-a-dunning-email-gets-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-dunning-email-gets-sent/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">How a branded dunning email gets sent at each attempt: a template from S3, one Bedrock Haiku call to write copy in your voice that escalates gently, an embedded update-card link, and SES outbound &amp;mdash; with bounces and complaints handled. Part 4 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>How access gets paused and restored</title>
    <link href="https://www.allanninal.dev/build/how-access-gets-paused-and-restored/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-access-gets-paused-and-restored/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">How access gets paused after the final failed retry and restored the instant a payment succeeds or a card is updated: an entitlement check your app calls, a reversible paused state, and never a silent cancellation. Part 5 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>What dunning recovery costs</title>
    <link href="https://www.allanninal.dev/build/what-the-dunning-recovery-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-dunning-recovery-costs/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">What dunning recovery costs: a line-by-line breakdown that sums to about $1.90/month at ~150 subscriptions and ~30 failed charges, dominated by Secrets Manager, plus what it looks like at ten times the volume. Part 6 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the dunning recovery architecture</title>
    <link href="https://www.allanninal.dev/build/dunning-recovery-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/dunning-recovery-engineering-reference/</id>
    <published>2026-06-21T09:00:00Z</published>
    <updated>2026-06-21T09:00:00Z</updated>
    <summary type="text">The engineering reference: the full AWS topology, the Lambda inventory with runtimes, the EventBridge Scheduler config, the DynamoDB tables and key schemas, the S3 bucket, the SES setup, IAM scope notes, the Bedrock model id, and the region. Part 7 of 7 in the Dunning recovery series.</summary>
  </entry>
  <entry>
    <title>An order status responder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/order-status-responder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/order-status-responder-on-aws/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">The whole system on one page &amp;mdash; three inbound channels, a matcher, a live-tracking lookup, a reply writer, and an escalation lane, plus the guardrail that it never invents a status. Part 1 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>How a “where’s my order?” message gets matched</title>
    <link href="https://www.allanninal.dev/build/how-a-where-is-my-order-message-gets-matched/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-where-is-my-order-message-gets-matched/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">How an inbound message becomes a confident order match &amp;mdash; order number first, then sender email or phone &amp;mdash; and why ambiguous and no-match cases are escalated rather than guessed. Part 2 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>How live tracking gets pulled</title>
    <link href="https://www.allanninal.dev/build/how-live-tracking-gets-pulled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-live-tracking-gets-pulled/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">How the responder turns a matched order into live carrier tracking &amp;mdash; the carrier API call, the normalised status, the cached result, and what it does when the carrier is silent. Part 3 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>How a status reply gets written</title>
    <link href="https://www.allanninal.dev/build/how-a-status-reply-gets-written/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-status-reply-gets-written/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">How one short, accurate reply gets written &amp;mdash; the Bedrock call that is handed only the real tracking facts, the one-reply rule, and why the model can never invent a status. Part 4 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>How a stuck order gets escalated</title>
    <link href="https://www.allanninal.dev/build/how-a-stuck-order-gets-escalated/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-stuck-order-gets-escalated/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">How the ones that shouldn&amp;rsquo;t get an automatic reply reach a human &amp;mdash; unmatched messages, upset customers, and orders the carrier shows as delayed, lost, or stuck &amp;mdash; each with full context. Part 5 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>What the order status responder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-order-status-responder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-order-status-responder-costs/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">A line-by-line monthly cost at about $2.80 for roughly 500 enquiries, where the money actually goes, and what the bill looks like at ten times the volume. Part 6 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the order status responder architecture</title>
    <link href="https://www.allanninal.dev/build/order-status-responder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/order-status-responder-engineering-reference/</id>
    <published>2026-06-20T09:00:00Z</published>
    <updated>2026-06-20T09:00:00Z</updated>
    <summary type="text">The same system drawn for engineers &amp;mdash; Lambda inventory, EventBridge schedules, DynamoDB tables and keys, S3 buckets, SES rules, IAM scope, the Bedrock model id, and the region. Part 7 of 7 in the Order status responder series.</summary>
  </entry>
  <entry>
    <title>A supplier bill matcher on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/bill-matcher-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/bill-matcher-on-aws/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a bill intake, a matcher, and an approval desk, plus the four outcomes every bill lands in: matched, price variance, quantity variance, or no PO. Part 1 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>How a supplier bill gets read</title>
    <link href="https://www.allanninal.dev/build/how-a-supplier-bill-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-supplier-bill-gets-read/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">Three lanes bring bills in — an emailed-PDF lane parsed by Textract, a supplier-portal poll, and a manual upload — and one Bedrock call per bill turns the raw read into clean lines. Part 2 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>How a bill gets matched three ways</title>
    <link href="https://www.allanninal.dev/build/how-a-bill-gets-matched-three-ways/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-bill-gets-matched-three-ways/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">A plain-Python matcher lines the bill up against the purchase order and the goods-received note, checks item, quantity, and price against tolerances, and picks one of four outcomes. No model on the match. Part 3 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>How a mismatch reaches the right person</title>
    <link href="https://www.allanninal.dev/build/how-a-mismatch-reaches-the-right-person/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-mismatch-reaches-the-right-person/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">Routing per outcome, the right approver per supplier, a second sign-off for big spend, quiet hours, and an email carrying the exact failing line and the exact gap, with Approve, Query, and Reject. Part 4 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>How a bill gets approved for payment</title>
    <link href="https://www.allanninal.dev/build/how-a-bill-gets-approved-for-payment/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-bill-gets-approved-for-payment/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">Three actions on every bill: approve (clear for the payment run, with a reason if it overrides a flag), query (send a templated question back to the supplier), and reject. Every action is logged; no bill pays itself. Part 5 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>What the bill matcher costs</title>
    <link href="https://www.allanninal.dev/build/what-the-bill-matcher-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-bill-matcher-costs/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">About $3.20/month at ~200 bills. The three-way match is essentially free; the cost is Textract reading each bill and one small Bedrock call per bill to clean the lines. Part 6 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the bill matcher architecture</title>
    <link href="https://www.allanninal.dev/build/bill-matcher-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/bill-matcher-engineering-reference/</id>
    <published>2026-06-19T09:00:00Z</published>
    <updated>2026-06-19T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Region, service names, Bedrock model IDs, the Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Supplier bill matcher series.</summary>
  </entry>
  <entry>
    <title>A weekly report builder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/weekly-report-builder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/weekly-report-builder-on-aws/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a gather step, a writer step, and a sender step — plus the one rule they all share: every number in the report comes from your data. Part 1 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>How the numbers get gathered</title>
    <link href="https://www.allanninal.dev/build/how-the-numbers-get-gathered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-numbers-get-gathered/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">Each source you list — a sales sheet, a Stripe or bank CSV, a point-of-sale roll-up — is mirrored to S3 on a schedule, then read and turned into one clean set of weekly figures with comparisons attached. Part 2 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>How the weekly report gets written</title>
    <link href="https://www.allanninal.dev/build/how-the-weekly-report-gets-written/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-weekly-report-gets-written/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">Plain Python computes every figure and every comparison first. Only then does one Bedrock call turn those real numbers into a short paragraph, and every figure it mentions is checked back against the data before sending. Part 3 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>How the report reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-the-report-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-report-reaches-the-owner/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">The Monday send: the right recipients, the owner's timezone, a hold if the week's data isn't complete, and a composed email with the summary, the numbers table, and any flags. Part 4 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>How a number that looks off gets flagged</title>
    <link href="https://www.allanninal.dev/build/how-a-number-that-looks-off-gets-flagged/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-number-that-looks-off-gets-flagged/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">Plain-Python checks catch a stale source, a figure far outside its normal range, or a total that doesn't reconcile. Anything that trips goes in a Needs a look section — shown, but marked unverified, never reported as fact. Part 5 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>What the weekly report builder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-weekly-report-builder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-weekly-report-builder-costs/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The builder runs once a week, gathers and compares with plain Python, and only fires one Bedrock call to write the summary paragraph. Part 6 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the weekly report builder architecture</title>
    <link href="https://www.allanninal.dev/build/weekly-report-builder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/weekly-report-builder-engineering-reference/</id>
    <published>2026-06-18T09:00:00Z</published>
    <updated>2026-06-18T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: region, service names, Bedrock model IDs, Lambda inventory, IAM scopes, EventBridge Scheduler config, the DynamoDB schemas, and the grounding contract that keeps the model from ever sourcing a number. Part 7 of 7 in the Weekly report builder series.</summary>
  </entry>
  <entry>
    <title>A content moderator on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/content-moderator-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/content-moderator-on-aws/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an intake, a checker, and a review piece, plus the three calls they share for every item: pass, hold, or send to a human. Part 1 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>How a comment gets checked</title>
    <link href="https://www.allanninal.dev/build/how-a-comment-gets-checked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-comment-gets-checked/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">Every comment, review, or post arrives through one webhook, gets cleaned and stored once, and a fast rule pass settles the easy cases before any model is asked to look. Part 2 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>How a comment gets a verdict</title>
    <link href="https://www.allanninal.dev/build/how-a-comment-gets-a-verdict/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-comment-gets-a-verdict/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">Only the borderline middle goes to Bedrock Haiku 4.5, which returns one of a few calls — pass, hold, or send to a human — with a confidence score and the exact house rule cited. Part 3 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>How flagged content reaches a moderator</title>
    <link href="https://www.allanninal.dev/build/how-flagged-content-reaches-a-moderator/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-flagged-content-reaches-a-moderator/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">The review queue, who owns each area, quiet hours and batching so nobody is pinged at 2am, and the four guardrails between a held verdict and the review card landing. Part 4 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>How the moderator makes the final call</title>
    <link href="https://www.allanninal.dev/build/how-the-moderator-makes-the-final-call/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-moderator-makes-the-final-call/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">Three actions on the review card: publish a held item, remove it with the reason logged, or edit-and-publish. Removal is always a human's call, and every overturn teaches the system. Part 5 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>What the content moderator costs</title>
    <link href="https://www.allanninal.dev/build/what-the-content-moderator-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-content-moderator-costs/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The fast rule pass settles most items with no model; Bedrock fires only on the borderline middle and the weekly digest. Part 6 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the content moderator architecture</title>
    <link href="https://www.allanninal.dev/build/content-moderator-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/content-moderator-engineering-reference/</id>
    <published>2026-06-17T09:00:00Z</published>
    <updated>2026-06-17T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — region, service names, Bedrock model IDs, Lambda inventory, IAM scopes, the SQS review queue, EventBridge config, and the DynamoDB schemas. Part 7 of 7 in the Content moderator series.</summary>
  </entry>
  <entry>
    <title>A shift scheduler on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/shift-scheduler-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/shift-scheduler-on-aws/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">The whole system on one page: a week intake, a drafter, and a publish piece, plus the rule that the manager approves every schedule and every swap before anything reaches staff. Part 1 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>How a shift week gets set up</title>
    <link href="https://www.allanninal.dev/build/how-a-shift-week-gets-set-up/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-shift-week-gets-set-up/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">Three lanes feed the week: the Drive sheet itself, a time-off note lane that reads plain-English requests into dates for one-tap approval, and a recurring template lane that fills in the standing shifts. Part 2 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>How a fair rota gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-fair-rota-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-fair-rota-gets-drafted/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">A weekly run reads availability, hours targets, and skills, then places people into shifts by rule, preferring whoever is furthest below their target. It picks one of four outcomes per shift: filled, fair-swap, short-staffed, or held. No model on the draft. Part 3 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>How a schedule reaches the team</title>
    <link href="https://www.allanninal.dev/build/how-a-schedule-reaches-the-team/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-schedule-reaches-the-team/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">Manager approval first, then per-person delivery, quiet hours, calendar invites, Slack DMs with each person's own shifts, email fallback, and the four guardrails between the approved draft and the schedule landing. Part 4 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>How a shift swap gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-shift-swap-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-shift-swap-gets-approved/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">Three actions on a swap request: cover (find a qualified replacement, route to the manager), drop (give the shift back to the open pool), and time-off (hold the day for the next draft). Every action is logged and needs a manager yes. Part 5 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>What the shift scheduler costs</title>
    <link href="https://www.allanninal.dev/build/what-the-shift-scheduler-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-shift-scheduler-costs/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">Pennies a month at small-team volume. The drafter runs once a week, calls no models on the draft, and only fires Bedrock on the time-off note lane and the weekly fairness summary. About $2.20/month at 15 staff. Part 6 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the shift scheduler architecture</title>
    <link href="https://www.allanninal.dev/build/shift-scheduler-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/shift-scheduler-engineering-reference/</id>
    <published>2026-06-16T09:00:00Z</published>
    <updated>2026-06-16T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Region, service names, resource identifiers, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Shift scheduler series.</summary>
  </entry>
  <entry>
    <title>A menu sync on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/menu-sync-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/menu-sync-on-aws/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a change intake, a planner, and a publisher, plus the four moves they share for every item and channel. Part 1 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>How a menu change gets made</title>
    <link href="https://www.allanninal.dev/build/how-a-menu-change-gets-made/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-menu-change-gets-made/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">Three lanes feed the master menu: the Drive sheet itself, a phone quick-edit lane for sold-out and small tweaks mid-service, and a supplier-price lane that parses forwarded lists into proposed prices for one-tap approval. Part 2 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>How a change turns into updates</title>
    <link href="https://www.allanninal.dev/build/how-a-change-turns-into-updates/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-change-turns-into-updates/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">The planner reads the menu, diffs each item against what each channel currently shows, checks the change against the auto-sync limits, and picks one of four moves: in sync, push, hold, or flag. No model on the planner. Part 3 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>How an update reaches each place</title>
    <link href="https://www.allanninal.dev/build/how-an-update-reaches-each-place/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-update-reaches-each-place/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">Channel resolution per item, the approval gate for held changes, per-channel formatting, idempotent sends, and the four guardrails between the planner's chosen move and the change actually landing. Part 4 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>How a rejected update gets fixed</title>
    <link href="https://www.allanninal.dev/build/how-a-rejected-update-gets-fixed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-rejected-update-gets-fixed/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">Three actions on a flag: retry (resend after a fix), edit (correct the value and resend), and skip (accept the channel can't take it, but keep it on the out-of-step report). Every action is logged. Part 5 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>What the menu sync costs</title>
    <link href="https://www.allanninal.dev/build/what-the-menu-sync-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-menu-sync-costs/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">A few dollars a month at single-restaurant volume. The sync only does work on a change, calls no models in the planner, and only fires Bedrock on the supplier-price lane and the monthly summary. Part 6 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the menu sync architecture</title>
    <link href="https://www.allanninal.dev/build/menu-sync-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/menu-sync-engineering-reference/</id>
    <published>2026-06-15T09:00:00Z</published>
    <updated>2026-06-15T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge config, the channel-adapter contract, and the DynamoDB schemas. Part 7 of 7 in the Menu sync series.</summary>
  </entry>
  <entry>
    <title>A tax doc collector on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/tax-doc-collector-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/tax-doc-collector-on-aws/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a document intake, a tracker, and a dispatch piece, plus the four moves they share for every client file. Part 1 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>How a client file gets set up</title>
    <link href="https://www.allanninal.dev/build/how-a-client-file-gets-set-up/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-client-file-gets-set-up/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">Three ways a file starts: the Drive checklist sheet itself, a one-tap copy from last year's file, and a short intake form that picks the right checklist for the client type. Part 2 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>How a document arrives and gets checked</title>
    <link href="https://www.allanninal.dev/build/how-a-document-arrives-and-gets-checked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-document-arrives-and-gets-checked/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">A client uploads on a secure page; the file lands in private storage; the collector reads it just enough to confirm the document type, matches it to a checklist item, and checks it off pending a human review. Part 3 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>How a client gets chased for missing docs</title>
    <link href="https://www.allanninal.dev/build/how-a-client-gets-chased-for-missing-docs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-client-gets-chased-for-missing-docs/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">A daily tick reads each file, sees what's still missing, compares against the chase cadence, and picks one of four moves: complete, first request, reminder, escalate. No model on the tick. Part 4 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>How a finished file reaches the preparer</title>
    <link href="https://www.allanninal.dev/build/how-a-finished-file-reaches-the-preparer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-finished-file-reaches-the-preparer/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">Three actions on a complete file: accept (the file moves to done), reject one item (the client gets a fresh request for just that item), and reopen (add a forgotten item back). Every action is logged. Part 5 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>What the tax doc collector costs</title>
    <link href="https://www.allanninal.dev/build/what-the-tax-doc-collector-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-tax-doc-collector-costs/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">A few dollars a month at small-practice volume. The chase tick calls no models; Textract and Bedrock fire only when a document is uploaded, plus the monthly summary. Part 6 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the tax doc collector architecture</title>
    <link href="https://www.allanninal.dev/build/tax-doc-collector-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/tax-doc-collector-engineering-reference/</id>
    <published>2026-06-14T09:00:00Z</published>
    <updated>2026-06-14T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Tax doc collector series.</summary>
  </entry>
  <entry>
    <title>A quote follow-up system on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/quote-followup-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/quote-followup-on-aws/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">The whole system on one page: a quote intake, a timer, and a sender, plus the way a reply stops everything cleanly and hands the deal to a person. Part 1 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>How a sent quote gets tracked</title>
    <link href="https://www.allanninal.dev/build/how-a-sent-quote-gets-tracked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-sent-quote-gets-tracked/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">Three lanes feed the open-quote list: the Drive sheet itself, an inbox lane that reads a sent-quote email into a proposed row for one-tap approval, and a webhook from your quoting tool. Part 2 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>How a follow-up gets timed</title>
    <link href="https://www.allanninal.dev/build/how-a-follow-up-gets-timed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-follow-up-gets-timed/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">A daily check reads the list, computes days since sent and days to expiry, compares against the cadence in the rules doc, and picks one of four moves: resting, first nudge, follow-up, last call. No model on the check. Part 3 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>How a quote nudge reaches the buyer</title>
    <link href="https://www.allanninal.dev/build/how-a-quote-nudge-reaches-the-buyer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-quote-nudge-reaches-the-buyer/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">Owner resolution per quote, a draft written in your voice for one-tap approval, quiet hours and weekends, email send, and the four guardrails between the chosen move and the message landing. Part 4 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>How a reply stops the follow-ups</title>
    <link href="https://www.allanninal.dev/build/how-a-reply-stops-the-follow-ups/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reply-stops-the-follow-ups/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">Three outcomes when the customer replies: stop-and-hand-off (accept, decline, or question go to the rep), defer (out-of-office pushes the next nudge), and the expiry close-out. Every step is logged. Part 5 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>What the quote follow-up costs</title>
    <link href="https://www.allanninal.dev/build/what-the-quote-followup-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-quote-followup-costs/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The check runs once a day and calls no model; Bedrock only fires to read a reply and draft a nudge. About $2.40/month at ~200 open quotes. Part 6 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the quote follow-up architecture</title>
    <link href="https://www.allanninal.dev/build/quote-followup-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/quote-followup-engineering-reference/</id>
    <published>2026-06-13T09:00:00Z</published>
    <updated>2026-06-13T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Quote follow-up series.</summary>
  </entry>
  <entry>
    <title>A social inbox unifier on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/social-inbox-unifier-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/social-inbox-unifier-on-aws/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">The whole system on one page — platform connectors, a labeler, and a shared inbox, plus the rule that a human writes every reply. Part 1 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>How a DM reaches the unified inbox</title>
    <link href="https://www.allanninal.dev/build/how-a-dm-reaches-the-unified-inbox/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-dm-reaches-the-unified-inbox/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">Each platform pushes new messages to a webhook; a small connector verifies the call, turns every format into one common shape, and drops it on a single work queue. Part 2 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>How a DM gets labeled and deduped</title>
    <link href="https://www.allanninal.dev/build/how-a-dm-gets-labeled-and-deduped/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-dm-gets-labeled-and-deduped/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">A fingerprint check drops re-delivered and cross-channel duplicates; one Bedrock call returns topic, urgency, and language. No model ever writes a reply. Part 3 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>How a DM finds the right teammate</title>
    <link href="https://www.allanninal.dev/build/how-a-dm-finds-the-right-teammate/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-dm-finds-the-right-teammate/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">Routing by topic, working hours, fair load-sharing, and a backup — the four guardrails between a labeled message and the right person's queue. Part 4 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>How a DM gets answered</title>
    <link href="https://www.allanninal.dev/build/how-a-dm-gets-answered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-dm-gets-answered/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">Three actions in the shared inbox: send a human-written reply, reassign, or close. Every reply goes back out on the customer's own platform, and every action is logged. Part 5 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>What the social inbox unifier costs</title>
    <link href="https://www.allanninal.dev/build/what-the-social-inbox-unifier-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-social-inbox-unifier-costs/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The hot path is plain code; the only model call is one cheap label per new message. Part 6 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the social inbox unifier architecture</title>
    <link href="https://www.allanninal.dev/build/social-inbox-unifier-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/social-inbox-unifier-engineering-reference/</id>
    <published>2026-06-12T09:00:00Z</published>
    <updated>2026-06-12T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the webhook setup, SQS config, and DynamoDB schemas. Part 7 of 7 in the Social inbox unifier series.</summary>
  </entry>
  <entry>
    <title>A renewal negotiator on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/renewal-negotiator-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/renewal-negotiator-on-aws/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an account intake, a drafter, and an approval queue, plus the rule that the system only ever prepares an offer and never sends one itself. Part 1 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>How a renewal gets prepared</title>
    <link href="https://www.allanninal.dev/build/how-a-renewal-gets-prepared/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-renewal-gets-prepared/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">Three lanes feed the account registry — the Drive sheet itself, an inbox-forwarding lane that parses contracts and usage exports into proposed rows, and an hourly calendar import for renewal dates tagged #renews. Part 2 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>How a renewal offer gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-renewal-offer-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-renewal-offer-gets-drafted/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">Plain Python picks the plan and the discount band from your rules; Bedrock then writes the offer in your voice. The model never sets a price or a discount — it only phrases what the rules already decided. Part 3 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>How a renewal offer reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-a-renewal-offer-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-renewal-offer-reaches-the-owner/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">Owner resolution per account, a discount-cap check that nothing skips, quiet hours, and the four guardrails between a freshly drafted offer and the moment it lands in your approval queue. Part 4 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>How a renewal gets sent or skipped</title>
    <link href="https://www.allanninal.dev/build/how-a-renewal-gets-sent-or-skipped/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-renewal-gets-sent-or-skipped/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">Three actions on every draft: approve &amp; send (the offer goes to the customer), edit (change wording, plan, or discount within the cap, then send), and skip (no offer this cycle). Every action is logged. Part 5 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>What the renewal negotiator costs</title>
    <link href="https://www.allanninal.dev/build/what-the-renewal-negotiator-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-renewal-negotiator-costs/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The daily check reads accounts with no model; Bedrock fires only when a renewal comes due and a draft is needed, plus the monthly summary. Part 6 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the renewal negotiator architecture</title>
    <link href="https://www.allanninal.dev/build/renewal-negotiator-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/renewal-negotiator-engineering-reference/</id>
    <published>2026-06-11T09:00:00Z</published>
    <updated>2026-06-11T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Renewal negotiator series.</summary>
  </entry>
  <entry>
    <title>A feedback collector on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/feedback-collector-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/feedback-collector-on-aws/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a request builder, a router, and a dispatch piece, plus the simple happy-or-unhappy split that decides where every reply goes. Part 1 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>How a feedback request goes out</title>
    <link href="https://www.allanninal.dev/build/how-a-feedback-request-goes-out/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-feedback-request-goes-out/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">Three lanes feed the customer list — the Drive sheet, a point-of-sale webhook, and an hourly booking import — then a timing rule picks the right moment to send one simple ask. Part 2 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>How a feedback reply comes back</title>
    <link href="https://www.allanninal.dev/build/how-a-feedback-reply-comes-back/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-feedback-reply-comes-back/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">A one-tap star link hits a Function URL, or a short written reply comes back by email. The reply is recorded exactly once, the customer is marked answered, and the same ask is never sent twice. Part 3 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>How feedback gets routed by mood</title>
    <link href="https://www.allanninal.dev/build/how-feedback-gets-routed-by-mood/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-feedback-gets-routed-by-mood/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">A star tap is read by plain Python; a few words of free text get a quick Bedrock mood read. Every reply lands as happy, unhappy, or unclear — and that decides the next move. Part 4 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>How a review ask or a private fix happens</title>
    <link href="https://www.allanninal.dev/build/how-a-review-ask-or-private-fix-happens/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-review-ask-or-private-fix-happens/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">Happy gets a gentle, one-tap nudge to a public review link. Unhappy goes straight to the owner privately with the customer's words and a way to reach back. Unclear waits for a human. Every move is logged. Part 5 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>What the feedback collector costs</title>
    <link href="https://www.allanninal.dev/build/what-the-feedback-collector-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-feedback-collector-costs/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The system sends a handful of asks, reads star taps with no model, and only fires Bedrock on free-text replies and the monthly summary. Part 6 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the feedback collector architecture</title>
    <link href="https://www.allanninal.dev/build/feedback-collector-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/feedback-collector-engineering-reference/</id>
    <published>2026-06-10T09:00:00Z</published>
    <updated>2026-06-10T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — region, service names, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Feedback collector series.</summary>
  </entry>
  <entry>
    <title>A form intake router on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/form-intake-router-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/form-intake-router-on-aws/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an intake door that saves and acknowledges first, a checker, and a dispatch piece, plus the shared design rules that mean no lead is ever lost. Part 1 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>How a form submission gets captured</title>
    <link href="https://www.allanninal.dev/build/how-a-form-submission-gets-captured/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-form-submission-gets-captured/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">A tiny snippet posts each form to a Function URL; the door saves the raw payload and writes a record, then acknowledges the customer before any slow downstream work. A submission key keeps a double-click from becoming two leads. Part 2 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>How a form submission gets checked</title>
    <link href="https://www.allanninal.dev/build/how-a-form-submission-gets-checked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-form-submission-gets-checked/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">Required-field checks and a layered spam filter that holds flagged leads for review instead of deleting them. Plain Python on the hot path; one cheap Bedrock second-opinion only on borderline spam and unknown categories. Part 3 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>How a form submission finds the right tool</title>
    <link href="https://www.allanninal.dev/build/how-a-form-submission-finds-the-right-tool/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-form-submission-finds-the-right-tool/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">A routing table in a Drive sheet maps each form to a team email, a CRM list or sheet tab, and an auto-reply. Each delivery runs as its own job on an SQS queue with retries, so a busy tool never costs a lead or holds up the others. Part 4 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>How a form submission gets confirmed</title>
    <link href="https://www.allanninal.dev/build/how-a-form-submission-gets-confirmed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-form-submission-gets-confirmed/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">The customer we-got-it reply on its own queued job, the retry budget and dead-letter queue that hold and replay anything a tool refuses, and the de-duplicate guard that stops double sends. Every finish is logged and replayable. Part 5 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>What the form intake router costs</title>
    <link href="https://www.allanninal.dev/build/what-the-form-intake-router-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-form-intake-router-costs/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">About $2/month at SMB volume. The hot path is plain Python and a couple of emails; Bedrock fires only on the few submissions that need a category guess or a spam second-opinion. No API Gateway, no NAT Gateway, no always-on compute. Part 6 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the form intake router architecture</title>
    <link href="https://www.allanninal.dev/build/form-intake-router-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/form-intake-router-engineering-reference/</id>
    <published>2026-06-09T09:00:00Z</published>
    <updated>2026-06-09T09:00:00Z</updated>
    <summary type="text">Same system for engineers — region, service names, Bedrock model IDs, the Lambda inventory, IAM scopes, the Function URL and SES ingress, the SQS and dead-letter queue config, the idempotency design, and the DynamoDB schemas. Part 7 of 7 in the Form intake router series.</summary>
  </entry>
  <entry>
    <title>A backup sentinel on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/backup-sentinel-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/backup-sentinel-on-aws/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a job intake, a checker, and a dispatch piece, plus the four states they share for every backup job. Part 1 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>How a backup job gets registered</title>
    <link href="https://www.allanninal.dev/build/how-a-backup-job-gets-registered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-backup-job-gets-registered/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">Three lanes feed the job list: the Drive sheet itself, an inbox-forwarding lane that reads backup reports into proposed rows for one-tap approval, and a heartbeat lane where a job pings a private URL when it finishes. Part 2 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>How a backup gets checked</title>
    <link href="https://www.allanninal.dev/build/how-a-backup-gets-checked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-backup-gets-checked/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">A scheduled check reads each job's latest evidence, runs three plain tests — did it finish, is it recent, is it the right size — and picks one of four states: all green, warn, alert, escalate. No model on the check. Part 3 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>How a backup alert reaches its owner</title>
    <link href="https://www.allanninal.dev/build/how-a-backup-alert-reaches-its-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-backup-alert-reaches-its-owner/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">Owner resolution per job, quiet hours, holiday calendars, Slack DMs with full context, email fallback, and the four guardrails between the checker's chosen state and the actual ping landing. Part 4 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>How a backup status gets cleared</title>
    <link href="https://www.allanninal.dev/build/how-a-backup-status-gets-cleared/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-backup-status-gets-cleared/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">Three actions on the alert: mark fixed (the job ran clean again, resume checks), snooze (quiet it while you work), and mute (this job is meant to be paused). Mark fixed only sticks once the next check confirms a real healthy run. Every action is logged. Part 5 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>What the backup sentinel costs</title>
    <link href="https://www.allanninal.dev/build/what-the-backup-sentinel-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-backup-sentinel-costs/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">Pennies a month at SMB volume. The checker runs a few times a day, calls no model on the check, and only fires Bedrock for the plain-English daily summary. About $1.50/month at ~30 jobs. Part 6 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the backup sentinel architecture</title>
    <link href="https://www.allanninal.dev/build/backup-sentinel-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/backup-sentinel-engineering-reference/</id>
    <published>2026-06-08T09:00:00Z</published>
    <updated>2026-06-08T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Region, service names, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Backup sentinel series.</summary>
  </entry>
  <entry>
    <title>A transcription archive on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/transcription-archive-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/transcription-archive-on-aws/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an intake, a filing-and-indexing piece, and a search piece, plus the way every recording becomes a findable, quotable record. Part 1 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>How a recording gets filed</title>
    <link href="https://www.allanninal.dev/build/how-a-recording-gets-filed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-recording-gets-filed/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">Three lanes feed the archive — a watched Drive folder, a forward-to-an-address lane, and a scheduled pull from your meeting tool — then Transcribe turns speech to text and a plain-Python step files it by date, people, and topic. Part 2 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>How a transcript becomes searchable</title>
    <link href="https://www.allanninal.dev/build/how-a-transcript-becomes-searchable/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-transcript-becomes-searchable/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">Each transcript is split into short timed chunks, every chunk is turned into a vector by Titan Text Embeddings V2, and the vectors land in S3 Vectors so search can match by meaning, not just exact words. Part 3 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>How a question finds the moment</title>
    <link href="https://www.allanninal.dev/build/how-a-question-finds-the-moment/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-question-finds-the-moment/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">A plain-language question becomes a vector, matches the closest chunks, gets filtered by who's allowed to see them, and Haiku 4.5 writes a short answer with a direct quote linked to the exact second. Part 4 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>How the archive stays private</title>
    <link href="https://www.allanninal.dev/build/how-the-archive-stays-private/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-archive-stays-private/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">Access tags per recording, search results filtered before the answer is written, sensitive recordings kept out of open search, and a full search log of who asked what and when. Part 5 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>What the transcription archive costs</title>
    <link href="https://www.allanninal.dev/build/what-the-transcription-archive-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-transcription-archive-costs/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. Transcription runs once per recording and dominates the bill; embeddings, the answer model, and the vector store are small slivers, and there's no always-on compute. Part 6 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the transcription archive architecture</title>
    <link href="https://www.allanninal.dev/build/transcription-archive-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/transcription-archive-engineering-reference/</id>
    <published>2026-06-07T09:00:00Z</published>
    <updated>2026-06-07T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, region, the Transcribe job config, Bedrock model IDs, the S3 Vectors index, Lambda inventory, IAM scopes, and the DynamoDB schemas. Part 7 of 7 in the Transcription archive series.</summary>
  </entry>
  <entry>
    <title>A product photo tagger on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/photo-tagger-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/photo-tagger-on-aws/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a photo intake, a reader that drafts details, and a review piece, plus the safety rails that keep a wrong photo from ever reaching your store. Part 1 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>How a product photo gets read</title>
    <link href="https://www.allanninal.dev/build/how-a-product-photo-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-product-photo-gets-read/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">Two ways a photo gets in — a Drive folder and a direct S3 drop — then a resize-and-check step that shrinks the image and runs plain quality checks before any model looks at it. Part 2 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>How photo details get drafted</title>
    <link href="https://www.allanninal.dev/build/how-photo-details-get-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-photo-details-get-drafted/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">One Bedrock vision call reads the photo and drafts five fields — title, alt text, tags, category, description — each with a confidence score, against the rules and word list in your style doc. No invented details. Part 3 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>How a bad photo gets flagged</title>
    <link href="https://www.allanninal.dev/build/how-a-bad-photo-gets-flagged/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-bad-photo-gets-flagged/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">Two layers catch a wrong or low-quality image — a deterministic quality gate before any model, and a model self-check that spots screenshots, receipts, people, and empty boxes — so a human looks instead of a bad draft going out. Part 4 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>How a listing gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-listing-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-listing-gets-approved/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">Three actions on every draft card: approve (write the fields to your store or an export sheet), edit (fix a word, then approve), and reject (send to a flagged folder with a reason). Every action is logged. Part 5 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>What the photo tagger costs</title>
    <link href="https://www.allanninal.dev/build/what-the-photo-tagger-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-photo-tagger-costs/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The system runs only when a photo lands, and the only real cost is the one Bedrock vision call per photo that drafts the details. Part 6 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the photo tagger architecture</title>
    <link href="https://www.allanninal.dev/build/photo-tagger-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/photo-tagger-engineering-reference/</id>
    <published>2026-06-06T09:00:00Z</published>
    <updated>2026-06-06T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the S3 and SQS event wiring, and the DynamoDB schemas. Part 7 of 7 in the Photo tagger series.</summary>
  </entry>
  <entry>
    <title>A waitlist manager on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/waitlist-manager-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/waitlist-manager-on-aws/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a waitlist intake, an offer engine, and a sender, plus the four moves they share for every freed slot. Part 1 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>How a waitlist entry gets added</title>
    <link href="https://www.allanninal.dev/build/how-a-waitlist-entry-gets-added/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-waitlist-entry-gets-added/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">Three lanes feed the list: the Drive sheet itself, an inbox-forwarding lane that parses booking requests into proposed rows for approval, and a web form that drops new entries straight in. Part 2 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>How a freed slot gets noticed</title>
    <link href="https://www.allanninal.dev/build/how-a-freed-slot-gets-noticed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-freed-slot-gets-noticed/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">A cancellation, a no-show, or a new opening comes in; the engine finds who fits, sorts them in fair order, and picks one of four moves: nobody fits, make an offer, roll on, or hand back to staff. No model in the loop. Part 3 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>How an offer reaches the next person</title>
    <link href="https://www.allanninal.dev/build/how-an-offer-reaches-the-next-person/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-offer-reaches-the-next-person/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">Picking the next eligible entry, quiet hours, a text or email with a one-tap claim link and a countdown, and the four guardrails between the chosen move and the offer landing. Part 4 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>How a slot gets claimed or rolls on</title>
    <link href="https://www.allanninal.dev/build/how-a-slot-gets-claimed-or-rolls-on/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-slot-gets-claimed-or-rolls-on/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">One conditional write decides it: the first valid claim books the slot, any later click is told it's taken. If the window ends unclaimed, the offer rolls to the next person. Claim, decline, and time-out are all logged. Part 5 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>What the waitlist manager costs</title>
    <link href="https://www.allanninal.dev/build/what-the-waitlist-manager-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-waitlist-manager-costs/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The engine wakes only when a slot frees up, calls no models on the offer path, and only fires Bedrock on the inbound parsing lane and the monthly summary. Part 6 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the waitlist manager architecture</title>
    <link href="https://www.allanninal.dev/build/waitlist-manager-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/waitlist-manager-engineering-reference/</id>
    <published>2026-06-05T09:00:00Z</published>
    <updated>2026-06-05T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, the conditional-write claim, and the DynamoDB schemas. Part 7 of 7 in the Waitlist manager series.</summary>
  </entry>
  <entry>
    <title>A loyalty tracker on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/loyalty-tracker-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/loyalty-tracker-on-aws/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a sign-up piece, an earn-and-balance piece, and a redeem piece, plus the four moves they share for every sale. Part 1 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>How a shopper joins the loyalty program</title>
    <link href="https://www.allanninal.dev/build/how-a-shopper-joins-the-loyalty-program/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-shopper-joins-the-loyalty-program/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">Three lanes create a member: a sign-up form the shopper fills in, a quick add by staff at the counter, and an auto-enroll when a sale comes in for an unknown phone number. Part 2 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>How a purchase earns points</title>
    <link href="https://www.allanninal.dev/build/how-a-purchase-earns-points/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-purchase-earns-points/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">A sale arrives, the earn function reads the rules doc, works out the points, and picks one of four moves: skip, add points, add points and announce a reward, or hold for review. No model on the earn path. Part 3 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>How a reward gets redeemed</title>
    <link href="https://www.allanninal.dev/build/how-a-reward-gets-redeemed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reward-gets-redeemed/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">Look up the customer, show the reward and what it costs, and the four guardrails between a staff tap and the points coming off: balance check, confirm, one-at-a-time lock, and a logged, reversible ledger row. Part 4 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>How a lapsing regular gets nudged</title>
    <link href="https://www.allanninal.dev/build/how-a-lapsing-regular-gets-nudged/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-lapsing-regular-gets-nudged/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">Three actions on a regular who's slipping away: nudge (a gentle come-back email), bonus (a one-time points top-up to win them back), and rest (mark them inactive, stop the emails). Every action is logged. Part 5 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>What the loyalty tracker costs</title>
    <link href="https://www.allanninal.dev/build/what-the-loyalty-tracker-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-loyalty-tracker-costs/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">A few dollars a month at small-shop volume. The earn math and the redeem check call no models, and Bedrock only fires once a month for the owner summary. Part 6 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the loyalty tracker architecture</title>
    <link href="https://www.allanninal.dev/build/loyalty-tracker-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/loyalty-tracker-engineering-reference/</id>
    <published>2026-06-04T09:00:00Z</published>
    <updated>2026-06-04T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES outbound config, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Loyalty tracker series.</summary>
  </entry>
  <entry>
    <title>An event RSVP manager on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/event-rsvp-manager-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/event-rsvp-manager-on-aws/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a register form, a guest-list keeper, and a messenger — plus the moves they share for every guest: confirm, remind, cancel, and offer. Part 1 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>How an RSVP gets confirmed</title>
    <link href="https://www.allanninal.dev/build/how-an-rsvp-gets-confirmed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-rsvp-gets-confirmed/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">Three lanes get a guest onto the list — the web form, an email reply, and a host import — and a single conditional write claims a seat only if the event isn't full, so two people can never win the same last seat. Part 2 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>How an RSVP reminder gets scheduled</title>
    <link href="https://www.allanninal.dev/build/how-an-rsvp-reminder-gets-scheduled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-rsvp-reminder-gets-scheduled/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">When a guest confirms, the system books their reminders — a week out, a day out, the morning of — as one-off Scheduler alarms. Quiet hours defer any reminder that would land overnight. Part 3 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>How a cancellation frees a spot</title>
    <link href="https://www.allanninal.dev/build/how-a-cancellation-frees-a-spot/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-cancellation-frees-a-spot/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">One signed-link click cancels cleanly: the seat is released, the guest's pending reminders are deleted, and the freed seat kicks off the waitlist offer. Every step is logged so the headcount always adds up. Part 4 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>How a freed spot finds the next guest</title>
    <link href="https://www.allanninal.dev/build/how-a-freed-spot-finds-the-next-guest/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-freed-spot-finds-the-next-guest/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">The freed seat is offered, not given, to the first person on the waitlist. A timed claim link holds it; if it lapses, the offer rolls to the next person automatically. Nobody is silently skipped. Part 5 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>What the event RSVP manager costs</title>
    <link href="https://www.allanninal.dev/build/what-the-event-rsvp-manager-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-event-rsvp-manager-costs/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">A few dollars per event — about $2.40 at 200 guests. Most of the bill is the emails it sends; the rest is pennies. Bedrock fires only on the post-event thank-you note and a small host Q&amp;A helper. Part 6 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the event RSVP manager architecture</title>
    <link href="https://www.allanninal.dev/build/event-rsvp-manager-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/event-rsvp-manager-engineering-reference/</id>
    <published>2026-06-03T09:00:00Z</published>
    <updated>2026-06-03T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the conditional-write capacity logic, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Event RSVP manager series.</summary>
  </entry>
  <entry>
    <title>A proposal generator on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/proposal-generator-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/proposal-generator-on-aws/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a brief intake, a generator, and a delivery piece, plus the five sections they produce for every proposal. Part 1 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>How a proposal brief gets captured</title>
    <link href="https://www.allanninal.dev/build/how-a-proposal-brief-gets-captured/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-proposal-brief-gets-captured/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">Three lanes feed one clean brief — a short web form, a forwarded email thread read into a brief, and a one-line Slack command — with a one-tap card to start. Part 2 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>How a proposal gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-proposal-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-proposal-gets-drafted/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">The build job reads the brief, retrieves your closest past proposals, computes the price in plain Python, then writes all five sections in one grounded Sonnet call. Part 3 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>How a proposal stays on brand</title>
    <link href="https://www.allanninal.dev/build/how-a-proposal-stays-on-brand/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-proposal-stays-on-brand/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">Four gates between a fresh draft and your sign-off: a voice check, a banned-claim scan, a price-and-date guard, and a final assemble-and-preview step. Part 4 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>How a proposal gets sent</title>
    <link href="https://www.allanninal.dev/build/how-a-proposal-gets-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-proposal-gets-sent/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">Three actions on Approve: approve (render a branded PDF and email it), edit (revise in the linked doc, re-approve), and discard (archive the brief). Every action is logged. Part 5 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>What the proposal generator costs</title>
    <link href="https://www.allanninal.dev/build/what-the-proposal-generator-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-proposal-generator-costs/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. One Claude Sonnet draft call per proposal dominates the bill; storage, retrieval, and the build steps are slivers. Part 6 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the proposal generator architecture</title>
    <link href="https://www.allanninal.dev/build/proposal-generator-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/proposal-generator-engineering-reference/</id>
    <published>2026-06-02T09:00:00Z</published>
    <updated>2026-06-02T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the S3 Vectors setup, EventBridge config, and DynamoDB schemas. Part 7 of 7 in the Proposal generator series.</summary>
  </entry>
  <entry>
    <title>A sentiment monitor on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/sentiment-monitor-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/sentiment-monitor-on-aws/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">The whole system on one page: a collector, a reader, and a reporter, plus the one firm rule they share. It listens and reports, and never posts a reply. Part 1 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>How a mention gets collected</title>
    <link href="https://www.allanninal.dev/build/how-a-mention-gets-collected/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-mention-gets-collected/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">Three sources feed one queue: review-site feeds, a social-listening export, and your own comment webhooks. Each new mention is de-duplicated so the same review is never read twice. Part 2 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>How the mood of a mention gets read</title>
    <link href="https://www.allanninal.dev/build/how-the-mood-of-a-mention-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-mood-of-a-mention-gets-read/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">One Bedrock Haiku 4.5 call per mention returns a mood score and a one-line reason. The score is stored; the rolling trend on top of it is plain Python. Part 3 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>How an angry mention reaches a human</title>
    <link href="https://www.allanninal.dev/build/how-an-angry-mention-reaches-a-human/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-angry-mention-reaches-a-human/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">Two deterministic triggers, a single very angry mention or a sharp drop in the average, pass through four guardrails before an instant alert lands, worst item first. Part 4 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>How the weekly pulse gets sent</title>
    <link href="https://www.allanninal.dev/build/how-the-weekly-pulse-gets-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-weekly-pulse-gets-sent/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">A short Monday email built from three blocks: the mood trend, the standout mentions, and what changed since last week. One model call writes the summary line. Part 5 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>What the sentiment monitor costs</title>
    <link href="https://www.allanninal.dev/build/what-the-sentiment-monitor-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-sentiment-monitor-costs/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The cost is one cheap mood read per mention; the collector, the trend math, the alerts, and the weekly pulse are pennies. Part 6 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the sentiment monitor architecture</title>
    <link href="https://www.allanninal.dev/build/sentiment-monitor-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/sentiment-monitor-engineering-reference/</id>
    <published>2026-06-01T09:00:00Z</published>
    <updated>2026-06-01T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: region, Bedrock model IDs, Lambda inventory, IAM scopes, the SQS and DLQ config, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Sentiment monitor series.</summary>
  </entry>
  <entry>
    <title>A translation relay on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/translation-relay-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/translation-relay-on-aws/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">The whole system on one page — two ways in, three pieces inside AWS (detect, translate-in, translate-back), and the rule that a human always writes the reply. Part 1 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>How a message gets its language detected</title>
    <link href="https://www.allanninal.dev/build/how-a-message-gets-its-language-detected/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-message-gets-its-language-detected/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">A message arrives by email or web widget, gets cleaned of signatures and quoted history, and the relay reads its language with a cheap first pass and a careful fallback for short or mixed notes. Part 2 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>How a message gets translated for staff</title>
    <link href="https://www.allanninal.dev/build/how-a-message-gets-translated-for-staff/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-message-gets-translated-for-staff/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">The relay masks protected terms, translates the message into the team's language with the cheap model, re-runs only the shaky passages on the stronger model, and shows staff the original and translation side by side. Part 3 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>How a reply gets translated back</title>
    <link href="https://www.allanninal.dev/build/how-a-reply-gets-translated-back/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reply-gets-translated-back/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">The staff member writes the reply in their own language; the relay translates it back, shows a round-trip check of what it will send, and waits for a person to press send. Part 4 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>How names and prices stay exact</title>
    <link href="https://www.allanninal.dev/build/how-names-and-prices-stay-exact/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-names-and-prices-stay-exact/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">Brand names, product names, account IDs, numbers, and prices are locked behind placeholders before translation and restored after, so the machine can never quietly change a figure or rename your product. Part 5 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>What the translation relay costs</title>
    <link href="https://www.allanninal.dev/build/what-the-translation-relay-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-translation-relay-costs/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. Most of the bill is the Bedrock translate calls; the cheap model carries almost all of it and the stronger model only runs on the tricky passages. Part 6 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the translation relay architecture</title>
    <link href="https://www.allanninal.dev/build/translation-relay-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/translation-relay-engineering-reference/</id>
    <published>2026-05-31T09:00:00Z</published>
    <updated>2026-05-31T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, the SQS lanes, and the DynamoDB schemas. Part 7 of 7 in the Translation relay series.</summary>
  </entry>
  <entry>
    <title>A ticket router on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/ticket-router-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/ticket-router-on-aws/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a ticket intake, a router, and a hand-off piece, plus the four moves they share for every new ticket. Part 1 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>How a ticket gets read</title>
    <link href="https://www.allanninal.dev/build/how-a-ticket-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-ticket-gets-read/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">Three lanes feed one queue — an email inbox, a web form, and a chat lane — then one small Bedrock Haiku 4.5 call reads the topic, the urgency, and the tone. Part 2 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>How a ticket gets routed</title>
    <link href="https://www.allanninal.dev/build/how-a-ticket-gets-routed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-ticket-gets-routed/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">The router takes the topic, urgency, and tone, checks the rules and the VIP list, and picks one of four moves: route, priority, escalate, or hold. Plain Python, no model on the decision. Part 3 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>How a ticket reaches the right team</title>
    <link href="https://www.allanninal.dev/build/how-a-ticket-reaches-the-right-team/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-ticket-reaches-the-right-team/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">Team resolution per topic, a priority lane that jumps the queue, a duplicate check, a Slack ping with full context, an email fallback, and the four guardrails on every hand-off. Part 4 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>How a misroute gets corrected</title>
    <link href="https://www.allanninal.dev/build/how-a-misroute-gets-corrected/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-misroute-gets-corrected/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">Three actions on a routed ticket: reassign (move it and teach the router), bump priority (raise or drop the urgency), and split (one ticket, two topics). Every action is logged and feeds the examples. Part 5 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>What the ticket router costs</title>
    <link href="https://www.allanninal.dev/build/what-the-ticket-router-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-ticket-router-costs/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The only real cost is one small model call per ticket to read it; everything else is pennies. No always-on compute, no API Gateway. Part 6 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the ticket router architecture</title>
    <link href="https://www.allanninal.dev/build/ticket-router-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/ticket-router-engineering-reference/</id>
    <published>2026-05-30T09:00:00Z</published>
    <updated>2026-05-30T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, the SQS queue config, and the DynamoDB schemas. Part 7 of 7 in the Ticket router series.</summary>
  </entry>
  <entry>
    <title>A vendor onboarder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/vendor-onboarder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/vendor-onboarder-on-aws/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an invite piece, a document checker, and a chase-and-approve piece, plus the four states every vendor file moves through. Part 1 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>How a vendor gets invited</title>
    <link href="https://www.allanninal.dev/build/how-a-vendor-gets-invited/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-vendor-gets-invited/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">An owner starts a vendor in three ways — a quick form, a forwarded email from the supplier, or a Drive sheet row — and each one creates a vendor folder and sends a private upload link. Part 2 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>How a vendor document gets checked</title>
    <link href="https://www.allanninal.dev/build/how-a-vendor-document-gets-checked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-vendor-document-gets-checked/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">An uploaded file is read once with Textract and Haiku 4.5, then plain Python decides which required item it is, whether it is present, and whether it is still in date — with a human confirming before it counts. Part 3 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>How a vendor gets chased for missing items</title>
    <link href="https://www.allanninal.dev/build/how-a-vendor-gets-chased-for-missing-items/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-vendor-gets-chased-for-missing-items/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">A daily tick looks at what each vendor still owes, picks one of four moves, and sends a reminder that lists only the outstanding items — nudge, follow-up, escalate, or done. Part 4 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>How a vendor file gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-vendor-file-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-vendor-file-gets-approved/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">When the file is complete, the owner gets one message with the whole file and three actions: approve (write to the system of record), request a fix, or reject. Nothing is added without a human tap. Part 5 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>What the vendor onboarder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-vendor-onboarder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-vendor-onboarder-costs/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The chase tick calls no model; Textract and Bedrock fire only when a vendor actually uploads a document. Part 6 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the vendor onboarder architecture</title>
    <link href="https://www.allanninal.dev/build/vendor-onboarder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/vendor-onboarder-engineering-reference/</id>
    <published>2026-05-29T09:00:00Z</published>
    <updated>2026-05-29T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Vendor onboarder series.</summary>
  </entry>
  <entry>
    <title>An expense approver on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/expense-approver-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/expense-approver-on-aws/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a claim intake, a policy checker, and a routing piece, plus the one rule they all share: a human approves every payment. Part 1 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>How an expense claim gets submitted</title>
    <link href="https://www.allanninal.dev/build/how-an-expense-claim-gets-submitted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-expense-claim-gets-submitted/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">Three ways a claim gets in — a short web form, a forwarded receipt email, and a chat upload — all landing as one claim record with the receipt read by Textract into amount, date, and vendor. Part 2 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>How an expense claim gets checked against policy</title>
    <link href="https://www.allanninal.dev/build/how-an-expense-claim-gets-checked-against-policy/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-expense-claim-gets-checked-against-policy/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">A checker reads the claim, sorts it into a category, compares the amount against the per-category limit in the policy doc, and picks one of four outcomes: clear, confirm, review, reject. No model on the limit check. Part 3 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>How an expense claim finds its approver</title>
    <link href="https://www.allanninal.dev/build/how-an-expense-claim-finds-its-approver/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-expense-claim-finds-its-approver/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">Approver resolution per claim, the reason line that travels with it, the one-tap confirm for in-policy claims, full review for the rest, and the four guardrails before any approval request lands. Part 4 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>How an expense claim gets paid</title>
    <link href="https://www.allanninal.dev/build/how-an-expense-claim-gets-paid/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-expense-claim-gets-paid/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">Three actions on the approval card: approve (write to the payable sheet), reject (with a reason back to the claimant), and ask (request a missing receipt or note). Every action is logged. The system never moves money itself. Part 5 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>What the expense approver costs</title>
    <link href="https://www.allanninal.dev/build/what-the-expense-approver-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-expense-approver-costs/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The policy check calls no model; Textract reads each receipt and a small Bedrock call sorts the category. Where the dollars go and how it scales. Part 6 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the expense approver architecture</title>
    <link href="https://www.allanninal.dev/build/expense-approver-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/expense-approver-engineering-reference/</id>
    <published>2026-05-28T09:00:00Z</published>
    <updated>2026-05-28T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the Textract flow, EventBridge config, and the DynamoDB schemas. Part 7 of 7 in the Expense approver series.</summary>
  </entry>
  <entry>
    <title>A staff policy answerer on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/staff-policy-answerer-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/staff-policy-answerer-on-aws/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an indexer, an answerer, and a reply piece, plus the three moves they share for every question: a grounded answer, 'ask HR', or a clean handoff. Part 1 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>How a staff question reaches the answerer</title>
    <link href="https://www.allanninal.dev/build/how-a-staff-question-reaches-the-answerer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-staff-question-reaches-the-answerer/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">Three lanes feed one answer flow — a Slack DM, an @-mention in a shared channel, and an email fallback — all landing on one Function URL and normalized into a single shape. Part 2 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>How a policy answer gets grounded</title>
    <link href="https://www.allanninal.dev/build/how-a-policy-answer-gets-grounded/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-policy-answer-gets-grounded/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">The question is searched against the handbook by meaning, the few best sections are pulled, and the model answers from only those sections — or says it can't. Retrieval over S3 Vectors with Titan embeddings. Part 3 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>How a policy answer stays honest</title>
    <link href="https://www.allanninal.dev/build/how-a-policy-answer-stays-honest/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-policy-answer-stays-honest/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">Four guardrails between the draft and the staff member: an off-limits topic check, a citation check, a hedge check, and a final compose with the exact section link and an honest 'ask HR' path. Part 4 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>How the handbook stays current</title>
    <link href="https://www.allanninal.dev/build/how-the-handbook-stays-current/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-handbook-stays-current/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">Three refresh paths: a regular sync that re-reads only changed docs, a one-tap manual rebuild for urgent edits, and a gap report that turns unanswered questions into a to-do list for the handbook. Part 5 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>What the staff policy answerer costs</title>
    <link href="https://www.allanninal.dev/build/what-the-staff-policy-answerer-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-staff-policy-answerer-costs/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. Cost tracks how many questions staff ask, not how big the handbook is. One small Bedrock call per question; the index is cheap to keep fresh. Part 6 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the staff policy answerer architecture</title>
    <link href="https://www.allanninal.dev/build/staff-policy-answerer-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/staff-policy-answerer-engineering-reference/</id>
    <published>2026-05-27T09:00:00Z</published>
    <updated>2026-05-27T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, the S3 Vectors index config, Lambda inventory, IAM scopes, the Slack app config, and the DynamoDB schemas. Part 7 of 7 in the Staff policy answerer series.</summary>
  </entry>
  <entry>
    <title>A churn predictor on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/churn-predictor-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/churn-predictor-on-aws/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a signals intake, a scorer, and a hand-off piece, plus the four outcomes they share for every customer. Part 1 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>How an at-risk customer gets watched</title>
    <link href="https://www.allanninal.dev/build/how-an-at-risk-customer-gets-watched/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-at-risk-customer-gets-watched/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">Three lanes feed the customer list: the Drive sheet itself, a daily order-feed import that refreshes last order and pace, and a support-inbox lane that reads each ticket's mood. Part 2 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>How a churn score gets calculated</title>
    <link href="https://www.allanninal.dev/build/how-a-churn-score-gets-calculated/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-churn-score-gets-calculated/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">A weekly run turns each signal into points using weights in the rules doc, adds them up, and picks one of four bands: steady, watch, at-risk, churning. Plain arithmetic, no black box. Part 3 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>How an at-risk list reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-an-at-risk-list-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-at-risk-list-reaches-the-owner/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">Owner lookup per customer, a cap of five names a week, a plain reason per name, Slack or email delivery, and the four guardrails between the score and the list landing. Part 4 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>How a win-back gets tracked</title>
    <link href="https://www.allanninal.dev/build/how-a-win-back-gets-tracked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-win-back-gets-tracked/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">Three actions per name: reached out (log and pause re-flagging), won back (mark the save and reset to steady), and lost (record the churn with a reason). Every action is logged. Part 5 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>What the churn predictor costs</title>
    <link href="https://www.allanninal.dev/build/what-the-churn-predictor-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-churn-predictor-costs/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The scorer runs once a week, calls no model on the hot path, and only fires Bedrock on the support-mood lane and the weekly reason write-up. Part 6 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the churn predictor architecture</title>
    <link href="https://www.allanninal.dev/build/churn-predictor-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/churn-predictor-engineering-reference/</id>
    <published>2026-05-26T09:00:00Z</published>
    <updated>2026-05-26T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Churn predictor series.</summary>
  </entry>
  <entry>
    <title>A compliance tracker on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/compliance-tracker-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/compliance-tracker-on-aws/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a task intake, a scheduler, and a reminder piece, plus the four moves they share for every recurring task. Part 1 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>How a compliance task gets set up</title>
    <link href="https://www.allanninal.dev/build/how-a-compliance-task-gets-set-up/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-compliance-task-gets-set-up/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">Three lanes feed the task list: the Drive sheet itself, a starter-pack lane that loads common controls for your industry, and an inbox-forwarding lane that turns a forwarded policy into a proposed task for one-tap approval. Part 2 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>How a control comes due</title>
    <link href="https://www.allanninal.dev/build/how-a-control-comes-due/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-control-comes-due/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">A daily tick computes the next due date per task from its repeat rule, compares against per-task reminder chains in the rules doc, and picks one of four moves: on-track, due now, overdue, escalate. No model on the tick. Part 3 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>How a compliance reminder reaches its owner</title>
    <link href="https://www.allanninal.dev/build/how-a-compliance-reminder-reaches-its-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-compliance-reminder-reaches-its-owner/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">Owner resolution per task, quiet hours, holiday calendars, Slack DMs with full context, email fallback, and the four guardrails between the chosen move and the reminder actually landing. Part 4 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>How evidence gets captured</title>
    <link href="https://www.allanninal.dev/build/how-evidence-gets-captured/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-evidence-gets-captured/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">Three actions on the Done button: done (stamp the date, compute the next due date), attach evidence (a note or file kept as proof), and snooze (delay without dismissing, capped per cycle). Every action is logged. Part 5 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>What the compliance tracker costs</title>
    <link href="https://www.allanninal.dev/build/what-the-compliance-tracker-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-compliance-tracker-costs/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">Pennies a month at SMB volume. The tracker runs once a day, calls no models on the tick, and only fires Bedrock on the evidence-reading lane and the monthly summary. Part 6 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the compliance tracker architecture</title>
    <link href="https://www.allanninal.dev/build/compliance-tracker-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/compliance-tracker-engineering-reference/</id>
    <published>2026-05-25T09:00:00Z</published>
    <updated>2026-05-25T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Compliance tracker series.</summary>
  </entry>
  <entry>
    <title>A content repurposer on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/content-repurposer-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/content-repurposer-on-aws/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a source intake, a drafter, and a review desk, plus the rule that keeps every short post traceable back to the original. Part 1 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>How a long post gets loaded</title>
    <link href="https://www.allanninal.dev/build/how-a-long-post-gets-loaded/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-long-post-gets-loaded/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">Three lanes feed the source store — a Drive folder you drop posts into, a forwarding lane for recording transcripts, and a paste-a-link lane that fetches a published post and cleans it to plain text. Part 2 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>How the strongest points get pulled</title>
    <link href="https://www.allanninal.dev/build/how-the-strongest-points-get-pulled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-strongest-points-get-pulled/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">The source is split into passages, each one indexed, and Bedrock picks the handful worth posting — every chosen point carries the exact passage it came from, so nothing is invented. Part 3 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>How a clip gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-clip-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-clip-gets-drafted/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">Each chosen point becomes platform-sized drafts in your voice — a thread, short posts, a quote-card caption — written from a voice doc and checked against the source before it reaches you. Part 4 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>How a repurposed draft gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-repurposed-draft-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-repurposed-draft-gets-approved/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">Three buttons on every draft: approve (send it on or queue it), edit (tweak it in a pre-filled box), and skip (drop it). Nothing posts on its own, and every choice is logged. Part 5 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>What the content repurposer costs</title>
    <link href="https://www.allanninal.dev/build/what-the-content-repurposer-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-content-repurposer-costs/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The system only runs when you give it a piece, and the cost is dominated by the Bedrock calls that pull points and draft each clip. Part 6 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the content repurposer architecture</title>
    <link href="https://www.allanninal.dev/build/content-repurposer-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/content-repurposer-engineering-reference/</id>
    <published>2026-05-24T09:00:00Z</published>
    <updated>2026-05-24T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, the S3 Vectors index, Lambda inventory, IAM scopes, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Content repurposer series.</summary>
  </entry>
  <entry>
    <title>A cart recovery system on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/cart-recovery-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/cart-recovery-on-aws/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a cart intake, a waiter, and a sending piece, plus the four moves they share for every abandoned cart. Part 1 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>How an abandoned cart gets spotted</title>
    <link href="https://www.allanninal.dev/build/how-an-abandoned-cart-gets-spotted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-abandoned-cart-gets-spotted/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">Three lanes feed one cart list — a storefront webhook that catches every add and checkout, a nightly Drive export a human can read, and a saved-link tag lane. Part 2 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>How a cart reminder gets timed</title>
    <link href="https://www.allanninal.dev/build/how-a-cart-reminder-gets-timed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-cart-reminder-gets-timed/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">A per-cart wake-up computes time-since-abandon, compares against per-size waits in the rules doc, and picks one of four moves: still shopping, first reminder, second reminder, give up. No model on the decision. Part 3 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>How a cart reminder reaches the shopper</title>
    <link href="https://www.allanninal.dev/build/how-a-cart-reminder-reaches-the-shopper/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-cart-reminder-reaches-the-shopper/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">Address resolution, quiet hours, a do-not-disturb check, a polished friendly reminder with the items and a return link, and the four guardrails between the chosen move and the actual email landing. Part 4 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>How a cart recovery stops on checkout</title>
    <link href="https://www.allanninal.dev/build/how-a-cart-recovery-stops-on-checkout/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-cart-recovery-stops-on-checkout/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">The chase stops the moment they buy. Plus three owner actions: suppress (stop one cart), unsubscribe (stop an email), and write off (close it out). Every action is logged. Part 5 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>What the cart recovery costs</title>
    <link href="https://www.allanninal.dev/build/what-the-cart-recovery-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-cart-recovery-costs/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The system wakes once per cart, calls no model on the decision, and only fires Bedrock to polish a line and on the monthly summary. Part 6 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the cart recovery architecture</title>
    <link href="https://www.allanninal.dev/build/cart-recovery-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/cart-recovery-engineering-reference/</id>
    <published>2026-05-23T09:00:00Z</published>
    <updated>2026-05-23T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SQS and DLQ wiring, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Cart recovery series.</summary>
  </entry>
  <entry>
    <title>A competitor price monitor on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/price-monitor-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/price-monitor-on-aws/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a page checker, a watcher, and a dispatch piece, plus the four moves they share for every watched page. Designed on AWS for about $2/month. Part 1 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>How a competitor page gets watched</title>
    <link href="https://www.allanninal.dev/build/how-a-competitor-page-gets-watched/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-competitor-page-gets-watched/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">Three lanes feed the watch list — the Drive sheet itself, a paste-a-link lane that reads a page once and proposes a row, and an hourly catalog import — and each new page gets a saved price rule for one-tap approval. Part 2 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>How a price move gets noticed</title>
    <link href="https://www.allanninal.dev/build/how-a-price-move-gets-noticed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-price-move-gets-noticed/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">A scheduled check fetches each page, reads the price with a saved rule, compares it to the last reading and the per-page threshold, and picks one of four moves: steady, first alert, repeat move, big swing. No model on the check. Part 3 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>How a price alert reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-a-price-alert-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-price-alert-reaches-the-owner/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">Owner resolution per page, quiet hours, a daily cap so a sale day doesn't bury you, Slack DMs with full context, email fallback, and the four guardrails between the watcher's chosen move and the alert landing. Part 4 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>How a price alert gets handled</title>
    <link href="https://www.allanninal.dev/build/how-a-price-alert-gets-handled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-price-alert-gets-handled/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">Three actions on every alert: note (log a decision, keep watching), mute (silence this page for a while), and stop (drop the page). The monitor never touches your prices. Every action is logged. Part 5 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>What the price monitor costs</title>
    <link href="https://www.allanninal.dev/build/what-the-price-monitor-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-price-monitor-costs/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">About $2.40/month at typical SMB volume (around 200 watched pages). The routine check calls no models; Bedrock fires only when a page layout changes and on the monthly summary. No API Gateway, no NAT Gateway, no browser farm. Part 6 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the price monitor architecture</title>
    <link href="https://www.allanninal.dev/build/price-monitor-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/price-monitor-engineering-reference/</id>
    <published>2026-05-22T09:00:00Z</published>
    <updated>2026-05-22T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Region, service names, Bedrock model IDs, Lambda inventory, IAM scopes, the polite-fetch policy, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Price monitor series.</summary>
  </entry>
  <entry>
    <title>An applicant screener on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/applicant-screener-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/applicant-screener-on-aws/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an intake, a reader, and a router — plus the rule that keeps it fair: it sorts and explains, but a human always decides. Part 1 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>How an application gets read</title>
    <link href="https://www.allanninal.dev/build/how-an-application-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-application-gets-read/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">Three lanes feed one queue — an apply inbox, a careers-form upload, and a job-board export. Each resume becomes plain text, and the personal fields are stripped before scoring. Part 2 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>How an applicant gets scored</title>
    <link href="https://www.allanninal.dev/build/how-an-applicant-gets-scored/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-applicant-gets-scored/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">The reader checks each must-have and quotes the line that meets it. Plain Python counts met must-haves against your pass marks and lands on yes, maybe, or no. The cut-off is yours. Part 3 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>How a strong match reaches the hiring manager</title>
    <link href="https://www.allanninal.dev/build/how-a-strong-match-reaches-the-hiring-manager/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-strong-match-reaches-the-hiring-manager/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">Strong matches get a short summary and land in the manager's review queue. Four guardrails sit between a score and a routed candidate: dedupe, the fairness check, the summary, and human review. Part 4 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>How a hiring decision gets made</title>
    <link href="https://www.allanninal.dev/build/how-a-hiring-decision-gets-made/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-hiring-decision-gets-made/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">Three buttons on every card: advance, hold, and pass with a reason. Every tap is the human's call and every tap is logged. Any screener label can be overturned; the screener never decides. Part 5 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>What the applicant screener costs</title>
    <link href="https://www.allanninal.dev/build/what-the-applicant-screener-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-applicant-screener-costs/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">A few dollars a month at small-business volume. One Bedrock read per application, Textract only on scanned PDFs, and no always-on parts. Where the dollars go and how the bill scales. Part 6 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the applicant screener architecture</title>
    <link href="https://www.allanninal.dev/build/applicant-screener-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/applicant-screener-engineering-reference/</id>
    <published>2026-05-21T09:00:00Z</published>
    <updated>2026-05-21T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, the DynamoDB schemas, and the fairness controls as concrete design decisions. Part 7 of 7 in the Applicant screener series.</summary>
  </entry>
  <entry>
    <title>A meeting notetaker on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/meeting-notetaker-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/meeting-notetaker-on-aws/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">The whole system on one page: a recording intake, a notes engine, and a recap step, plus the rule that every line of notes traces back to something actually said. Part 1 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>How a meeting recording comes in</title>
    <link href="https://www.allanninal.dev/build/how-a-meeting-recording-comes-in/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-meeting-recording-comes-in/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">Three ways a recording enters the pipeline: a Drive folder you drop files into, a direct upload link straight to S3, and an email-forwarding lane for the file your conferencing tool sends. Part 2 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>How the transcript becomes notes</title>
    <link href="https://www.allanninal.dev/build/how-the-transcript-becomes-notes/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-transcript-becomes-notes/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">Amazon Transcribe turns the audio into text with speaker labels; then two grounded Bedrock passes write a short summary and pull out the decisions and action items, each tied to a transcript line. Part 3 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>How the notes stay grounded</title>
    <link href="https://www.allanninal.dev/build/how-the-notes-stay-grounded/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-notes-stay-grounded/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">Four plain-code checks keep the notes honest: cite-or-drop, owner resolution, date sanity, and a needs-confirm queue for anything uncertain. Nothing invented reaches the recap. Part 4 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>How the recap reaches everyone</title>
    <link href="https://www.allanninal.dev/build/how-the-recap-reaches-everyone/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-recap-reaches-everyone/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">The organizer gets a draft to approve, fix, or trim; only then does the clean recap go to the whole room. Three actions on the draft: approve, edit, discard, each one logged. Part 5 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>What the meeting notetaker costs</title>
    <link href="https://www.allanninal.dev/build/what-the-meeting-notetaker-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-meeting-notetaker-costs/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. Transcription is the dominant cost, billed per minute of audio; Bedrock fires just twice per meeting. No always-on compute. Part 6 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the meeting notetaker architecture</title>
    <link href="https://www.allanninal.dev/build/meeting-notetaker-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/meeting-notetaker-engineering-reference/</id>
    <published>2026-05-20T09:00:00Z</published>
    <updated>2026-05-20T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the Amazon Transcribe job config, EventBridge wiring, and the DynamoDB schemas. Part 7 of 7 in the Meeting notetaker series.</summary>
  </entry>
  <entry>
    <title>A deadline reminder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/deadline-reminder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/deadline-reminder-on-aws/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a deadline intake, a checker, and a sender, plus the four moves they share for every recurring obligation. Part 1 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>How a deadline gets on the calendar</title>
    <link href="https://www.allanninal.dev/build/how-a-deadline-gets-on-the-calendar/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-deadline-gets-on-the-calendar/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">Three lanes feed the calendar — the Drive sheet itself, an inbox-forwarding lane that parses notices into proposed rows for one-tap approval, and an hourly calendar import for events tagged #deadline. Part 2 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>How a deadline comes due</title>
    <link href="https://www.allanninal.dev/build/how-a-deadline-comes-due/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-deadline-comes-due/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">A daily check reads the calendar, computes days-to-due per deadline, compares against per-type lead-time chains in the rules doc, and picks one of four moves: clear, first reminder, follow-up, escalate. No model on the check. Part 3 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>How a deadline reminder reaches its owner</title>
    <link href="https://www.allanninal.dev/build/how-a-deadline-reminder-reaches-its-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-deadline-reminder-reaches-its-owner/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">Owner resolution per deadline, quiet hours, holiday calendars, Slack DMs with full context, email fallback, and the four guardrails between the checker's chosen move and the reminder landing. Part 4 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>How a deadline gets marked done</title>
    <link href="https://www.allanninal.dev/build/how-a-deadline-gets-marked-done/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-deadline-gets-marked-done/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">Three actions on the Done button: done (roll the next due date forward, fresh chain), snooze (delay without dismissing, capped at three per cycle), and ack-only (silence this cycle without rolling the date). Every action is logged. Part 5 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>What the deadline reminder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-deadline-reminder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-deadline-reminder-costs/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">Pennies a month at SMB volume. The checker runs once a day, calls no models on the check, and only fires Bedrock on the inbox parsing lane and the monthly summary. Part 6 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the deadline reminder architecture</title>
    <link href="https://www.allanninal.dev/build/deadline-reminder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/deadline-reminder-engineering-reference/</id>
    <published>2026-05-19T09:00:00Z</published>
    <updated>2026-05-19T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Deadline reminder series.</summary>
  </entry>
  <entry>
    <title>A shipping notifier on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/shipping-notifier-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/shipping-notifier-on-aws/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an order intake, a notifier, and a sender, plus the five moves they share for every order as it moves through each shipping step. Part 1 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>How an order gets watched</title>
    <link href="https://www.allanninal.dev/build/how-an-order-gets-watched/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-order-gets-watched/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">Three lanes feed the order list — the Drive sheet itself, a carrier webhook that posts tracking updates in real time, and an inbox-forwarding lane that reads forwarded carrier emails into proposed status changes for one-tap approval. Part 2 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>How a shipping update gets sent</title>
    <link href="https://www.allanninal.dev/build/how-a-shipping-update-gets-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-shipping-update-gets-sent/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">A scheduled check reads the order list, compares each order's status to what was last sent, and picks one of five moves: nothing, shipped, out for delivery, delivered, or delayed. No model on the check. Part 3 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>How a message reaches the customer</title>
    <link href="https://www.allanninal.dev/build/how-a-message-reaches-the-customer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-message-reaches-the-customer/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">Contact resolution per order, quiet hours, unsubscribe checks, a friendly email with tracking, and the four guardrails between the notifier's chosen move and the actual update landing. Part 4 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>How a late delivery gets caught</title>
    <link href="https://www.allanninal.dev/build/how-a-late-delivery-gets-caught/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-late-delivery-gets-caught/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">Three things happen when an order passes its expected date: warn the customer, alert the owner, and log the delay. Each updates the order list and writes an audit row so nothing slips silently. Part 5 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>What the shipping notifier costs</title>
    <link href="https://www.allanninal.dev/build/what-the-shipping-notifier-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-shipping-notifier-costs/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume — about $2.40 at ~300 orders. The check runs on a schedule, calls no models, and only fires Bedrock on forwarded carrier emails and the monthly summary. Part 6 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the shipping notifier architecture</title>
    <link href="https://www.allanninal.dev/build/shipping-notifier-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/shipping-notifier-engineering-reference/</id>
    <published>2026-05-18T09:00:00Z</published>
    <updated>2026-05-18T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Region, service names, Bedrock model IDs, Lambda inventory, IAM scopes, the carrier webhook flow, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Shipping notifier series.</summary>
  </entry>
  <entry>
    <title>A refund handler on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/refund-handler-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/refund-handler-on-aws/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an intake, a checker, and a reply piece, plus the one rule they all share: a person approves every refund. Part 1 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>How a refund request arrives</title>
    <link href="https://www.allanninal.dev/build/how-a-refund-request-arrives/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-refund-request-arrives/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">Three lanes feed one queue — a help inbox that catches forwarded emails, a contact-form webhook, and a manual paste lane for requests that came in by phone or chat. Part 2 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>How a refund gets checked against policy</title>
    <link href="https://www.allanninal.dev/build/how-a-refund-gets-checked-against-policy/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-refund-gets-checked-against-policy/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">The checker pulls the exact policy lines that apply, decides only from them, cites the line it used, and picks one of four outcomes: in policy, out of policy, high-value, or not covered. Part 3 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>How a refund reply gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-refund-reply-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-refund-reply-gets-drafted/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">The drafter writes a kind, plain reply in your voice, quotes the policy line it used, and four guardrails sit between the draft and the person who approves it. Part 4 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>How a refund gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-refund-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-refund-gets-approved/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">Two actions on the approval card — approve and edit — plus a decline path with a reason. The money only moves after a human tap, and every action is logged. Part 5 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>What the refund handler costs</title>
    <link href="https://www.allanninal.dev/build/what-the-refund-handler-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-refund-handler-costs/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The handler runs only when a request comes in, uses a cheap model for most cases, and reserves the heavier model for the few hard ones. Part 6 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the refund handler architecture</title>
    <link href="https://www.allanninal.dev/build/refund-handler-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/refund-handler-engineering-reference/</id>
    <published>2026-05-17T09:00:00Z</published>
    <updated>2026-05-17T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — service names, the S3 Vectors policy index, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, and the DynamoDB schemas. Part 7 of 7 in the Refund handler series.</summary>
  </entry>
  <entry>
    <title>An onboarding guide on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/onboarding-guide-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/onboarding-guide-on-aws/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">The whole system on one page: a signup intake, a guide, and a sender, plus the four moves they share for every customer. Part 1 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>How a new customer gets enrolled</title>
    <link href="https://www.allanninal.dev/build/how-a-new-customer-gets-enrolled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-new-customer-gets-enrolled/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">Three lanes start a sequence: a signup webhook from your app, a Drive sheet you fill in by hand, and an inbox-forwarding lane that reads a welcome email into a proposed row for one-tap approval. Part 2 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>How an onboarding step comes due</title>
    <link href="https://www.allanninal.dev/build/how-an-onboarding-step-comes-due/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-onboarding-step-comes-due/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">A daily tick computes days-since-signup, checks which steps are done, compares against the per-plan step plan, and picks one of four moves: on track, next step due, gentle nudge, or flag to owner. No model on the tick. Part 3 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>How a nudge reaches a customer</title>
    <link href="https://www.allanninal.dev/build/how-a-nudge-reaches-a-customer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-nudge-reaches-a-customer/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">Channel resolution, quiet hours, weekends, an email with the next step and a one-click done link, and the four guardrails between the guide's chosen move and the message landing. Part 4 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>How an onboarding finishes</title>
    <link href="https://www.allanninal.dev/build/how-an-onboarding-finishes/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-onboarding-finishes/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">Three ways a sequence ends: finish (all steps done, send a wrap-up), pause (hold the sequence without dropping the customer), and hand off (a human takes over). Every action is logged. Part 5 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>What the onboarding guide costs</title>
    <link href="https://www.allanninal.dev/build/what-the-onboarding-guide-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-onboarding-guide-costs/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">Pennies a month at SMB volume. The guide runs once a day, calls no models on the tick, and only fires Bedrock on the occasional rewrite and the monthly summary. Part 6 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the onboarding guide architecture</title>
    <link href="https://www.allanninal.dev/build/onboarding-guide-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/onboarding-guide-engineering-reference/</id>
    <published>2026-05-16T09:00:00Z</published>
    <updated>2026-05-16T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers: service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Onboarding guide series.</summary>
  </entry>
  <entry>
    <title>A survey analyzer on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/survey-analyzer-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/survey-analyzer-on-aws/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an answer intake, a grouping piece, and a summary piece, plus the urgent lane that jumps the queue for anything that can't wait. Part 1 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>How a survey answer gets collected</title>
    <link href="https://www.allanninal.dev/build/how-a-survey-answer-gets-collected/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-survey-answer-gets-collected/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">Three lanes feed the answer store — a form-submit lane that posts each response the moment it's sent, an inbox-forwarding lane for email replies and exports, and a Drive sheet you paste exports into. Part 2 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>How answers get grouped into themes</title>
    <link href="https://www.allanninal.dev/build/how-answers-get-grouped-into-themes/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-answers-get-grouped-into-themes/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">A weekly pass turns each answer into a vector with Titan embeddings, clusters the vectors with plain code, then asks Haiku to name each cluster and pick one real quote. The math is deterministic; the model only labels. Part 3 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>How a summary reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-a-summary-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-summary-reaches-the-owner/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">Sonnet writes a short summary from the grouped themes — never from one cherry-picked answer — and four guardrails check every count and quote before the weekly email lands in the owner's inbox. Part 4 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>How an urgent answer gets flagged</title>
    <link href="https://www.allanninal.dev/build/how-an-urgent-answer-gets-flagged/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-urgent-answer-gets-flagged/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">Every answer runs a quick urgent check the moment it lands. An angry customer, a threat to leave, or a safety or legal concern jumps the weekly queue and goes straight to a human via SNS. Each flag is logged. Part 5 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>What the survey analyzer costs</title>
    <link href="https://www.allanninal.dev/build/what-the-survey-analyzer-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-survey-analyzer-costs/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The grouping pass runs once a week, the urgent check uses the cheap model on each answer, and the summary is one larger call — so the bill stays small. Part 6 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the survey analyzer architecture</title>
    <link href="https://www.allanninal.dev/build/survey-analyzer-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/survey-analyzer-engineering-reference/</id>
    <published>2026-05-15T09:00:00Z</published>
    <updated>2026-05-15T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, region, Bedrock model IDs, the S3 Vectors index, Lambda inventory, IAM scopes, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Survey analyzer series.</summary>
  </entry>
  <entry>
    <title>A newsletter composer on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/newsletter-composer-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/newsletter-composer-on-aws/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an item intake, a composer, and a sending piece, plus the four moves they share for every weekly run. Part 1 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>How a newsletter gathers the week's updates</title>
    <link href="https://www.allanninal.dev/build/how-a-newsletter-gathers-the-weeks-updates/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-newsletter-gathers-the-weeks-updates/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">Three lanes feed one item pool — the Drive sheet where anyone drops a win, a feed lane that watches your blog's RSS, and an inbox-forwarding lane that turns a forwarded update into a proposed item for one-tap approval. Part 2 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>How a newsletter issue gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-newsletter-issue-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-newsletter-issue-gets-drafted/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">A weekly run counts the fresh items, decides whether there's enough to send, then drafts the issue grounded only in those items and your voice doc. Five steps pick one of four moves: skip, draft, redraft, ready. Part 3 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>How a newsletter draft reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-a-newsletter-draft-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-newsletter-draft-reaches-the-owner/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">Reviewer resolution, quiet hours, the grounding check that flags anything unsourced, the full draft in context, and the four guardrails between the finished draft and the review message landing. Part 4 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>How a newsletter issue gets approved and sent</title>
    <link href="https://www.allanninal.dev/build/how-a-newsletter-issue-gets-approved-and-sent/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-newsletter-issue-gets-approved-and-sent/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">Three actions on the review message: approve (queue to send after a cooling-off window), edit (open the draft, change it, re-submit), and skip (don't send this week). Every action is logged. Part 5 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>What the newsletter composer costs</title>
    <link href="https://www.allanninal.dev/build/what-the-newsletter-composer-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-newsletter-composer-costs/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The composer runs once a week, drafts with one Bedrock call, and sends one email per subscriber through SES. Part 6 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the newsletter composer architecture</title>
    <link href="https://www.allanninal.dev/build/newsletter-composer-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/newsletter-composer-engineering-reference/</id>
    <published>2026-05-14T09:00:00Z</published>
    <updated>2026-05-14T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Newsletter composer series.</summary>
  </entry>
  <entry>
    <title>A social scheduler on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/social-scheduler-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/social-scheduler-on-aws/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a post intake, a scheduler, and a sender, plus the four moves they share for every post. Part 1 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>How a post gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-a-post-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-post-gets-drafted/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">Three lanes feed the queue: the Drive sheet itself, an optional draft-helper that roughs out text from a short note for one-tap approval, and a recurring-template lane that drops standing posts onto the calendar. Part 2 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>How the schedule fires on time</title>
    <link href="https://www.allanninal.dev/build/how-the-schedule-fires-on-time/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-schedule-fires-on-time/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">A daily tick reads the calendar, computes when each approved post is due, books a one-off send for the exact minute, and picks one of four moves: resting, queue, send, retry. No model on the tick. Part 3 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>How a post gets formatted per channel</title>
    <link href="https://www.allanninal.dev/build/how-a-post-gets-formatted-per-channel/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-post-gets-formatted-per-channel/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">Format checks per channel, image rules, link limits, token freshness, the per-channel send, and the four guardrails between a due post and the post actually landing. Part 4 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>How a post gets approved or held</title>
    <link href="https://www.allanninal.dev/build/how-a-post-gets-approved-or-held/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-post-gets-approved-or-held/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">Three actions on the Review button: approve (lock it in for its time), hold (pull it back to draft without losing it), and send now (push it out immediately). Every action is logged. Part 5 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>What the social scheduler costs</title>
    <link href="https://www.allanninal.dev/build/what-the-social-scheduler-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-social-scheduler-costs/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The scheduler runs once a day, calls no models on the tick, and only fires Bedrock on the optional draft-helper and the monthly recap. Part 6 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the social scheduler architecture</title>
    <link href="https://www.allanninal.dev/build/social-scheduler-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/social-scheduler-engineering-reference/</id>
    <published>2026-05-13T09:00:00Z</published>
    <updated>2026-05-13T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the channel-token setup, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Social scheduler series.</summary>
  </entry>
  <entry>
    <title>A testimonial collector on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/testimonial-collector-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/testimonial-collector-on-aws/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a moment finder, a tidier, and a publishing gate, plus the two permissions every testimonial must clear before it goes live. Part 1 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>How a testimonial request goes out</title>
    <link href="https://www.allanninal.dev/build/how-a-testimonial-request-goes-out/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-testimonial-request-goes-out/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">Three sources feed the list — a Drive sheet, a glowing-email forwarding lane, and a 5-star ratings webhook — then one warm ask goes out with a one-tap link, at most one gentle reminder, then it stops. Part 2 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>How a testimonial reply becomes a quote</title>
    <link href="https://www.allanninal.dev/build/how-a-testimonial-reply-becomes-a-quote/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-testimonial-reply-becomes-a-quote/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">The customer's reply comes in, Haiku 4.5 trims it into a short clean quote, a second pass checks nothing was invented, and the exact quote is shown back to the customer. Part 3 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>How a testimonial gets sign-off</title>
    <link href="https://www.allanninal.dev/build/how-a-testimonial-gets-sign-off/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-testimonial-gets-sign-off/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">Two gates, both required: the customer ticks a permission box and picks how to be credited, then the owner approves from a card with Approve, Edit, or Skip. No yes, no approval — no publish. Part 4 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>How a testimonial gets published</title>
    <link href="https://www.allanninal.dev/build/how-a-testimonial-gets-published/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-testimonial-gets-published/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">An approved testimonial is written to a clean JSON file in S3 that your site reads, credited exactly how the customer chose; a customer can update their credit or withdraw later and it changes on the next build. Part 5 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>What the testimonial collector costs</title>
    <link href="https://www.allanninal.dev/build/what-the-testimonial-collector-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-testimonial-collector-costs/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">Pennies a month at SMB volume. The collector scans once a day and only fires Bedrock once per reply to tidy it into a quote; about $2/month at typical volume. Part 6 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the testimonial collector architecture</title>
    <link href="https://www.allanninal.dev/build/testimonial-collector-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/testimonial-collector-engineering-reference/</id>
    <published>2026-05-12T09:00:00Z</published>
    <updated>2026-05-12T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Testimonial collector series.</summary>
  </entry>
  <entry>
    <title>A self-updating FAQ builder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/faq-builder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/faq-builder-on-aws/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a question intake, a grouper, and a drafter, plus the human review that gates every entry before it publishes. Part 1 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>How the question pile gets built</title>
    <link href="https://www.allanninal.dev/build/how-the-question-pile-gets-built/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-question-pile-gets-built/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">Three lanes feed the pile: an inbox-forwarding lane for support email, a chat-export lane that picks up transcripts, and a manual lane where a rep drops in a question worth answering once and for all. Part 2 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>How repeat questions get grouped</title>
    <link href="https://www.allanninal.dev/build/how-repeat-questions-get-grouped/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-repeat-questions-get-grouped/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">A daily pass embeds each new question, compares it against the question vectors, and groups the near-duplicates into clusters. Clusters that cross a repeat threshold become candidates. Plain Python over the vectors. Part 3 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>How the FAQ answer gets drafted</title>
    <link href="https://www.allanninal.dev/build/how-the-faq-answer-gets-drafted/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-faq-answer-gets-drafted/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">The drafter pulls the matching passages from your help docs, asks the model for a short answer grounded in those passages, and makes it cite a source. If nothing grounds the answer, it says so instead of guessing. Part 4 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>How a FAQ entry gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-faq-entry-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-faq-entry-gets-approved/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">Three actions on each proposal: approve (publish to the live FAQ doc), edit (fix the wording, then publish your version), and reject (drop it with a note). Every action is logged; nothing reaches the live FAQ without a human tap. Part 5 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>What the FAQ builder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-faq-builder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-faq-builder-costs/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The builder embeds each question, groups once a day with no model, and only drafts an answer for the handful of clusters that earn one. Part 6 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the FAQ builder architecture</title>
    <link href="https://www.allanninal.dev/build/faq-builder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/faq-builder-engineering-reference/</id>
    <published>2026-05-11T09:00:00Z</published>
    <updated>2026-05-11T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, S3 Vectors config, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the FAQ builder series.</summary>
  </entry>
  <entry>
    <title>A contract summarizer on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/contract-summarizer-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/contract-summarizer-on-aws/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an intake, a reader, and a delivery piece — plus the read, pull-terms, flag-risks flow every contract runs through, all grounded in the actual clause text. Part 1 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>How a contract gets read</title>
    <link href="https://www.allanninal.dev/build/how-a-contract-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-contract-gets-read/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">Three lanes feed one reader — a Drive folder you drop files into, an inbox-forwarding lane, and an e-sign webhook — then Textract reads the pages and a Python pass splits them into numbered clauses. Part 2 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>How the key terms get pulled</title>
    <link href="https://www.allanninal.dev/build/how-the-key-terms-get-pulled/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-key-terms-get-pulled/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">A single cheap Haiku 4.5 call fills a fixed shape — parties, deal, money, key dates, renewal and cancellation — and every field must cite the clause it came from, or it is left blank rather than guessed. Part 3 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>How risky clauses get flagged</title>
    <link href="https://www.allanninal.dev/build/how-risky-clauses-get-flagged/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-risky-clauses-get-flagged/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">Retrieval over the contract's own clauses finds the few worth a second look — auto-renewal, penalties, liability, exclusivity — and Sonnet 4.6 explains each in plain words, never as legal advice, flagging when to call a lawyer. Part 4 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>How a summary stays grounded</title>
    <link href="https://www.allanninal.dev/build/how-a-summary-stays-grounded/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-summary-stays-grounded/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">Every claim is checked against the real clause text before the one-page summary goes out, the not-legal-advice banner is attached, and a person approves it before it is filed and delivered as a Doc and an email. Part 5 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>What the contract summarizer costs</title>
    <link href="https://www.allanninal.dev/build/what-the-contract-summarizer-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-contract-summarizer-costs/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The cost is mostly reading each contract once — Textract plus a cheap Haiku pass — with Sonnet firing only on the handful of flagged clauses. Part 6 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the contract summarizer architecture</title>
    <link href="https://www.allanninal.dev/build/contract-summarizer-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/contract-summarizer-engineering-reference/</id>
    <published>2026-05-10T09:00:00Z</published>
    <updated>2026-05-10T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — region, service names, Bedrock model IDs, the S3 Vectors clause index, Lambda inventory, IAM scopes, the SES inbound rule set, and the DynamoDB schemas. Part 7 of 7 in the Contract summarizer series.</summary>
  </entry>
  <entry>
    <title>A receipt organizer on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/receipt-organizer-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/receipt-organizer-on-aws/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a capture piece, a reader, and a filing piece, plus the one path every receipt travels from photo to a clean row in the books. Part 1 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>How a receipt gets captured</title>
    <link href="https://www.allanninal.dev/build/how-a-receipt-gets-captured/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-receipt-gets-captured/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">Three lanes feed the inbox — an email-forward lane for emailed receipts, an upload lane where staff drop a photo, and a mobile-snap lane where a phone photo lands straight in S3 for reading. Part 2 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>How a receipt gets read</title>
    <link href="https://www.allanninal.dev/build/how-a-receipt-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-receipt-gets-read/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">Textract reads the vendor, date, total, and tax with a confidence score each; the reader checks the threshold, looks for duplicates, and picks one of four results: filed, needs-review, duplicate, or rejected. Part 3 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>How a receipt gets categorized</title>
    <link href="https://www.allanninal.dev/build/how-a-receipt-gets-categorized/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-receipt-gets-categorized/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">Four gates between the read fields and a filed expense: a vendor-hint lookup, a Bedrock Haiku 4.5 pick from your own chart of accounts, a sanity check, and the review gate that sends the unsure ones to a human. Part 4 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>How a receipt reaches the books</title>
    <link href="https://www.allanninal.dev/build/how-a-receipt-reaches-the-books/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-receipt-reaches-the-books/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">Three actions on the review card: approve (append to the accounting sheet), correct (fix a field, then append), and reject (not a business expense). Every action files the image and writes an audit row. Part 5 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>What the receipt organizer costs</title>
    <link href="https://www.allanninal.dev/build/what-the-receipt-organizer-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-receipt-organizer-costs/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume — about $3 at 200 receipts, $7 at 500, $26 at 2,000. Textract and one small Bedrock call per receipt are the only lines that grow; filing, math, and export are plain Python. Part 6 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the receipt organizer architecture</title>
    <link href="https://www.allanninal.dev/build/receipt-organizer-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/receipt-organizer-engineering-reference/</id>
    <published>2026-05-09T09:00:00Z</published>
    <updated>2026-05-09T09:00:00Z</updated>
    <summary type="text">Same system, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, the Textract config, and the DynamoDB schemas. Part 7 of 7 in the Receipt organizer series.</summary>
  </entry>
  <entry>
    <title>An inventory reorder bot on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/inventory-reorder-bot-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/inventory-reorder-bot-on-aws/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">The whole system on one page — a stock intake, a checker, and a draft-PO piece, plus the four moves they share for every item. Part 1 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>How a stock level gets read</title>
    <link href="https://www.allanninal.dev/build/how-a-stock-level-gets-read/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-stock-level-gets-read/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">Three lanes keep the stock list current: the Drive sheet itself, an inbox-forwarding lane that parses stock-count sheets into proposed rows for approval, and a live POS lane that updates counts the moment something sells. Part 2 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>How the bot spots a low item</title>
    <link href="https://www.allanninal.dev/build/how-the-bot-spots-a-low-item/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-bot-spots-a-low-item/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">A daily check reads the stock list, works out a reorder point per item from sales rate and supplier lead time, and picks one of four moves: stocked, watch, reorder, urgent reorder. No model on the check. Part 3 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>How a draft PO reaches the owner</title>
    <link href="https://www.allanninal.dev/build/how-a-draft-po-reaches-the-owner/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-draft-po-reaches-the-owner/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">Supplier resolution per item, quiet hours, a sensible order quantity, a ready-to-send draft purchase order, and the four guardrails between the bot's chosen move and the draft landing in front of the owner. Part 4 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>How a reorder gets approved</title>
    <link href="https://www.allanninal.dev/build/how-a-reorder-gets-approved/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reorder-gets-approved/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">Three actions on the draft PO: approve (send to the supplier, mark on-order), edit (change quantity or supplier first), and skip (do nothing this round). Nothing is ordered without a human tap. Every action is logged. Part 5 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>What the inventory reorder bot costs</title>
    <link href="https://www.allanninal.dev/build/what-the-inventory-reorder-bot-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-inventory-reorder-bot-costs/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The bot runs once a day, calls no models on the check, and only fires Bedrock on the inbound parsing lane and the monthly summary. Part 6 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the inventory reorder bot architecture</title>
    <link href="https://www.allanninal.dev/build/inventory-reorder-bot-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/inventory-reorder-bot-engineering-reference/</id>
    <published>2026-05-08T09:00:00Z</published>
    <updated>2026-05-08T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Inventory reorder bot series.</summary>
  </entry>
  <entry>
    <title>An appointment reminder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/appointment-reminder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/appointment-reminder-on-aws/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an appointment intake, an hourly reminder, and a reply piece, plus the four moves they share for every booking. Part 1 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>How an appointment gets on the list</title>
    <link href="https://www.allanninal.dev/build/how-an-appointment-gets-on-the-list/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-appointment-gets-on-the-list/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">Three lanes feed the list — the Drive sheet itself, an inbox-forwarding lane that parses booking emails into proposed rows for one-tap approval, and an hourly calendar import for events tagged #appt. Part 2 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>How the reminder knows when to send</title>
    <link href="https://www.allanninal.dev/build/how-the-reminder-knows-when-to-send/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-reminder-knows-when-to-send/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">An hourly tick reads the list, computes hours-to-appointment per booking, compares against per-service reminder schedules in the rules doc, and picks one of four moves: scheduled, first reminder, second reminder, gap alert. No model on the tick. Part 3 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>How a reminder reaches the customer</title>
    <link href="https://www.allanninal.dev/build/how-the-reminder-finds-the-customer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-reminder-finds-the-customer/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">Channel choice per customer, quiet hours, holiday calendars, text with one-tap reply links, email fallback, and the four guardrails between the reminder's chosen move and the message landing. Part 4 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>How a customer confirms or reschedules</title>
    <link href="https://www.allanninal.dev/build/how-a-customer-confirms-or-reschedules/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-customer-confirms-or-reschedules/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">Three actions on the reply links: confirm (stop the reminders), reschedule (new time, fresh chain), and cancel (free the slot and alert staff). Every action is logged. Part 5 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>What the appointment reminder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-appointment-reminder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-appointment-reminder-costs/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">A few dollars a month at SMB volume. The reminder runs hourly, calls no models on the tick, and the main cost is the text messages themselves. Part 6 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the appointment reminder architecture</title>
    <link href="https://www.allanninal.dev/build/appointment-reminder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/appointment-reminder-engineering-reference/</id>
    <published>2026-05-07T09:00:00Z</published>
    <updated>2026-05-07T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers — service names, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, SNS SMS setup, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Appointment reminder series.</summary>
  </entry>
  <entry>
    <title>An invoice chaser on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/invoice-chaser-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/invoice-chaser-on-aws/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">The whole system on one page — an invoice intake, a chaser, and a sender, plus the four moves they share for every open invoice. Part 1 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>How an invoice gets loaded</title>
    <link href="https://www.allanninal.dev/build/how-an-invoice-gets-loaded/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-invoice-gets-loaded/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">Three lanes feed the list: the Drive sheet from your accounting tool, an inbox-forwarding lane that parses invoice PDFs into proposed rows for approval, and a webhook lane that picks up new invoices the moment they are issued. Part 2 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>How overdue gets detected</title>
    <link href="https://www.allanninal.dev/build/how-overdue-gets-detected/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-overdue-gets-detected/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">A daily tick reads the list, computes days-past-due per invoice, compares against per-terms cadences in the rules doc, and picks one of four moves: current, first nudge, follow-up, escalate. No model on the tick. Part 3 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>How a reminder reaches the customer</title>
    <link href="https://www.allanninal.dev/build/how-a-reminder-reaches-the-customer/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reminder-reaches-the-customer/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">Contact resolution per invoice, quiet hours, weekends, a friendly-to-firm tone ladder, a pay-link in every reminder, and the four guardrails between the chosen move and the email landing. Part 4 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>How a chase stops on payment</title>
    <link href="https://www.allanninal.dev/build/how-a-chase-stops-on-payment/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-chase-stops-on-payment/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">The chase stops the moment payment lands. Plus three owner actions: pause (hold without dismissing), mark disputed (stop and flag a human), and write off (close it out). Every action is logged. Part 5 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>What the invoice chaser costs</title>
    <link href="https://www.allanninal.dev/build/what-the-invoice-chaser-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-invoice-chaser-costs/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">A couple of dollars a month at SMB volume. The chaser runs once a day, calls no models on the tick, and only fires Bedrock on the inbound parsing lane and the monthly summary. Part 6 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the invoice chaser architecture</title>
    <link href="https://www.allanninal.dev/build/invoice-chaser-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/invoice-chaser-engineering-reference/</id>
    <published>2026-05-06T09:00:00Z</published>
    <updated>2026-05-06T09:00:00Z</updated>
    <summary type="text">Same system, drawn for engineers. Service names, resource identifiers, region, Bedrock model IDs, Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7 in the Invoice chaser series.</summary>
  </entry>
  <entry>
    <title>A document expiry watcher on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/expiry-watcher-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/expiry-watcher-on-aws/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">A small watcher that tracks every contract, certificate, insurance policy, license, lease, and software subscription your business renews; pings the right owner with full context before each one lapses; escalates if nobody acts. The owner can renew, snooze, or ack-only directly from the alert. The whole system on one page. Part 1 of 7 in the Document expiry watcher series.</summary>
  </entry>
  <entry>
    <title>How an item gets tracked</title>
    <link href="https://www.allanninal.dev/build/how-an-item-gets-tracked/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-item-gets-tracked/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">Three lanes feed the registry &#8212; the Drive sheet itself, an inbox-forwarding lane that uses Textract and Bedrock Haiku 4.5 to propose rows for one-tap approval, and an hourly calendar import for events tagged hashtag-expires. The Drive sheet stays the source of truth; the other lanes are conveniences that propose rows for it. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the watcher knows when to alert</title>
    <link href="https://www.allanninal.dev/build/how-the-watcher-knows-when-to-alert/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-watcher-knows-when-to-alert/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">A daily tick reads the registry, computes days-to-expiry per item, compares against per-category window chains in the rules doc (legal 90/60/30/14/3, insurance 60/30/7, software 30/14/3), and picks one of four moves: healthy, first alert, reminder, escalate. The watcher itself uses no model. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How an alert finds the right person</title>
    <link href="https://www.allanninal.dev/build/how-an-alert-finds-the-right-person/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-alert-finds-the-right-person/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">Owner resolution per item, quiet hours, holiday calendars, Slack DMs with full context, email fallback, and the four guardrails between the watcher&#8217;s chosen move and the actual ping landing. Every gate is a deterministic check. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How an item gets renewed</title>
    <link href="https://www.allanninal.dev/build/how-an-item-gets-renewed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-item-gets-renewed/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">Three actions on the Acknowledge button &#8212; renew (new expiry, fresh chain), snooze (delay, capped at three per chain), and ack-only (silence the chain without changing the expiry). Every action is logged to ew-audit and reversible. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the expiry watcher costs</title>
    <link href="https://www.allanninal.dev/build/what-the-expiry-watcher-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-expiry-watcher-costs/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">Pennies a month at SMB volume. The watcher runs once a day and uses no models on the daily tick. Bedrock and Textract fire only on the inbound parsing lane and the monthly summary. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the expiry watcher architecture</title>
    <link href="https://www.allanninal.dev/build/expiry-watcher-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/expiry-watcher-engineering-reference/</id>
    <published>2026-05-05T09:00:00Z</published>
    <updated>2026-05-05T09:00:00Z</updated>
    <summary type="text">The same system drawn purely for engineers &#8212; service names, resource identifiers, region, Bedrock model IDs (Claude Haiku 4.5 via Global cross-Region inference), Lambda inventory, IAM scopes, the SES inbound rule set, EventBridge Scheduler config, and the DynamoDB schemas. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A quote drafter on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/quote-drafter-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/quote-drafter-on-aws/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">A small drafter that catches every incoming RFQ from your website form, sales inbox, or direct file uploads; extracts line items against your catalog; prices each line against your rules; drafts a complete quote with a cover paragraph in your voice; and parks it in front of a rep for one-tap approval &#8212; designed on AWS for a few dollars a month at SMB volume. Drafts never auto-send. The whole system on one page. Part 1 of 7 in the Quote drafter series.</summary>
  </entry>
  <entry>
    <title>How an RFQ reaches the drafter</title>
    <link href="https://www.allanninal.dev/build/how-an-rfq-reaches-the-drafter/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-rfq-reaches-the-drafter/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">Three lanes at the door &#8212; a website RFQ form fast path, the shared sales inbox via SES, and direct uploads through a presigned S3 portal. Three different mechanisms folded into one queue, with deduplication and a free first-pass screen before any AI runs. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the drafter reads an RFQ</title>
    <link href="https://www.allanninal.dev/build/how-the-drafter-reads-an-rfq/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-drafter-reads-an-rfq/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">Three small extractors run in parallel against every RFQ &#8212; line items, constraints, context &#8212; plus a catalog lookup grounded in a Bedrock Knowledge Base. Each with a confidence score the move-picker reads before it picks one of four moves: auto-draft, clarify, out of scope, or reject. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How a quote gets priced</title>
    <link href="https://www.allanninal.dev/build/how-a-quote-gets-priced/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-quote-gets-priced/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">A five-stage pricing pipeline runs per line in plain Python &#8212; base price, tier discount, volume break, regional, bundle. Every applied rule cites the catalog row or rules-doc section that produced it. The engine never invents a number; missing rules mean no rule applied, never a guess. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How a draft stays honest</title>
    <link href="https://www.allanninal.dev/build/how-a-draft-stays-honest/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-draft-stays-honest/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">Four guardrails sit between the composer&#8217;s output and the rep&#8217;s review queue &#8212; citation required on every priced line, no fabricated SKUs in free-text, no commit on availability, and a discount cap that requires manager approval above a threshold. A draft never auto-sends; the rep&#8217;s approval is the only path to the customer. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the quote drafter costs</title>
    <link href="https://www.allanninal.dev/build/what-the-quote-drafter-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-quote-drafter-costs/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">A coffee a month at SMB RFQ volume. Pennies per RFQ, dominated by Bedrock tokens for the extractors and the cover-paragraph composer. Where the dollars actually go and how the cost scales linearly with volume. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the quote drafter architecture</title>
    <link href="https://www.allanninal.dev/build/quote-drafter-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/quote-drafter-engineering-reference/</id>
    <published>2026-05-04T09:00:00Z</published>
    <updated>2026-05-04T09:00:00Z</updated>
    <summary type="text">The same system drawn purely for engineers &#8212; service names, resource identifiers, region, Bedrock model IDs (Claude Haiku 4.5 + Titan Text Embeddings v2 with a Knowledge Base on S3 Vectors), Lambda Function URLs, SES inbound rule set, presigned S3 upload portal, IAM least-privilege scopes, and CRM adapters. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A lead intake bot on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/lead-intake-bot-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/lead-intake-bot-on-aws/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">A small intake bot that catches every new lead from your website forms, Meta Lead Ads, Google Ads, and the shared sales inbox, qualifies it against your ICP, instantly routes the hot ones to the right person with full context attached, and quietly drops the cold ones into a nurture queue &#8212; designed on AWS for a few dollars a month at SMB volume. The whole system on one page. Part 1 of 7 in the Lead intake bot series.</summary>
  </entry>
  <entry>
    <title>How a lead reaches the intake bot</title>
    <link href="https://www.allanninal.dev/build/how-a-lead-reaches-the-intake-bot/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-lead-reaches-the-intake-bot/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">Three lanes at the door &#8212; a website-form fast path with shared secret and captcha, an ad-platform push from Meta Lead Ads and Google Ads, and the shared sales inbox via SES. Three different mechanisms folded into one queue, with deduplication and a free first-pass screen before any AI runs. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the bot reads a lead</title>
    <link href="https://www.allanninal.dev/build/how-the-bot-reads-a-lead/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-bot-reads-a-lead/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">Three small extractors run in parallel against every lead &#8212; intent, urgency, and fit signals &#8212; plus a free passive enrichment from the email domain. Each with a confidence score the move-picker reads before it decides anything. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How the bot scores and routes a lead</title>
    <link href="https://www.allanninal.dev/build/how-the-bot-scores-and-routes-a-lead/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-bot-scores-and-routes-a-lead/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">Four moves, one pick per lead &#8212; hot route, warm follow-up, nurture, or reject. Two override gates bypass scoring (partner-allowlist forces hot, the reject gate kills off-ICP and support); the linear scorer reads the ICP file; round-robin assignment respects on-call hours with a 15-minute escalation timer. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How a reply stays on policy</title>
    <link href="https://www.allanninal.dev/build/how-a-reply-stays-on-policy/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reply-stays-on-policy/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">The composer reads three short Drive docs &#8212; voice, pricing-and-promos, and ICP-and-disqualifiers &#8212; and writes the reply only from those. Four guardrails sit between the model and the send button: citation required, no fabricated specifics, no commit on availability, no PII in subject lines. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the lead intake bot costs</title>
    <link href="https://www.allanninal.dev/build/what-the-lead-intake-bot-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-lead-intake-bot-costs/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">A coffee a month at SMB lead volume. The fixed cost is essentially zero; the variable cost is dominated by Bedrock tokens for the extractors and the composer. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the lead intake bot architecture</title>
    <link href="https://www.allanninal.dev/build/lead-intake-bot-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/lead-intake-bot-engineering-reference/</id>
    <published>2026-05-03T09:00:00Z</published>
    <updated>2026-05-03T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Knowledge Base wiring, Meta Lead Ads and Google Ads API specifics, SES inbound, CRM destinations, and the actual flow operations. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A review responder on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/review-responder-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/review-responder-on-aws/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">A small responder that watches every new review across your platforms, drafts replies in your voice from your own policies, posts the safe ones automatically, and quietly hands you the rest with everything you need to respond in one sitting &#8212; designed on AWS for a few dollars a month at SMB review volume. The whole system on one page. Part 1 of 7 in the Review responder series.</summary>
  </entry>
  <entry>
    <title>How a review reaches the responder</title>
    <link href="https://www.allanninal.dev/build/how-a-review-reaches-the-responder/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-review-reaches-the-responder/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">Three lanes at the door &#8212; Google Business Profile push, Facebook page reviews push, Yelp poll. Three different mechanisms folded into one queue, with deduplication and a free first-pass screen before any AI runs. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the responder reads a review</title>
    <link href="https://www.allanninal.dev/build/how-the-responder-reads-a-review/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-responder-reads-a-review/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">Three small extractors run in parallel against every review &#8212; the rating with a sentiment cross-check, the themes the customer raised, and the specifics like names, dishes, and dates &#8212; each with a confidence score the move-picker reads before it decides anything. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How the responder picks a move</title>
    <link href="https://www.allanninal.dev/build/how-the-responder-picks-a-move/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-responder-picks-a-move/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">Four moves, one pick per review &#8212; auto-reply, draft, escalate, or ignore. Safety keywords bypass everything and escalate; named staff and specific complaints become drafts; only the obvious thanks-yous auto-post. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How a reply stays in your voice</title>
    <link href="https://www.allanninal.dev/build/how-a-reply-stays-in-your-voice/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reply-stays-in-your-voice/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">The composer reads three short Drive docs &#8212; how you sound, what you can promise, what you must never say &#8212; and writes the reply only from those. Four guardrails sit between the model and the post button: citation, no PII echo, no fabricated specifics, no off-policy promises. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the review responder costs</title>
    <link href="https://www.allanninal.dev/build/what-the-review-responder-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-review-responder-costs/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">A coffee a month at SMB review volume. The fixed cost is essentially zero; the variable cost is dominated by Bedrock tokens for the extractors and the composer. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the review responder architecture</title>
    <link href="https://www.allanninal.dev/build/review-responder-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/review-responder-engineering-reference/</id>
    <published>2026-05-02T09:00:00Z</published>
    <updated>2026-05-02T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Knowledge Base wiring, Google Business Profile and Meta Graph API specifics, and the actual flow operations. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A website chat assistant on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/website-chat-assistant-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/website-chat-assistant-on-aws/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">A small chat widget that lives on your website, answers visitors from your own knowledge in real time, hands the rest to a human cleanly, and quietly logs every miss for next week&#8217;s review &#8212; designed on AWS for a few dollars a month at SMB volume. The whole system on one page. Part 1 of 7 in the Website chat assistant series.</summary>
  </entry>
  <entry>
    <title>How a conversation starts and stays alive</title>
    <link href="https://www.allanninal.dev/build/how-a-conversation-starts/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-conversation-starts/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">Connect, exchange, idle. A websocket and a small scratchpad &#8212; that&#8217;s the whole shape. The session knows the last few turns and forgets the visitor next week. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the assistant answers</title>
    <link href="https://www.allanninal.dev/build/how-the-assistant-answers/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-assistant-answers/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">Search first, then generate. The AI picks one of four tools per visitor turn &#8212; answer, clarify, hand off, decline &#8212; and never auto-answers without a citation. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How a handoff to a human works</title>
    <link href="https://www.allanninal.dev/build/how-a-handoff-to-a-human-works/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-handoff-to-a-human-works/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">Tell the visitor, package the transcript, deliver to one place, hold the session for a moment. The visitor never repeats themselves to the human; the transcript is the ticket. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How gaps become better answers</title>
    <link href="https://www.allanninal.dev/build/how-gaps-become-better-answers/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-gaps-become-better-answers/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">A small clockwise loop &#8212; log every miss, group similar questions, write a paragraph, re-index automatically. Ten minutes a week and month two&#8217;s assistant is meaningfully better than month one&#8217;s. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the chat assistant costs</title>
    <link href="https://www.allanninal.dev/build/what-the-chat-assistant-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-chat-assistant-costs/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">A coffee a month at SMB volume. The fixed cost is essentially zero; the variable cost is dominated by Bedrock tokens for the answerer and the managed knowledge-base queries. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the chat assistant architecture</title>
    <link href="https://www.allanninal.dev/build/website-chat-assistant-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/website-chat-assistant-engineering-reference/</id>
    <published>2026-05-01T09:00:00Z</published>
    <updated>2026-05-01T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Knowledge Base wiring, and the actual flow operations. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A booking assistant on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/booking-assistant-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/booking-assistant-on-aws/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">A small assistant that reads appointment requests, checks your Google Calendar, proposes slots that respect your service rules, locks in the customer&#8217;s pick, and sends quiet reminders &#8212; built purely on AWS for a few dollars a month at SMB volume. The whole system on one page. Part 1 of 7 in the Booking assistant series.</summary>
  </entry>
  <entry>
    <title>How a booking request reaches the assistant</title>
    <link href="https://www.allanninal.dev/build/how-a-booking-request-reaches-the-assistant/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-booking-request-reaches-the-assistant/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">Three lanes at the door: the web-form fast path for the easy 80%, an AI-handle path for free-text email, and a direct escalate for VIPs the AI should never touch. By the time the scheduler runs, the easy ones are answered and the VIPs are already through. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the assistant understands the request</title>
    <link href="https://www.allanninal.dev/build/how-the-assistant-understands-the-request/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-assistant-understands-the-request/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">Three small extractors run in parallel: which service, when roughly, and who&#8217;s booking. Each returns a confidence score; anything borderline becomes a draft a human approves in seconds, never a silent guess. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How the assistant picks slots</title>
    <link href="https://www.allanninal.dev/build/how-the-assistant-picks-slots/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-assistant-picks-slots/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">Five filters in order: calendar query, working hours, service duration plus buffers, blackouts and capacity caps, then rank. Every constraint comes from your service-rules file or the calendar &#8212; nothing is hard-coded. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How a booking gets confirmed</title>
    <link href="https://www.allanninal.dev/build/how-a-booking-gets-confirmed/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-booking-gets-confirmed/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">Claim, write, confirm, remind &#8212; in that order. An atomic conditional write on a small claim table prevents double-booking even when two customers click at the same second; idempotency keys mean a retry never duplicates an event. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the booking assistant costs</title>
    <link href="https://www.allanninal.dev/build/what-the-booking-assistant-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-booking-assistant-costs/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">A coffee a month at SMB volume. Cents per request, scaling with how much volume runs through the email lane &#8212; the web-form fast path is essentially free. Where the dollars actually go in a serverless booking assistant on AWS. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the booking assistant architecture</title>
    <link href="https://www.allanninal.dev/build/booking-assistant-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/booking-assistant-engineering-reference/</id>
    <published>2026-04-30T09:00:00Z</published>
    <updated>2026-04-30T09:00:00Z</updated>
    <summary type="text">Same booking assistant as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, Google Calendar API choices, and the actual flow operations &#8212; everything you&#8217;d need to recreate it in your own AWS account. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>An email assistant on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/email-assistant-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/email-assistant-on-aws/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">A small assistant that reads your inbox, drafts replies from your own knowledge, and escalates anything beyond its remit to a human &#8212; built purely on AWS for a few dollars a month at SMB volume. The whole system on one page. Part 1 of 7 in the Email assistant series.</summary>
  </entry>
  <entry>
    <title>How an email enters the assistant</title>
    <link href="https://www.allanninal.dev/build/how-an-email-enters-the-assistant/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-an-email-enters-the-assistant/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">Three lanes at the door: auto-archive for newsletters and bots, AI-handle for normal mail, direct escalate for the people you always want to see. By the time the brain runs, the noise is gone and the VIPs are already through. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the assistant reads an email</title>
    <link href="https://www.allanninal.dev/build/how-the-assistant-reads-an-email/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-assistant-reads-an-email/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">Strip the noise &#8212; quoted threads, signatures, footers, trackers &#8212; and what&#8217;s left is the real message. The brain only sees the clean version, and the cost of every reply drops with it. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How the assistant decides what to do</title>
    <link href="https://www.allanninal.dev/build/how-the-assistant-decides-what-to-do/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-assistant-decides-what-to-do/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">Four tools, one pick per email: answer directly from the knowledge file, draft a reply for review, escalate to a human, or archive without replying. The AI is allowed to be confident or to defer &#8212; never to invent. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How a reply stays accurate</title>
    <link href="https://www.allanninal.dev/build/how-a-reply-stays-accurate/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-reply-stays-accurate/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">Every auto-reply cites a passage from your knowledge file. No citation, no auto-send. Borderline confidence routes to a draft you approve in seconds. Two thresholds, one safety net, no invented prices. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the email assistant costs</title>
    <link href="https://www.allanninal.dev/build/what-the-email-assistant-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-email-assistant-costs/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">A coffee a month at SMB volume. Cents per email, scaling smoothly with how often the inbox rings. Where the dollars actually go in a serverless email assistant on AWS. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the email assistant architecture</title>
    <link href="https://www.allanninal.dev/build/email-assistant-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/email-assistant-engineering-reference/</id>
    <published>2026-04-29T09:00:00Z</published>
    <updated>2026-04-29T09:00:00Z</updated>
    <summary type="text">Same email assistant as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, and the actual flow operations &#8212; everything you&#8217;d need to recreate it in your own AWS account. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A voice agent on AWS for the price of a phone plan</title>
    <link href="https://www.allanninal.dev/build/voice-agent-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/voice-agent-on-aws/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">A small voice agent that answers your business phone, replies from your own knowledge in real time, and politely passes the rest to a human &#8212; built purely on AWS for the cost of a basic phone plan. The whole system on one page. Part 1 of 7 in the Voice agent series.</summary>
  </entry>
  <entry>
    <title>How a call connects</title>
    <link href="https://www.allanninal.dev/build/how-a-call-connects/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-call-connects/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">Three ways a call can go: voicemail after hours, AI session in business hours, or direct human transfer for VIP numbers. By the time the AI hears anything, the call has already been screened. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the listener hears</title>
    <link href="https://www.allanninal.dev/build/how-the-listener-hears/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-listener-hears/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">Streaming transcription, partial guesses refined live, locked the moment the caller pauses. The brain only sees the final version. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How the brain decides what to say</title>
    <link href="https://www.allanninal.dev/build/how-the-brain-decides-what-to-say/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-brain-decides-what-to-say/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">Four tools, one pick per turn: answer from the knowledge file, book the appointment, transfer to a human, or end the call gracefully. The AI is allowed to be confident or to defer &#8212; never to invent. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How the speaker stays natural</title>
    <link href="https://www.allanninal.dev/build/how-the-speaker-stays-natural/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-speaker-stays-natural/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">The latency budget for a one-second-or-less voice reply &#8212; where each millisecond goes, how the system streams the voice back, and what happens when the budget blows. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the voice agent costs</title>
    <link href="https://www.allanninal.dev/build/what-the-voice-agent-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-voice-agent-costs/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">Phone-bill territory at SMB volume. The phone number is the floor; everything else scales with how often it rings. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the voice agent architecture</title>
    <link href="https://www.allanninal.dev/build/voice-agent-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/voice-agent-engineering-reference/</id>
    <published>2026-04-28T09:00:00Z</published>
    <updated>2026-04-28T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the voice-agent series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, and the actual flow operations. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A document pipeline on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/document-pipeline-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/document-pipeline-on-aws/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">A serverless pipeline that reads documents for you, checks each one, and sends the structured data straight to wherever it needs to go &#8212; built purely on AWS. The whole system on one page. Part 1 of 7 in the Document pipeline series.</summary>
  </entry>
  <entry>
    <title>How a document enters the pipeline</title>
    <link href="https://www.allanninal.dev/build/how-a-document-enters-the-pipeline/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-document-enters-the-pipeline/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">Three ways in &#8212; uploaded, emailed, or dropped into a folder. The intake step gets them into one shared place, screens them for trouble, and tells the rest of the pipeline what type each one is. Part 2 of 7.</summary>
  </entry>
  <entry>
    <title>How the AI reads a document</title>
    <link href="https://www.allanninal.dev/build/how-the-ai-reads-a-document/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-ai-reads-a-document/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">Two AIs in sequence &#8212; a specialist for layout, a generalist for meaning. Together they read a document better, faster, and cheaper than either alone. Part 3 of 7.</summary>
  </entry>
  <entry>
    <title>How extraction stays accurate</title>
    <link href="https://www.allanninal.dev/build/how-extraction-stays-accurate/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-extraction-stays-accurate/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">The validator gates the obvious cases through and queues the unsure ones for a quick human review. Wrong values never quietly slip into your tools &#8212; they always hit a human gate first. Part 4 of 7.</summary>
  </entry>
  <entry>
    <title>How the data flows out</title>
    <link href="https://www.allanninal.dev/build/how-the-data-flows-out/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-data-flows-out/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">Each destination is a small pluggable piece. The rules file decides which document type lands where &#8212; sheet, accounting software, Slack. Part 5 of 7.</summary>
  </entry>
  <entry>
    <title>What the document pipeline costs</title>
    <link href="https://www.allanninal.dev/build/what-the-document-pipeline-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-document-pipeline-costs/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">A coffee a month at typical SMB volume. Cents per document, not dollars. Where the bill actually comes from. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the document pipeline architecture</title>
    <link href="https://www.allanninal.dev/build/document-pipeline-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/document-pipeline-engineering-reference/</id>
    <published>2026-04-27T09:00:00Z</published>
    <updated>2026-04-27T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, and the actual flow operations. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A daily briefing bot on AWS for a few dollars a month</title>
    <link href="https://www.allanninal.dev/build/daily-briefing-bot-on-aws/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/daily-briefing-bot-on-aws/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">An automated digest that watches the sources you care about and emails you the few items worth your time &#8212; built purely on AWS. The whole system on one page. Part 1 of 6 in the Daily briefing bot series.</summary>
  </entry>
  <entry>
    <title>How the ingestor walks your sources</title>
    <link href="https://www.allanninal.dev/build/how-the-ingestor-walks-your-sources/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-ingestor-walks-your-sources/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">A small dispatcher reads your source list, hands each source to a specialist worker, and drops everything new into one shared place. Three workers, one mailbox &#8212; the boring part of the briefing bot. Part 2 of 6.</summary>
  </entry>
  <entry>
    <title>How the bot picks what matters</title>
    <link href="https://www.allanninal.dev/build/how-the-bot-picks-what-matters/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-bot-picks-what-matters/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">Three filters in order, cheapest first. Most items die at the free filter; a small AI sorts most of the rest; the smart AI only sees the few borderline cases. Part 3 of 6.</summary>
  </entry>
  <entry>
    <title>How the digest gets delivered</title>
    <link href="https://www.allanninal.dev/build/how-the-digest-gets-delivered/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-digest-gets-delivered/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">Five short blocks in your inbox at 7am &#8212; or nothing on quiet days. The postman writes the digest, ships it to one channel, and knows when not to send. Part 4 of 6.</summary>
  </entry>
  <entry>
    <title>How you change what the bot watches</title>
    <link href="https://www.allanninal.dev/build/how-you-change-what-the-bot-watches/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-you-change-what-the-bot-watches/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">Two short Drive docs are the entire interface. Edit, save, the bot picks up the change tomorrow &#8212; with a safety check that keeps the old version live if anything looks broken. Part 5 of 6.</summary>
  </entry>
  <entry>
    <title>What the briefing bot costs</title>
    <link href="https://www.allanninal.dev/build/what-the-briefing-bot-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-the-briefing-bot-costs/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">A coffee a month, possibly less. Line by line, where the dollars actually go in a serverless briefing bot on AWS. Part 6 of 7.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the briefing bot architecture</title>
    <link href="https://www.allanninal.dev/build/briefing-bot-engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/briefing-bot-engineering-reference/</id>
    <published>2026-04-26T09:00:00Z</published>
    <updated>2026-04-26T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, Bedrock model IDs, and the actual flow operations &#8212; everything you&#8217;d need to recreate this in your own AWS account. Part 7 of 7.</summary>
  </entry>
  <entry>
    <title>A Facebook autoposting system on AWS for $2&#8211;$5 a month</title>
    <link href="https://www.allanninal.dev/build/aws-facebook-autoposting-overview/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/aws-facebook-autoposting-overview/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">A scheduled Facebook poster that stays on-topic, answers questions correctly from a Google Drive knowledge base, and doesn't surprise you with a huge cloud bill. The whole system on one page. Part 1 of 6 in the AWS autoposting series.</summary>
  </entry>
  <entry>
    <title>How code becomes a working system</title>
    <link href="https://www.allanninal.dev/build/how-code-becomes-a-working-system/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-code-becomes-a-working-system/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">Push to GitHub, walk away. The cloud handles the rest &#8212; and never holds a long-lived password. How a private repo becomes a live AWS deployment via OIDC and short-lived credentials. Part 2 of 6.</summary>
  </entry>
  <entry>
    <title>How a post actually goes out</title>
    <link href="https://www.allanninal.dev/build/how-a-post-actually-goes-out/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-a-post-actually-goes-out/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">Five quick checks between &#8220;it's time&#8221; and &#8220;post is live.&#8221; Cheap gates first, expensive gates only when needed &#8212; so a faith post never lands on a forex page. Part 3 of 6.</summary>
  </entry>
  <entry>
    <title>How the Drive folder powers everything</title>
    <link href="https://www.allanninal.dev/build/how-the-drive-folder-powers-everything/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-the-drive-folder-powers-everything/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">The client edits a Google Doc. The system updates itself &#8212; with a safety layer that keeps the old version live if anything looks broken. Part 4 of 6.</summary>
  </entry>
  <entry>
    <title>How replies work without making things up</title>
    <link href="https://www.allanninal.dev/build/how-replies-work-without-making-things-up/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/how-replies-work-without-making-things-up/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">The bot answers from the client's docs only &#8212; or escalates to a human. Citation required, no exceptions. How to build an AI reply pipeline that can't hallucinate. Part 5 of 6.</summary>
  </entry>
  <entry>
    <title>What this all costs</title>
    <link href="https://www.allanninal.dev/build/what-this-all-costs/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/what-this-all-costs/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">A coffee a month, not a Netflix subscription. Line by line, where the dollars actually go in a serverless Facebook autoposting system on AWS. Part 6 of 6.</summary>
  </entry>
  <entry>
    <title>Engineering reference: the full architecture</title>
    <link href="https://www.allanninal.dev/build/engineering-reference/" rel="alternate" type="text/html"/>
    <id>https://www.allanninal.dev/build/engineering-reference/</id>
    <published>2026-04-25T09:00:00Z</published>
    <updated>2026-04-25T09:00:00Z</updated>
    <summary type="text">Same system as the rest of the series, drawn purely for engineers. Service names, resource identifiers, region, and the actual flow operations — everything you'd need to recreate this in your own AWS account. Part 7 of 7.</summary>
  </entry>
</feed>
