<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Weekly Tinkerer]]></title><description><![CDATA[Things I build out of utility for myself. Software some weeks, the farm other weeks. Output first, process as subplot.]]></description><link>https://theweeklytinkerer.com</link><image><url>https://substackcdn.com/image/fetch/$s_!ubT6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F491f2404-dd18-4eeb-9093-fe9046b9dedf_256x256.png</url><title>The Weekly Tinkerer</title><link>https://theweeklytinkerer.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 01 Aug 2026 07:37:54 GMT</lastBuildDate><atom:link href="https://theweeklytinkerer.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Kevin Ebert]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[weeklytinkerer@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[weeklytinkerer@substack.com]]></itunes:email><itunes:name><![CDATA[Kevin Ebert]]></itunes:name></itunes:owner><itunes:author><![CDATA[Kevin Ebert]]></itunes:author><googleplay:owner><![CDATA[weeklytinkerer@substack.com]]></googleplay:owner><googleplay:email><![CDATA[weeklytinkerer@substack.com]]></googleplay:email><googleplay:author><![CDATA[Kevin Ebert]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Impotent Inbox]]></title><description><![CDATA[Three companies, one AI trend, and the experiment I'm running to stop giving my inbox away for free.]]></description><link>https://theweeklytinkerer.com/p/the-impotent-inbox</link><guid isPermaLink="false">https://theweeklytinkerer.com/p/the-impotent-inbox</guid><dc:creator><![CDATA[Kevin Ebert]]></dc:creator><pubDate>Wed, 29 Jul 2026 05:47:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ubT6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F491f2404-dd18-4eeb-9093-fe9046b9dedf_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have 1,390 unread messages in my inbox telling me that my inbox is impotent.</p><p>It&#8217;s time to turn the opt-out model of email on its head to fix it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://theweeklytinkerer.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Weekly Tinkerer! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I wanted another Apple HomePod speaker for our home. Found it online for in-store pickup at my local Electronic Express. I added it to the cart, checked out as a guest, and forgot about it.</p><p>A couple of years later, I received a spam message addressed to: electronicexpress-0921@k3bert.net. I was confused; I didn&#8217;t create an account with them. Why am I receiving spam?</p><p>I looked up the address on Have I Been Pwned and found out that in Feb of 2023, Eye4Fraud, an online fraud protection system used during payment transactions, was breached and the data made available to hackers.</p><p>The forgotten email came back to life until I created an inbox rule to delete any messages sent to electronicexpress-0921@k3bert.net permanently<a href="mailto:electronicexpress-0921@k3bert.net.">.</a> <strong>Opt-out</strong>.</p><p>And then there was the time I decided to sign up for updates to my Alma Mater, CU. I opted to receive alum emails at cualumni@k3bert.net related to the engineering school, from which I graduated.</p><p>I received my first few updates, but then there was a shift. It wasn&#8217;t just updates on the School of Engineering; the emails were now boosters for the Alumni Association and covered other clubs and associations within the university. And then, to make matters worse, someone from the Alumni Association started calling. Their whole job is traveling around meeting alumni to &#8220;catch up,&#8221; which I&#8217;m fairly sure means asking for money.</p><p>I tried to opt-out through my mail preferences, but the emails and calls kept coming in.</p><p>I created an inbox rule to delete any messages sent to cualumni@k3bert.net permanently. <strong>Opt-out</strong> and blocked the phone number.</p><p>And then there was Verizon and Heritage Bank and Trust. For a few years, we did business with these companies until we stopped and permanently closed our accounts. Or at least we thought.</p><p>But, for months after closing the accounts, we still received monthly statements of our accounts. Confused, I called several times, explaining the accounts were closed and that there was no need to send us statements anymore. They agreed. The emails kept coming.</p><p>I created an inbox rule to delete any message sent to their aliases permanently. <strong>Opt-out</strong>.</p><p>Today, AI agents are augmenting and supercharging inbox rules and, if you decide, will take over your entire inbox for you. But what&#8217;s the difference between an AI agent managing your inbox for you versus you manually managing the rules? Other than out of sight, out of mind, it&#8217;s still opt-out.</p><p>In the immortal words of Buzz Lightyear: &#8220;This isn&#8217;t flying, it&#8217;s falling with style.&#8221;</p><p>And personally, I&#8217;m not all that comfortable with an AI agent crawling through my inbox and noticing who I shop or bank with, what credit cards I have, when I received my tax return, or when I pay my utility bills. This is all personal information that I would like to keep personal.</p><p>For years, I&#8217;ve been trying to manage the 1,390 unread messages like most everyone else. Giving out my email, receiving unwanted messages, trying to opt-out, giving up, and waving the white flag.</p><p>And now, I&#8217;m tinkering. I&#8217;ve started an experiment with my personal k3bert.net domain that, if successful, I&#8217;ll transition the domain my wife and I share for all our household accounts.</p><p>The experiment started with a declaration of email bankruptcy, archiving off all my messages into cold storage. And a gate slotted in front of the domain that inspects all senders and aliases, comparing them to an allowed list. When a new email arrives and the sender is not in the allowed list, I have a choice to allow the email to land in my inbox; in other words, I <strong>opt-in</strong>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://theweeklytinkerer.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Weekly Tinkerer! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[I thought color was taste. It's a number.]]></title><description><![CDATA[WCAG doesn't care about taste. It cares about a computable number, and mine wasn't hitting it.]]></description><link>https://theweeklytinkerer.com/p/i-thought-color-was-taste-its-a-number</link><guid isPermaLink="false">https://theweeklytinkerer.com/p/i-thought-color-was-taste-its-a-number</guid><dc:creator><![CDATA[Kevin Ebert]]></dc:creator><pubDate>Sun, 26 Jul 2026 00:50:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DVP5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was deep into the weeds trying to crack my onboarding dilemma. For a few weeks now, I&#8217;ve been trying to figure out why people are signing up for the Bwak News app, but not onboarding any newsletters.</p><p>And then something caught my attention. The green highlight text I chose for the site was difficult to read.</p><p>I have good color perception. In fact, Charlotte has crowned me the official paint color picker when it comes to decorating our house. So, I figured if the text was difficult for me to read, the roughly 8% of men with red-green color vision deficiency would struggle to read it also.</p><p>That wasn&#8217;t ok with me.</p><p>I was aware of the accessibility guidelines published by the Web Content Accessibility Guidelines (WCAG), but never had to worry about building with them. So I was surprised when I handed the problem off to my AI agent, and it threw up a sample page showing that indeed, the emerald-600 (hex #059669) green color I selected for the website failed the standard.</p><p>Standard? What standard? Up until now, my choice of colors was always based on taste and if it reads fine, ship it. I had no idea that the standard specified a way to calculate the contrast and grade it as a means to quantify accessibility usage of your application.</p><p>Every color has a computable relative luminance, which is a fancier and more accurate way of saying &#8220;how bright it actually reads to the eye.&#8221; Take two colors, compare their luminance, and you get a contrast ratio. Identical colors give you 1:1, invisible against themselves. Pure black on pure white gives you 21:1, the ceiling. Everything else falls somewhere in between, and the formula gives the same answer every time, for every viewer, regardless of taste.</p><p>My green, emerald-600, measured 3.77:1 against white. WCAG&#8217;s baseline standard for normal text is AA, and AA requires 4.5:1. My &#8220;fine, it&#8217;s just a light green&#8221; accent color was failing the accessibility standard I didn&#8217;t know existed.</p><p>There are two levels worth knowing; AA is the baseline most sites are expected to hit: 4.5:1 for normal text, 3:1 for large text (think 18-point-plus, or bold at 14-point-plus). AAA is the stricter tier, not usually a legal requirement but the one that gives you real margin instead of a bare pass: 7:1 for normal text, 4.5:1 for large. My original green wasn&#8217;t even clearing the low bar.</p><p>I went with the recommended emerald-800 (hex #065f46), a 7.68:1 ratio. Not a bare pass over the AA line, but clear of AAA too, with real margin to spare.</p><p>Because the color lived in one config token instead of being copy-pasted into forty places, changing it fixed the accent color across the entire app in one shot: CSS, every transactional email, account and billing pages, badges. All of it, except three admin-only internal tools I left alone on purpose, because nobody outside my team ever sees those.</p><p>The part I want to hand you isn&#8217;t the story. It&#8217;s that this is checkable, right now, on whatever you&#8217;re running, without asking anyone&#8217;s opinion:</p><p>1. <strong>Chrome DevTools</strong> &#8212; inspect any text, click the color swatch next to color in the styles panel, and it shows you the live ratio plus AA/AAA pass or fail, along with a suggested color that would pass. Fastest option, already installed.</p><p>2. <strong>WebAIM Contrast Checker</strong> (webaim.org/resources/contrastchecker) &#8212; paste in two hex codes, get the ratio and pass/fail for both normal and large text. It&#8217;s the tool most accessibility writeups point to first.</p><p>3. <strong>Lighthouse</strong> (also built into Chrome DevTools) &#8212; audits a whole page at once instead of one pair of colors at a time, so it&#8217;ll surface every low-contrast spot you didn&#8217;t think to check.</p><p>4. <strong>An AI coding assistant</strong> &#8212; ask it to build you a side-by-side comparison tool for your candidate colors, with real computed ratios. It&#8217;s not a shortcut around the standard; it&#8217;s a fast way to see the standard applied to your actual palette, which is exactly what got me here.</p><p>You already have a website, an app, a newsletter template, something with text on a background. Open DevTools, click your accent color, and read the number. If it says fail, you now know something concrete instead of something you assumed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DVP5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DVP5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 424w, https://substackcdn.com/image/fetch/$s_!DVP5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 848w, https://substackcdn.com/image/fetch/$s_!DVP5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 1272w, https://substackcdn.com/image/fetch/$s_!DVP5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DVP5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png" width="979" height="781" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0dfc536f-6960-4ed8-9336-58402f151589_979x781.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:781,&quot;width&quot;:979,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:105941,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://weeklytinkerer.substack.com/i/208508668?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DVP5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 424w, https://substackcdn.com/image/fetch/$s_!DVP5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 848w, https://substackcdn.com/image/fetch/$s_!DVP5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 1272w, https://substackcdn.com/image/fetch/$s_!DVP5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0dfc536f-6960-4ed8-9336-58402f151589_979x781.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p>]]></content:encoded></item><item><title><![CDATA[A bot logged into my app before my friend could]]></title><description><![CDATA[Found it before an appointment. Fixed it on the drive. Shipped it in the waiting room.]]></description><link>https://theweeklytinkerer.com/p/a-bot-logged-into-my-app-before-my</link><guid isPermaLink="false">https://theweeklytinkerer.com/p/a-bot-logged-into-my-app-before-my</guid><dc:creator><![CDATA[Kevin Ebert]]></dc:creator><pubDate>Sat, 25 Jul 2026 02:21:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ubT6!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F491f2404-dd18-4eeb-9093-fe9046b9dedf_256x256.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Receiving a message from a friend (and former work colleague) telling you they can&#8217;t access the system you built sucks. The kind of suck that one wishes there was a rock I could crawl under. I might be milking the scene a bit for dramatic effect, but it still sucks, no one likes to hear they have a problem.</p><p>That&#8217;s what happened to me today.</p><blockquote><p>&#8220;Hey Kevin, I signed up for your service, but I can&#8217;t login. Here&#8217;s my email address I&#8217;m trying to log in with and the device/browser I&#8217;m using. Can you help?&#8221;</p></blockquote><p>I had 10 minutes before Charlotte and I were to head out for an appointment, so the pressure was on.</p><p>Check the logs, yep, there were the three login attempts made. But no indication of an error in the process nor that they received the message saying the magic link the app sent them to failed.</p><p>This was going to require intuition and an educated guess.</p><p>The difference in the time that the token was created and the time it was used was only a dozen seconds. That seems quick, too quick.</p><p>An AI prompt later, the working theory was developed. Somehow, the token for the magic link was already used before my friend could click the link in the email. But, why?</p><p>AI had an idea. I didn&#8217;t, this was outside of any experience level. I&#8217;ve been around a lot of systems in my career, but authentication and authorization mechanisms was an area I didn&#8217;t spend a lot of time in.</p><p>The most likely cause was that an email security scanner/pre-fetch was loading the link in the email and because the way the /auth end point was written, it was validating the token with the pre-fetch which in turn told the system that the token was valid, and no longer needed and discarded.</p><p>That all happened between the email being delivered and the time my friend clicked the link. When they clicked the link, they received the unfriendly message that they couldn&#8217;t login.</p><p>And to add insult to injury, there was no way in the app for them to reach out and ask for assistance. Luckily, they had my number.</p><p>This was the theory, it was easily tested and confirmed.</p><p>The fix is simple, don&#8217;t validate the token when the /auth end point is called. Instead, put an intermediary step requiring the user to click the link in the email, load a new page with another button to select to finish the login process.</p><p>Yes, the solution adds a bit of friction to the process, there&#8217;s an extra click now. But, it eliminates any pre-fetch or inadvertent previewing of the page assuring that the token will work when the user intends it to work.</p><p>So, the embarrassment is real, I have no way of knowing how many other people have had the issue, more than I&#8217;m willing to admit to b/c I can see the similar usage patterns.</p><p>The embarrassment was replaced with gratitude quickly b/c someone took the time to say, something wasn&#8217;t right and brought it to my attention.</p><p>This isn&#8217;t about not trusting what AI builds. That&#8217;s too easy, and it sidesteps the real responsibility. I understood the design. What I didn&#8217;t have was the experience to know there was a gap in it. And building alone means there&#8217;s no one else around to catch what you don&#8217;t know to look for.</p><p>The counter to that is building a system that expects surprises and handles them gracefully. For me, that means an easy way for someone to reach me when something breaks, and auditing every critical process that could stop a user dead and hardening those first.</p><p>What tips or techniques do you build with to handle the unexpected surprises that will inevitably hit your system?</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://theweeklytinkerer.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading The Weekly Tinkerer! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>