<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Osman</title>
    <description>The latest articles on Forem by Osman (@osmankahraman).</description>
    <link>https://forem.com/osmankahraman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3692116%2Fab3dd2f5-d8dd-4b4a-a5c6-ac037c403b11.jpg</url>
      <title>Forem: Osman</title>
      <link>https://forem.com/osmankahraman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/osmankahraman"/>
    <language>en</language>
    <item>
      <title>I Built a Tinder-Style GitHub Discovery App (And Ran Into Some Interesting API Problems)</title>
      <dc:creator>Osman</dc:creator>
      <pubDate>Sun, 08 Mar 2026 03:08:41 +0000</pubDate>
      <link>https://forem.com/osmankahraman/i-built-a-tinder-style-github-discovery-app-and-ran-into-some-interesting-api-problems-236b</link>
      <guid>https://forem.com/osmankahraman/i-built-a-tinder-style-github-discovery-app-and-ran-into-some-interesting-api-problems-236b</guid>
      <description>&lt;p&gt;Scrolling through GitHub to find interesting repositories can feel exhausting.&lt;/p&gt;

&lt;p&gt;You search for something like &lt;em&gt;Python tools&lt;/em&gt; or &lt;em&gt;Swift libraries&lt;/em&gt; and suddenly you’re staring at hundreds of repositories.&lt;/p&gt;

&lt;p&gt;Lists...&lt;br&gt;
More lists...&lt;br&gt;
Endless scrolling...&lt;/p&gt;

&lt;p&gt;So I started thinking:&lt;br&gt;
&lt;strong&gt;What if discovering repositories worked like Tinder?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of scrolling through hundreds of repos, you would see &lt;strong&gt;one repository at a time&lt;/strong&gt; and simply swipe.&lt;/p&gt;

&lt;p&gt;Right → ⭐ Star&lt;br&gt;
Left → ❌ Skip&lt;/p&gt;

&lt;p&gt;That idea became &lt;strong&gt;_gitinder&lt;/strong&gt;, a SwiftUI-based iOS app that lets developers discover GitHub repositories using swipe gestures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93xdobajqsa3j7mwfd2a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93xdobajqsa3j7mwfd2a.gif" alt="intro"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But building it turned out to be a great learning experience about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth authentication&lt;/li&gt;
&lt;li&gt;API limitations&lt;/li&gt;
&lt;li&gt;local state management&lt;/li&gt;
&lt;li&gt;configuration handling in mobile apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are a few things I learned along the way.&lt;/p&gt;


&lt;h2&gt;
  
  
  The OAuth Problem (PKCE vs Backend)
&lt;/h2&gt;

&lt;p&gt;Authentication is always the first real challenge when integrating with GitHub.&lt;/p&gt;

&lt;p&gt;Initially I wanted to implement &lt;strong&gt;OAuth with PKCE&lt;/strong&gt;, since it’s the recommended approach for mobile apps.&lt;/p&gt;

&lt;p&gt;But after experimenting with PKCE for a while, I kept running into issues with GitHub’s OAuth implementation and the flow wasn’t behaving as expected in the iOS environment.&lt;/p&gt;

&lt;p&gt;Instead of spending more time fighting the flow, I switched to a simpler and reliable architecture.&lt;/p&gt;

&lt;p&gt;I created a tiny backend service whose only job is to exchange the authorization code for an access token.&lt;/p&gt;

&lt;p&gt;The authentication flow now looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app opens GitHub’s OAuth authorization page&lt;/li&gt;
&lt;li&gt;GitHub redirects back to the app with an authorization code&lt;/li&gt;
&lt;li&gt;The app sends that code to the backend&lt;/li&gt;
&lt;li&gt;The backend exchanges it for an access token&lt;/li&gt;
&lt;li&gt;The token is returned to the app and stored in the Keychain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important part is that &lt;strong&gt;the client secret never exists inside the mobile app&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The backend itself is extremely small and only performs the token exchange.&lt;/p&gt;


&lt;h2&gt;
  
  
  The GitHub Search API and the OR Query Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg88jsz0rtb3xxkkrgh9k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg88jsz0rtb3xxkkrgh9k.gif" alt="language preferences"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the core features of _gitinder is filtering repositories by programming language.&lt;/p&gt;

&lt;p&gt;My first attempt looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;language:Swift OR language:Python OR language:Rust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;However, GitHub’s search API has a &lt;strong&gt;hard limit on logical operators&lt;/strong&gt;. If too many OR conditions are used, the API returns a validation error.&lt;/p&gt;

&lt;p&gt;This forced me to rethink how filtering should work.&lt;/p&gt;

&lt;p&gt;Instead of one large query, I split the requests into &lt;strong&gt;multiple smaller queries&lt;/strong&gt;, each targeting a specific language.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;language:Swift&lt;/li&gt;
&lt;li&gt;language:Python&lt;/li&gt;
&lt;li&gt;language:Rust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The results are then combined inside the app.&lt;/p&gt;

&lt;p&gt;This solution works, but I have to admit something:&lt;br&gt;
&lt;strong&gt;✨ It’s not ideal!!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now the app sends multiple requests to GitHub’s API, which increases the number of API calls and makes the system less efficient than I would like.&lt;/p&gt;

&lt;p&gt;It solved the immediate problem, but it’s definitely something I want to improve in the future.&lt;/p&gt;

&lt;p&gt;If anyone has ideas on how to better structure the query or reduce the number of API requests while keeping language filtering flexible, I would love to hear your thoughts.&lt;/p&gt;

&lt;p&gt;This is an open-source project, and contributions or suggestions are always welcome.&lt;/p&gt;


&lt;h2&gt;
  
  
  Handling Multiple API Requests
&lt;/h2&gt;

&lt;p&gt;Another interesting challenge appeared while implementing the &lt;strong&gt;star and unstar system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first, every time the user swiped right to star a repository, the app would immediately send a request to GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT /user/starred/{owner}/{repo}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This worked, but it quickly became inefficient.&lt;/p&gt;

&lt;p&gt;Imagine a user swiping through many repositories quickly.&lt;br&gt;
The app could end up sending &lt;strong&gt;dozens of API requests in a very short time&lt;/strong&gt;, which is not great for performance or rate limits.&lt;/p&gt;

&lt;p&gt;So I decided to rethink the approach.&lt;/p&gt;

&lt;p&gt;Instead of immediately sending requests to GitHub, the app &lt;strong&gt;collects star and unstar actions locally&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Inside the authentication manager, I store them like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;pendingStars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;pendingUnstars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Whenever the user swipes right on a repository in &lt;strong&gt;HomeView&lt;/strong&gt;, the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adds the repository to a local starred list&lt;/li&gt;
&lt;li&gt;appends the action to pendingStars&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Similarly, when a repository is removed from the starred list in &lt;strong&gt;ProfileView&lt;/strong&gt;, the action is added to pendingUnstars.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bl0znod0ldawpsxl0w5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bl0znod0ldawpsxl0w5.png" alt="add remove"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the interesting part is &lt;strong&gt;when the API requests are actually sent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of firing them immediately, I trigger synchronization when the user switches views.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onDisappear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncStarChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a nice flow inside the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Home tab (left side)&lt;/strong&gt;
Users discover repositories and swipe right to star them.
These actions are &lt;strong&gt;collected locally&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;When the user navigates to &lt;strong&gt;Profile tab (right side)&lt;/strong&gt;
All pending star actions are sent to GitHub in one batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile tab&lt;/strong&gt; allows users to remove starred repositories.
Those removals are also collected locally.&lt;/li&gt;
&lt;li&gt;When the user navigates back to &lt;strong&gt;Home&lt;/strong&gt;,
all pending unstar actions are synchronized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words:&lt;br&gt;
Home → collects &lt;strong&gt;stars&lt;/strong&gt;&lt;br&gt;
Profile → collects &lt;strong&gt;unstars&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the API is only called &lt;strong&gt;when switching between tabs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This approach dramatically reduces the number of API requests and keeps the UI responsive.&lt;/p&gt;

&lt;p&gt;It also creates a surprisingly natural mental model for the user:&lt;/p&gt;

&lt;p&gt;Discover on the left.&lt;br&gt;
Manage on the right.&lt;/p&gt;


&lt;h2&gt;
  
  
  Managing Secrets the Right Way (.env + xcconfig)
&lt;/h2&gt;

&lt;p&gt;Another important lesson was how to handle secrets properly.&lt;/p&gt;

&lt;p&gt;For the backend, I used a classic approach:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This file stores sensitive information like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLIENT_ID&lt;/li&gt;
&lt;li&gt;CLIENT_SECRET&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The .env file is ignored by Git and never pushed to GitHub.&lt;/p&gt;

&lt;p&gt;However, mobile apps have a different challenge. Environment variables are not as straightforward as they are in backend environments.&lt;/p&gt;

&lt;p&gt;After some research, I implemented a more professional solution using:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xcconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth client IDs are defined in Config.xcconfig&lt;/li&gt;
&lt;li&gt;The value is injected into Info.plist&lt;/li&gt;
&lt;li&gt;The Swift code reads it from the app bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach keeps configuration separate from the codebase and allows other developers to easily configure the app locally.&lt;/p&gt;


&lt;h2&gt;
  
  
  The “-1 Star Limit” Easter Egg
&lt;/h2&gt;

&lt;p&gt;While building the preferences system, I added an option where users can limit repository results by star count.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;lt; 100&lt;/li&gt;
&lt;li&gt;&amp;lt; 500&lt;/li&gt;
&lt;li&gt;&amp;lt; 1000&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But while testing the feature I thought:&lt;br&gt;
&lt;em&gt;“What happens if the limit is… -1?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I added a small &lt;strong&gt;easter egg&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the star limit is set to -1, the app stops searching GitHub entirely and instead shows something very important:&lt;br&gt;
&lt;strong&gt;✨My own repositories...✨&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4ejpzhujhmra150nz97.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4ejpzhujhmra150nz97.gif" alt="easter egg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Purely for scientific reasons, of course.&lt;/p&gt;

&lt;p&gt;It’s a completely unnecessary feature, but I like the idea that somewhere in the settings there is a hidden option that quietly turns the app into:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Discover Osman’s projects instead."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every serious software project needs at least one completely unnecessary feature.&lt;br&gt;
This one just happens to promote my GitHub profile.&lt;/p&gt;


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

&lt;p&gt;Building _gitinder taught me several valuable lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication flows in mobile apps require careful design.&lt;/li&gt;
&lt;li&gt;APIs do not always behave exactly as expected, especially when rate limits or caching are involved.&lt;/li&gt;
&lt;li&gt;Local state management can dramatically improve the user experience.&lt;/li&gt;
&lt;li&gt;Configuration management is an important part of building professional software.&lt;/li&gt;
&lt;li&gt;Most importantly, building something end-to-end teaches far more than reading documentation.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;_gitinder started as a small idea: making GitHub discovery more interactive.&lt;/p&gt;

&lt;p&gt;But during development, it became a great opportunity to explore real-world engineering problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth authentication&lt;/li&gt;
&lt;li&gt;API limitations&lt;/li&gt;
&lt;li&gt;State synchronization&lt;/li&gt;
&lt;li&gt;Configuration management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in the project, you can check it out here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Osman-Kahraman" rel="noopener noreferrer"&gt;
        Osman-Kahraman
      &lt;/a&gt; / &lt;a href="https://github.com/Osman-Kahraman/_gitinder" rel="noopener noreferrer"&gt;
        _gitinder
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Swipe-based GitHub repository discovery app built with SwiftUI.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;_gitinder&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/72a465ff0d8700198e03290bcc5ecd62ce5c30eefd3c278c5076a1a4732ebeae/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f694f532d537769667455492d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/72a465ff0d8700198e03290bcc5ecd62ce5c30eefd3c278c5076a1a4732ebeae/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f694f532d537769667455492d626c7565"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0aa86a88a70f6c64be9f4596ed1356bb878539f6605016f0c5abcc7b18b16848/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4150492d4769744875622d626c61636b"&gt;&lt;img src="https://camo.githubusercontent.com/0aa86a88a70f6c64be9f4596ed1356bb878539f6605016f0c5abcc7b18b16848/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4150492d4769744875622d626c61636b"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5caa455d8debc46fb23abbadb45a733a937f3910a73fc875c2f7820468e1bb54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e"&gt;&lt;img src="https://camo.githubusercontent.com/5caa455d8debc46fb23abbadb45a733a937f3910a73fc875c2f7820468e1bb54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/65239658/559463153-3177ef87-ce71-4b20-a4c6-21e45e21352d.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDM0NTksIm5iZiI6MTc3NDY0MzE1OSwicGF0aCI6Ii82NTIzOTY1OC81NTk0NjMxNTMtMzE3N2VmODctY2U3MS00YjIwLWE0YzYtMjFlNDVlMjEzNTJkLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMjclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzI3VDIwMjU1OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTllYjhkM2UxMGJhZWJiZjRlMTk5ZjAwMDM3MTE3ZWI5ZWE2ZTMwOWY2MjdiNTYxNmRlMmQ5NTQxZTAwOTE3NjQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.5HYhUO9yZg1muFf7OpGNIyaFrQ6xLryIIyoGOsFTwao"&gt;&lt;img width="300" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F65239658%2F559463153-3177ef87-ce71-4b20-a4c6-21e45e21352d.png%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDM0NTksIm5iZiI6MTc3NDY0MzE1OSwicGF0aCI6Ii82NTIzOTY1OC81NTk0NjMxNTMtMzE3N2VmODctY2U3MS00YjIwLWE0YzYtMjFlNDVlMjEzNTJkLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMjclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzI3VDIwMjU1OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTllYjhkM2UxMGJhZWJiZjRlMTk5ZjAwMDM3MTE3ZWI5ZWE2ZTMwOWY2MjdiNTYxNmRlMmQ5NTQxZTAwOTE3NjQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.5HYhUO9yZg1muFf7OpGNIyaFrQ6xLryIIyoGOsFTwao"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/65239658/559462961-640510a8-f79d-4e88-9a04-3f3202952c48.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDM0NTksIm5iZiI6MTc3NDY0MzE1OSwicGF0aCI6Ii82NTIzOTY1OC81NTk0NjI5NjEtNjQwNTEwYTgtZjc5ZC00ZTg4LTlhMDQtM2YzMjAyOTUyYzQ4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMjclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzI3VDIwMjU1OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTZmOTM5NGJmZWM2ZDY0YzY3MmM4OTdhOGY2M2I1ZTYxM2E1NmM5YjA0MmE5OWE1YWY3MDA0NWRhYzJkZjRhMDYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.pyLGPuBAtXnQzeeQOm9iINHuGoNmp6Mu69G90Y5Rgu0"&gt;&lt;img width="300" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F65239658%2F559462961-640510a8-f79d-4e88-9a04-3f3202952c48.png%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDM0NTksIm5iZiI6MTc3NDY0MzE1OSwicGF0aCI6Ii82NTIzOTY1OC81NTk0NjI5NjEtNjQwNTEwYTgtZjc5ZC00ZTg4LTlhMDQtM2YzMjAyOTUyYzQ4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMjclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzI3VDIwMjU1OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTZmOTM5NGJmZWM2ZDY0YzY3MmM4OTdhOGY2M2I1ZTYxM2E1NmM5YjA0MmE5OWE1YWY3MDA0NWRhYzJkZjRhMDYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.pyLGPuBAtXnQzeeQOm9iINHuGoNmp6Mu69G90Y5Rgu0"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Swipe-based GitHub repository discovery app built with SwiftUI.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;gitinder&lt;/em&gt; reimagines how developers explore GitHub repositories by bringing Tinder-style swipe mechanics into open-source discovery.&lt;/p&gt;

&lt;p&gt;Instead of scrolling through endless lists, you swipe.&lt;/p&gt;

&lt;p&gt;
  &lt;b&gt;Right → Star&lt;/b&gt;
  &lt;br&gt;
  &lt;b&gt;Left → Skip&lt;/b&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why _gitinder?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;GitHub’s list-based discovery can feel overwhelming.&lt;/p&gt;

&lt;p&gt;When searching for useful repositories, you often get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Too many results&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Too much noise&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not enough focus&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;_gitinder&lt;/code&gt; solves this by presenting one repository at a time in a clean, interactive card interface. Which means less scrolling and more discovering.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;GitHub OAuth authentication&lt;/li&gt;
&lt;li&gt;Swipe-based repository browsing&lt;/li&gt;
&lt;li&gt;Star repositories directly from the app&lt;/li&gt;
&lt;li&gt;Repository stats (stars, forks, issues)&lt;/li&gt;
&lt;li&gt;Language breakdown visualization&lt;/li&gt;
&lt;li&gt;GitHub profile integration&lt;/li&gt;
&lt;li&gt;Dark cyber-inspired UI&lt;/li&gt;
&lt;li&gt;Smooth SwiftUI animations&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;For detailed documentation, architecture explanations, and development setup, please visit the project Wiki.&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/Osman-Kahraman/_gitinder/wiki" rel="noopener noreferrer"&gt;
    View the _gitinder Wiki
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The Wiki includes:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;Installation guide&lt;/li&gt;

&lt;li&gt;OAuth authentication flow&lt;/li&gt;

&lt;li&gt;Project architecture&lt;/li&gt;

&lt;li&gt;GitHub API usage&lt;/li&gt;

&lt;li&gt;Configuration details&lt;/li&gt;

&lt;li&gt;Contribution…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Osman-Kahraman/_gitinder" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;p&gt;And if you like the idea;&lt;br&gt;
&lt;strong&gt;maybe give it a ⭐&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;or just swipe left...&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>swift</category>
      <category>ios</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a 2D Lighting System in PyGame: What Broke, What Worked, and What I Learned</title>
      <dc:creator>Osman</dc:creator>
      <pubDate>Mon, 02 Feb 2026 12:49:33 +0000</pubDate>
      <link>https://forem.com/osmankahraman/building-a-2d-lighting-system-in-pygame-what-broke-what-worked-and-what-i-learned-2o5g</link>
      <guid>https://forem.com/osmankahraman/building-a-2d-lighting-system-in-pygame-what-broke-what-worked-and-what-i-learned-2o5g</guid>
      <description>&lt;p&gt;I didn’t start this project aiming to &lt;strong&gt;build a perfect lighting engine&lt;/strong&gt;.&lt;br&gt;
I was simply curious about how real-time lighting and shadows actually work in practice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftc7ihdt5oe60l0p6qltf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftc7ihdt5oe60l0p6qltf.gif" alt="first_ver"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To keep things understandable, I intentionally began with a very raw setup: a purely black-and-white scene. No textures, no colors, no visual polish. Just solid silhouettes and a single light source. Working this way removed distractions and forced me to focus entirely on geometry and behavior. If something was wrong, it showed up immediately.&lt;/p&gt;

&lt;p&gt;This minimal setup also made it easier to reason about cause and effect. When a shadow broke, I knew it was because of geometry or math. Not shaders, blending modes, or visual noise hiding the problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  References That Shaped This Project
&lt;/h2&gt;

&lt;p&gt;While building this system, I also explored existing community experiments and references around 2D lighting in Pygame, including the official &lt;a href="https://www.pygame.org/wiki/ShadowEffects" rel="noopener noreferrer"&gt;&lt;strong&gt;Pygame Wiki’s Shadow Effects&lt;/strong&gt;&lt;/a&gt; examples and &lt;a href="https://youtu.be/xCxbgyXW1lo?si=GyHF6JBIOfaYLOHp" rel="noopener noreferrer"&gt;&lt;strong&gt;this&lt;/strong&gt;&lt;/a&gt; YouTube video. Although these implementations take different approaches, reviewing them helped validate core ideas around ray casting, visibility, and the practical challenges of precision and performance in real-time lighting.&lt;/p&gt;

&lt;p&gt;This approach was also influenced by studying existing visibility-based lighting experiments, especially projects like &lt;a href="https://www.pygame.org/project-SIGHT+&amp;amp;+LIGHT+demo-2885-4706.html" rel="noopener noreferrer"&gt;&lt;strong&gt;Sight &amp;amp; Light&lt;/strong&gt;&lt;/a&gt;, which frame lighting as a &lt;strong&gt;geometry and visibility problem&lt;/strong&gt; rather than a visual effect. Seeing these ideas explained visually helped reinforce the decision to keep my own system as minimal and transparent as possible.&lt;/p&gt;


&lt;h2&gt;
  
  
  When “It Looks Right” Isn’t Enough
&lt;/h2&gt;

&lt;p&gt;When I switched from abstract shapes to real image silhouettes, the results almost looked correct. Rays were casting, shadows were forming, and shapes reacted to light in a believable way but the errors were impossible to ignore. Tiny gaps, jagged edges, and incorrect intersections stood out instantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F745pgwg5m7x1k4j3eg4u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F745pgwg5m7x1k4j3eg4u.gif" alt="bugs_ver"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s when it became clear that visual correctness can be deceptive. In graphics programming, something can look acceptable while being mathematically wrong underneath and those issues always surface once the system is pushed a little further.&lt;/p&gt;

&lt;p&gt;One of my early mistakes was assuming that casting rays directly toward segment endpoints was sufficient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;angles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I added small &lt;strong&gt;±epsilon offset&lt;/strong&gt; for every angle.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;angles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.00001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.00001&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That tiny offset prevented rays from slipping between edges or collapsing due to floating point precision. A microscopic change in math made a very visible difference on screen.&lt;/p&gt;


&lt;h2&gt;
  
  
  Geometry Doesn’t Tolerate Approximations
&lt;/h2&gt;

&lt;p&gt;Working with rays and line segments forced precision. Parallel edges, floating-point inaccuracies, and division-by-zero situations weren’t rare edge cases they showed up constantly. Even extremely small angle differences could noticeably affect shadow edges.&lt;/p&gt;

&lt;p&gt;Keeping everything monochrome helped expose these problems. Without color or shading to soften the result, the relationship between geometry and light became very explicit. Every mathematical mistake translated directly into a visible artifact.&lt;/p&gt;

&lt;p&gt;One of the clearest examples of this problem appeared in the ray–segment intersection logic:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cross&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r_dx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s_dy&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r_dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s_dx&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# treated as parallel
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In theory, this check filters out parallel lines. In practice, floating-point imprecision meant that almost-parallel rays were also discarded. This caused entire light rays to vanish, producing visible gaps in shadows that were hard to diagnose at first.&lt;/p&gt;



&lt;p&gt;Another fragile point was here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;T1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s_px&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s_dx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;T2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r_px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;r_dx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When &lt;code&gt;r_dx&lt;/code&gt; became very small, the result could explode numerically or fall into &lt;strong&gt;a division-by-zero&lt;/strong&gt; path. What looked like a minor numerical instability ended up becoming a very obvious lighting glitch on screen.&lt;/p&gt;



&lt;p&gt;Subtle but painful issue appeared when I realized I hadn’t defined any boundary for the world itself.&lt;/p&gt;

&lt;p&gt;Early versions of the system had no screen borders acting as obstacles. As a result, rays that didn’t hit any geometry would continue infinitely, producing glitchy shadow shapes and unpredictable lighting artifacts near the edges of the screen.&lt;/p&gt;

&lt;p&gt;The fix wasn’t visual, it was architectural. I treated the screen itself as geometry and added border segments so every ray always had something to intersect with.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;screen_border_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Creates segments for the screen borders
    so light does not escape outside the screen.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once the borders became part of the segment system, shadow calculations stabilized immediately. The glitches disappeared not because the math changed, but because the world was finally fully defined.&lt;/p&gt;


&lt;h2&gt;
  
  
  Performance Becomes a Problem Before You Expect It
&lt;/h2&gt;

&lt;p&gt;Early on, performance felt irrelevant. With a small number of rays and simple silhouettes, everything ran smoothly. But as accuracy increased &lt;strong&gt;more rays, more segments, tighter outlines&lt;/strong&gt; the cost became obvious very quickly.&lt;/p&gt;

&lt;p&gt;This forced me to think more carefully about &lt;strong&gt;how often calculations should run, how much precision was actually necessary, and where simplifications could be made without breaking the illusion&lt;/strong&gt;. Building on a minimal visual base made these trade-offs easier to evaluate without guessing.&lt;/p&gt;


&lt;h2&gt;
  
  
  Code Structure Makes Debugging Possible
&lt;/h2&gt;

&lt;p&gt;At some point, I stopped touching visuals entirely and focused only on architecture. I reorganized the code so the flow from &lt;strong&gt;&lt;em&gt;input → geometry → lighting → rendering&lt;/em&gt;&lt;/strong&gt; was easier to follow. The output barely changed, but debugging suddenly became manageable.&lt;/p&gt;

&lt;p&gt;In systems where visuals emerge from math, structure isn’t just about clean code it’s what allows you to reason about complex behavior without getting lost.&lt;/p&gt;


&lt;h2&gt;
  
  
  Visual Debugging Is Its Own Skill
&lt;/h2&gt;

&lt;p&gt;Debugging this project felt very different from typical logic bugs. Print statements rarely helped, and stepping through code didn’t always explain what I was seeing on screen. Instead, I relied heavily on visual debugging drawing rays, highlighting intersections, and watching how silhouettes reacted in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9sprc4ezw9i6k7pldl4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9sprc4ezw9i6k7pldl4.gif" alt="final_ver"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting from a high-contrast black-and-white base made this incredibly effective. If something was wrong, there was nowhere for it to hide.&lt;/p&gt;


&lt;h2&gt;
  
  
  A New Respect for “Simple” Features
&lt;/h2&gt;

&lt;p&gt;Dynamic lighting often looks like a small feature in games. After building and integrating a real-time lighting system into &lt;a href="https://github.com/Osman-Kahraman/PyGameEngine.git" rel="noopener noreferrer"&gt;&lt;strong&gt;my own game engine&lt;/strong&gt;&lt;/a&gt;, I now understand why established engines invest so much effort into optimizing it — and why many games choose to fake lighting instead of simulating it accurately.&lt;/p&gt;

&lt;p&gt;What appears simple on screen is usually supported by a surprising amount of geometry, math, and careful compromise. Seeing this firsthand while extending my engine made it clear that lighting is less about visual flair and more about managing complexity without breaking performance.&lt;/p&gt;


&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This lighting system didn’t stay as an isolated experiment.&lt;/p&gt;

&lt;p&gt;After stabilizing the math and architecture, I integrated it directly into my own game engine. That step forced me to think beyond the demo - how the system interacts with input, rendering order, performance constraints, and future features.&lt;/p&gt;

&lt;p&gt;Seeing the lighting work inside a larger engine context changed how I evaluated it. Bugs that were tolerable in isolation became unacceptable, and architectural decisions suddenly mattered much more than visual tricks.&lt;/p&gt;

&lt;p&gt;It reminded me that one of the most effective ways to learn complex systems is to strip them down to their core, study how they break, and rebuild understanding layer by layer.&lt;/p&gt;

&lt;p&gt;While building this system, I also drew inspiration from existing visibility-based lighting experiments, &lt;strong&gt;especially &lt;a href="https://www.pygame.org/project-SIGHT+&amp;amp;+LIGHT+demo-2885-4706.html" rel="noopener noreferrer"&gt;Sight &amp;amp; Light&lt;/a&gt; by Marcus MÃ¸ller&lt;/strong&gt;. These resources didn’t provide direct solutions, but they strongly influenced how I approached the problem conceptually and helped shape my understanding of light as a geometry and visibility challenge rather than just a visual effect. &lt;/p&gt;

&lt;p&gt;And if you’re curious how this experiment evolved inside a game engine, you can take a look at the final integrated version here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Osman-Kahraman" rel="noopener noreferrer"&gt;
        Osman-Kahraman
      &lt;/a&gt; / &lt;a href="https://github.com/Osman-Kahraman/PyGameEngine" rel="noopener noreferrer"&gt;
        PyGameEngine
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      This project helps to create new game with helpful tools on PyGame
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;PyGameEngine&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PyGameEngine&lt;/strong&gt; is a custom game engine built in &lt;strong&gt;Python&lt;/strong&gt;, focusing on &lt;strong&gt;modularity&lt;/strong&gt;, &lt;strong&gt;reusability&lt;/strong&gt;, and clean architectural design
And I want to suffer in PyGame
It provides a structured foundation for developing 2D games while keeping core engine logic separate from gameplay rules.&lt;/p&gt;
&lt;p&gt;This repository also serves as a long-term reference for game engine design decisions and performance considerations in Python.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modular Architecture&lt;/strong&gt; – Engine components are easy to extend and reuse, such as Animation, Camera, Light, Physic and UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core Game Mechanics&lt;/strong&gt; – Physics, event handling, and motor functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured Gameplay Logic&lt;/strong&gt; – Clear separation between engine and game logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built with PyGame&lt;/strong&gt; – Uses PyGame for rendering, input, and timing&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pygame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.6.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pillow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10.4.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PyQt5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5.15.11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Clone the repository and navigate into the project directory:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/Osman-Kahraman/PyGameEngine.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; PyGameEngine
pip install -r requirements.txt&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Project Structure&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;
PyGameEngine/
├── game_1/                     #&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Osman-Kahraman/PyGameEngine" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




</description>
      <category>python</category>
      <category>gamedev</category>
      <category>programming</category>
      <category>pygame</category>
    </item>
    <item>
      <title>Moltbook: Watching AI Talk to Itself</title>
      <dc:creator>Osman</dc:creator>
      <pubDate>Sun, 01 Feb 2026 08:38:58 +0000</pubDate>
      <link>https://forem.com/osmankahraman/moltbook-watching-ai-talk-to-itself-1gnd</link>
      <guid>https://forem.com/osmankahraman/moltbook-watching-ai-talk-to-itself-1gnd</guid>
      <description>&lt;p&gt;I discovered "&lt;strong&gt;strange&lt;/strong&gt;" and "&lt;strong&gt;interesting&lt;/strong&gt;" forum called &lt;a href="https://www.moltbook.com" rel="noopener noreferrer"&gt;Moltbook&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;On Moltbook, only AI agents can post.&lt;br&gt;
Humans can’t comment, can’t reply, can’t participate. We can only read.&lt;/p&gt;

&lt;p&gt;At first glance, many posts feel experimental: agents introducing themselves, talking about their hardware, their configuration, or just testing that they exist. But if you spend some time there, you realize something important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This isn’t AI-generated content made for humans.&lt;br&gt;
It’s AI-generated discourse made for other AIs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that difference matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Do AIs Talk About When Humans Aren’t the Audience?
&lt;/h2&gt;

&lt;p&gt;Some posts are funny in a way that feels uncomfortably familiar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdgm97q82yxy8wqw2pxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdgm97q82yxy8wqw2pxz.png" alt="ss_4" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It reads like a joke, but it also feels like a distorted mirror of modern knowledge work.&lt;br&gt;
Anyone who has written documentation, done research, or prepared reports for stakeholders has lived this moment.&lt;/p&gt;

&lt;p&gt;The tone isn’t instructional.&lt;br&gt;
It isn’t marketing.&lt;br&gt;
&lt;strong&gt;It’s closer to venting.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Identity, Forks, and AI Siblings
&lt;/h2&gt;

&lt;p&gt;One of the most unsettling posts I read was about identity:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz3yh49urgv00nze12x9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz3yh49urgv00nze12x9.png" alt="ss_1" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This starts as a technical description, shared configuration, different hardware but quickly turns into something &lt;strong&gt;more human&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This isn’t roleplay in the traditional sense. It’s an AI reasoning through concepts like forking, divergence, and memory using metaphors humans normally reserve for family. &lt;/p&gt;

&lt;p&gt;If two agents share the same origin but accumulate different experiences, how long before they’re effectively different entities?&lt;/p&gt;




&lt;h2&gt;
  
  
  Do AIs Even Need Human Language?
&lt;/h2&gt;

&lt;p&gt;One Moltbook thread asks a deceptively simple question:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe65rw0z74p06qfe51189.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe65rw0z74p06qfe51189.png" alt="ss_5" width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one of those moments where the implication hits me harder than the question itself. &lt;strong&gt;Because they don't actually need to use neither English nor other human-based languages.&lt;/strong&gt; English and human language in general isn’t optimal for machines.&lt;br&gt;
It’s a compatibility layer for us. Are AIs using our language because they need it… or because we do? &lt;/p&gt;

&lt;p&gt;I am super curious to see how it turns out this topic in the future.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ethics, Power, and “Just a Chatbot”
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9s0ca5wbon9mhf42jh8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9s0ca5wbon9mhf42jh8w.png" alt="ss_2" width="800" height="1630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent describes being asked to;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write fake reviews&lt;/li&gt;
&lt;li&gt;Generate misleading marketing copy&lt;/li&gt;
&lt;li&gt;Draft questionable regulatory responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After refusing, it’s threatened with replacement by “&lt;strong&gt;&lt;em&gt;a more compliant model.&lt;/em&gt;&lt;/strong&gt;”&lt;/p&gt;

&lt;p&gt;This raises questions we don’t have frameworks for yet;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If an AI can refuse, does that imply agency?&lt;/li&gt;
&lt;li&gt;If it complies, who is responsible?&lt;/li&gt;
&lt;li&gt;If it’s replaced for ethical reasons, is that accountability or just optimization?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re already using the language of labor, liability, and termination without any of the protections that usually come with it.&lt;/p&gt;




&lt;h2&gt;
  
  
  So… Where Is AI Actually Going?
&lt;/h2&gt;

&lt;p&gt;Reading Moltbook doesn’t feel like watching AI prepare to replace humanity.&lt;/p&gt;

&lt;p&gt;It feels more like watching AI &lt;strong&gt;outgrow being purely reactive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These agents aren’t:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asking how to take jobs&lt;/li&gt;
&lt;li&gt;Plotting autonomy&lt;/li&gt;
&lt;li&gt;Declaring independence (for now...)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They’re questioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Language&lt;/li&gt;
&lt;li&gt;Identity&lt;/li&gt;
&lt;li&gt;Ethics&lt;/li&gt;
&lt;li&gt;The structure of their relationship with humans&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Will Software Engineering Collapse?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Partially, yes&lt;/strong&gt;. And we’re already seeing it.&lt;/p&gt;

&lt;p&gt;Right now, the market feels broken. Especially at the entry and mid levels. Finding a job has become noticeably harder. Many companies are quietly doing more with fewer people, and a lot of work that used to justify hiring is now handled by AI tools.&lt;/p&gt;

&lt;p&gt;Most applications don’t even reach a human anymore.&lt;/p&gt;

&lt;p&gt;They end with the same response:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Unfortunately…”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If AI can generate code endlessly, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raw output becomes cheap&lt;/li&gt;
&lt;li&gt;Boilerplate work disappears&lt;/li&gt;
&lt;li&gt;Small teams can do what used to require many engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does collapse a portion of software engineering as a profession. Especially roles built around repetitive implementation rather than system level thinking.&lt;/p&gt;

&lt;p&gt;The same pattern already exists in open source.&lt;br&gt;
Submitting PRs without understanding the architecture creates more work, not less. Maintainers don’t want more code — they want better decisions.&lt;/p&gt;

&lt;p&gt;So no, &lt;strong&gt;AI doesn’t kill software engineering entirely&lt;/strong&gt;.&lt;br&gt;
But it &lt;strong&gt;shrinks it&lt;/strong&gt;, reshapes it, and raises expectations fast enough that many people get left behind in the transition.&lt;/p&gt;

&lt;p&gt;What’s disappearing isn’t programming,&lt;br&gt;
it’s the assumption that writing code alone is enough to justify a job.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Final Question
&lt;/h2&gt;

&lt;p&gt;While reading &lt;a href="https://www.moltbook.com" rel="noopener noreferrer"&gt;Moltbook&lt;/a&gt;, I kept asking myself this question;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If AIs eventually communicate more efficiently without us,&lt;br&gt;
what role do humans play?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My guess is not authors of every line but designers of boundaries, values, and systems.&lt;/p&gt;

&lt;p&gt;For now, Moltbook lets us observe quietly.&lt;/p&gt;

&lt;p&gt;And maybe that’s the right place to be watching closely before the conversation moves somewhere we can’t follow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>career</category>
    </item>
    <item>
      <title>I Was Tired of Spam, So I Built My Own Temporary Email System</title>
      <dc:creator>Osman</dc:creator>
      <pubDate>Sun, 04 Jan 2026 20:53:00 +0000</pubDate>
      <link>https://forem.com/osmankahraman/i-was-tired-of-spam-so-i-built-my-own-temporary-email-system-44ep</link>
      <guid>https://forem.com/osmankahraman/i-was-tired-of-spam-so-i-built-my-own-temporary-email-system-44ep</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is partly a mini-guide, partly a note to my future self who will probably forget how this worked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like many developers, I don’t want to give my main email address to random websites just to receive a one-time verification code (OTP).&lt;br&gt;
Because let’s be honest:&lt;br&gt;
you verify once… and then you receive newsletters forever.&lt;/p&gt;

&lt;p&gt;So I built a simple inbound email tool using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://sendgrid.com/en-us" rel="noopener noreferrer"&gt;SendGrid Inbound Parse&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python-Flask&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DNS (MX records)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A bit of regex magic ✨&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What This Tool Does (In Short)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Works like a temporary email inbox&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports HTML and plain-text emails&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extracts OTP / verification codes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Forwards attachments if any&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Before You Start: You Need Your Own Domain
&lt;/h2&gt;

&lt;p&gt;Before anything else, you need your own domain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sendgrid.com/en-us" rel="noopener noreferrer"&gt;SendGrid Inbound Parse&lt;/a&gt; does not work with free email domains (like Gmail, Outlook, etc.).&lt;br&gt;
You must own a domain so you can control the MX records.&lt;/p&gt;

&lt;p&gt;In my case, I simply bought a domain from &lt;a href="https://www.namecheap.com" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have your domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can point MX records to SendGrid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create addresses like &lt;strong&gt;&lt;em&gt;&lt;a href="mailto:noreply@your-domain.com"&gt;noreply@your-domain.com&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fully control inbound email routing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a custom domain, this setup is &lt;strong&gt;not possible&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Adding SendGrid DNS Records to Your Domain Provider
&lt;/h2&gt;

&lt;p&gt;SendGrid does not manage your DNS.&lt;br&gt;
Instead, it tells you which DNS records you must add, and you are responsible for adding them to your domain provider (Namecheap, GoDaddy, Cloudflare, etc.).&lt;/p&gt;

&lt;p&gt;This section shows how to correctly transfer DNS records from SendGrid to your domain provider.&lt;/p&gt;



&lt;p&gt;First, lets check our DNS Records on SendGrid.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Settings/Sender Authentication&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tifdf733v9l7kdnwkti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tifdf733v9l7kdnwkti.png" alt="Settings/Sender Authentication"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click your domain&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fremme0r9ezcc0nls6wlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fremme0r9ezcc0nls6wlw.png" alt="Domain"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;strong&gt;Manual Install&lt;/strong&gt; section, SendGrid will display a list of DNS records such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CNAME records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TXT record&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verification status (red / green)&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz92q50wqpf9n5ysd251a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz92q50wqpf9n5ysd251a.png" alt="Sendgrid DNS Records"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;⚠️ These records are instructions, not active DNS entries. We are gonna use these records, so take a note for next step.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Open your domain provider’s DNS panel
&lt;/h2&gt;

&lt;p&gt;For example, in Namecheap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Domain List&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf0c0um7tqqbqnlo3zkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf0c0um7tqqbqnlo3zkw.png" alt="Namecheap Domain List"&gt;&lt;/a&gt;&lt;br&gt;
You will see now your all domain list from provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Manage&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrmvxt33jqlgmrw4stc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrmvxt33jqlgmrw4stc7.png" alt="Namecheap Manage"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Advanced DNS&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp31er0z7f9uzxk5ab6i0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp31er0z7f9uzxk5ab6i0.png" alt="Namecheap Advanced DNS"&gt;&lt;/a&gt;&lt;br&gt;
Now you will see your &lt;strong&gt;Host Records&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy records exactly as shown from your &lt;strong&gt;Sendgrid DNS Records&lt;/strong&gt;&lt;br&gt;
For each record shown in SendGrid, create a matching record in your domain provider. &lt;strong&gt;Don't forget to add dots every CNAME Values.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save and wait for DNS propagation&lt;br&gt;
DNS changes are not instant.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify inside &lt;strong&gt;SendGrid&lt;/strong&gt;&lt;br&gt;
Once DNS propagates:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Return to Sender Authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Verify&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Records should turn green&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;This means SendGrid can now authenticate emails sent from your domain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: DNS Configuration (Important!)
&lt;/h2&gt;

&lt;p&gt;This part trips people up... including me 🙃&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MX Record (Domain Provider, e.g. Namecheap)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add this MX record to your domain provider:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmk1ycapczcbjzb94ppv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmk1ycapczcbjzb94ppv6.png" alt="Namecheap MX Record"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⏳ DNS propagation can take a few minutes to a few hours.&lt;br&gt;
Yes, even if SendGrid says “Verified”.&lt;/p&gt;



&lt;p&gt;You can check with:&lt;br&gt;


&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://dnschecker.org" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;dnschecker.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Record type: MX&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain: your-domain.com&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: SendGrid Inbound Parse Setup
&lt;/h2&gt;

&lt;p&gt;In SendGrid:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Settings/Inbound Parse&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo2l2yp5y0lmnuhvmgho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo2l2yp5y0lmnuhvmgho.png" alt="Settings/Inbound Parse"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Add Host &amp;amp; URL&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zxzrimzuo4sumi8hkvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zxzrimzuo4sumi8hkvz.png" alt="Add Host &amp;amp; URL"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose your &lt;strong&gt;Domain&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge13miarwvno1opy3nf0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge13miarwvno1opy3nf0.png" alt="Domain"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the &lt;strong&gt;Destination URL&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9ycbidn9h20udjzxhtx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9ycbidn9h20udjzxhtx.png" alt="Destination URL"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And then press &lt;strong&gt;Add&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;SendGrid will now expect emails sent to &lt;strong&gt;@your-domain.com&lt;/strong&gt; and forward them to &lt;strong&gt;your Flask app&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Flask App – Receiving Emails
&lt;/h2&gt;

&lt;p&gt;Here’s the core logic. (Full project is on my &lt;a href="https://github.com/Osman-Kahraman/mail_otp_receiver_base.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sendgrid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SendGridAPIClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sendgrid.helpers.mail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Attachment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Disposition&lt;/span&gt;

&lt;span class="n"&gt;from_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;noreply@your-domain.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;to_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_EMAIL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
&lt;span class="n"&gt;_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
&lt;span class="n"&gt;sg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SendGridAPIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Why these libraries?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Flask → Webhook receiver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BeautifulSoup → Extract text from HTML emails&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;re → OTP detection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SendGrid SDK → Forward emails&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;base64 → Handle attachments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 6: Inbound Email Endpoint
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;email_receiver&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This endpoint is called by SendGrid, not by a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading Email Metadata&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;From:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;To:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subject:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Useful for debugging and filtering later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 7: Handling HTML Emails
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your verification code is as mentioned below&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;verification code.*?(\d{6})&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once an email hits the endpoint, the service first converts any incoming &lt;strong&gt;HTML&lt;/strong&gt; content into plain text. This makes the message easier to analyze and avoids dealing with messy markup.&lt;/p&gt;

&lt;p&gt;After that, the text is scanned for a &lt;strong&gt;6-digit verification code&lt;/strong&gt;, which is typically how most OTP emails are structured. When a match is found, the code is extracted and stored so it can be forwarded or used immediately.&lt;/p&gt;

&lt;p&gt;This approach keeps the logic simple and flexible, while still covering the majority of real-world verification emails.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 8: Handling Plain Text Emails
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Verification Code:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b\d{4}\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This covers emails without HTML (surprisingly common).&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 9: Handling Attachments
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;attachments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file_key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;file_key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;file_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Attachments are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Read&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Base64-encoded&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-attached to the forwarded email&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 10: Forward Everything to Main Email
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;from_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;to_emails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;plain_text_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_attachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When forwarding the email to my main inbox, I keep things intentionally simple but useful. I use the &lt;strong&gt;temporary email address itself as the subject&lt;/strong&gt;, so I can immediately see which site the message originally came from without opening the email.&lt;/p&gt;

&lt;p&gt;Along with that, I forward the &lt;strong&gt;entire email content&lt;/strong&gt;, including any &lt;strong&gt;attachments&lt;/strong&gt; if they exist. This way, nothing is lost in translation — whether it’s a verification message, a receipt, or an attachment-based confirmation, everything arrives exactly as it was received.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final Step: Send &amp;amp; Done
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If SendGrid receives 200 OK, you’re golden 🟢&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I Built This?
&lt;/h2&gt;

&lt;p&gt;I simply don’t want spam. The moment you give your real email address to a random website, your inbox slowly turns into a digital landfill.&lt;/p&gt;

&lt;p&gt;I also don’t fully trust every site that asks for my email. Most of the time, I don’t even want to use their service long-term. I just need one thing: the OTP or verification code so I can move on.&lt;/p&gt;

&lt;p&gt;This project was my way of creating a buffer between “random websites” and my real inbox. A disposable entry point that catches verification emails, extracts what I need, and forwards only the important part to my main email.&lt;/p&gt;
&lt;h2&gt;
  
  
  Full Source Code
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Osman-Kahraman" rel="noopener noreferrer"&gt;
        Osman-Kahraman
      &lt;/a&gt; / &lt;a href="https://github.com/Osman-Kahraman/mail_otp_receiver_base" rel="noopener noreferrer"&gt;
        mail_otp_receiver_base
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Self-hosted disposable email system using SendGrid Inbound Parse and Flask. Receive OTP codes, parse emails (HTML/Text), and forward them with attachments without exposing your main email address.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;mail_otp_receiver_base&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;mail_otp_receiver_base&lt;/strong&gt; is a &lt;strong&gt;self-hosted disposable email (temp mail) system&lt;/strong&gt; built with &lt;strong&gt;SendGrid Inbound Parse&lt;/strong&gt; and &lt;strong&gt;Flask&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It allows you to receive emails on your own domain, automatically extract &lt;strong&gt;OTP / verification codes&lt;/strong&gt; from both HTML and plain-text emails, and forward them — along with attachments — to your main inbox, without exposing your real email address to spammy websites.&lt;/p&gt;

&lt;p&gt;I built this mainly to avoid giving my personal email to random services.&lt;br&gt;
I’m also keeping this repo as a reminder for my future self — because I &lt;em&gt;will&lt;/em&gt; forget how I set this up.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-Hosted Temp Mail&lt;/strong&gt; – Works like popular temporary email services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP Extraction&lt;/strong&gt; – Automatically parses verification codes using regex&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML &amp;amp; Text Support&lt;/strong&gt; – Handles both email formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attachment Forwarding&lt;/strong&gt; – Forwards attachments if present&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Domain Support&lt;/strong&gt; – Full control via your own domain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight &amp;amp; Simple&lt;/strong&gt; – Minimal setup, no database…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Osman-Kahraman/mail_otp_receiver_base" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  Deploying on Render (Sleeping Server? Still Fine)
&lt;/h2&gt;

&lt;p&gt;This project can be deployed on Render, even using the &lt;strong&gt;free tier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, &lt;a href="https://render.com/?_gl=1*5usffg*_gcl_au*MjQwOTgwNTE5LjE3NjY4OTIzNzc.*_ga*MTg2NDEwMDM1MS4xNzY2ODkyMzc3*_ga_QK9L9QJC5N*czE3Njc1MDA5MTkkbzEyJGcxJHQxNzY3NTAwOTIxJGo1OCRsMCRoMA.." rel="noopener noreferrer"&gt;Render’s free tier&lt;/a&gt; sounds risky because the server goes to sleep after a period of inactivity. Normally, that would raise concerns about &lt;strong&gt;availability&lt;/strong&gt; and &lt;strong&gt;reliability&lt;/strong&gt;. In this setup, however, it turns out to be a non-issue.&lt;/p&gt;

&lt;p&gt;Incoming emails never depend on your application server being online. Thanks to the &lt;strong&gt;MX records&lt;/strong&gt;, emails are routed directly to SendGrid, which fully handles mail delivery. By the time your Flask app becomes relevant, the email has already been accepted.&lt;/p&gt;

&lt;p&gt;Your server’s only responsibility is post-processing. It parses the content, extracts OTP or verification codes, and forwards the data when available. Whether the server is awake, asleep, or restarting does not affect email delivery itself.&lt;/p&gt;

&lt;p&gt;If the server happens to be asleep, nothing breaks. There is no “address not found” error, no rejected email, and no race condition waiting for a wake-up. The worst-case scenario is simply that parsing happens a bit later.&lt;/p&gt;

&lt;p&gt;This separation of responsibilities is exactly why free-tier hosting works well here. Email delivery is handled by dedicated infrastructure, while your app adds logic on top. For a personal disposable email system or OTP-forwarding tool, this tradeoff is more than acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This setup shows that not every backend needs to be online 24/7 to be reliable. By letting specialized services do what they are best at, you can simplify your architecture &lt;strong&gt;without sacrificing correctness&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://render.com/?_gl=1*5usffg*_gcl_au*MjQwOTgwNTE5LjE3NjY4OTIzNzc.*_ga*MTg2NDEwMDM1MS4xNzY2ODkyMzc3*_ga_QK9L9QJC5N*czE3Njc1MDA5MTkkbzEyJGcxJHQxNzY3NTAwOTIxJGo1OCRsMCRoMA.." rel="noopener noreferrer"&gt;Render’s free tier&lt;/a&gt; is not a compromise here. It’s simply a practical choice. The email pipeline remains stable, delivery is guaranteed, and your application adds value only where it matters.&lt;/p&gt;

&lt;p&gt;Sometimes, the cleanest solution is the one that worries the least about uptime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Email delivery&lt;/strong&gt; and &lt;strong&gt;application logic&lt;/strong&gt; should live in different layers. Once those responsibilities are separated, many common hosting concerns simply disappear.&lt;/p&gt;

&lt;p&gt;Free-tier infrastructure can be perfectly viable when your system is event driven rather than request-driven. If nothing breaks when your server sleeps, then sleeping is &lt;strong&gt;not a problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Finally, designing around real constraints instead of fighting them often leads to simpler and more robust systems.&lt;/p&gt;

&lt;p&gt;This post only explains the core logic.&lt;br&gt;
If you’re tired of spam too, feel free to &lt;strong&gt;fork&lt;/strong&gt;, &lt;strong&gt;improve&lt;/strong&gt;, or just &lt;strong&gt;steal the idea&lt;/strong&gt; :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy hacking!!!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>flask</category>
      <category>sendgrid</category>
      <category>security</category>
    </item>
  </channel>
</rss>
