<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Purva Bangad</title>
    <description>The latest articles on DEV Community by Purva Bangad (@bangadpurva).</description>
    <link>https://dev.to/bangadpurva</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%2F3912602%2F0b07a90a-f20a-4ea6-a04a-7bb45c38b4d8.png</url>
      <title>DEV Community: Purva Bangad</title>
      <link>https://dev.to/bangadpurva</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bangadpurva"/>
    <language>en</language>
    <item>
      <title>Gemma 4: AI Masala Engine</title>
      <dc:creator>Purva Bangad</dc:creator>
      <pubDate>Tue, 26 May 2026 15:11:00 +0000</pubDate>
      <link>https://dev.to/bangadpurva/gemma-4-ai-masala-engine-4keh</link>
      <guid>https://dev.to/bangadpurva/gemma-4-ai-masala-engine-4keh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built the AI Masala Engine, a "Cine-Intelligence" platform designed to solve the chronic problem of "infinite scrolling" through movie catalogs. Instead of generic genres, this app maps a user's specific emotional state—like "Nostalgic but Edgy"—to a combination of current cinematic culture (vibes/memes) and personalized film recommendations.&lt;br&gt;
It acts as a hybrid bridge between deep movie reasoning and the fast-paced world of social cinema trends.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://filmymood-696319675839.us-west2.run.app/" rel="noopener noreferrer"&gt;Live Demo Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;The AI Masala Engine utilizes a Hybrid Multi-Model Architecture to ensure that each part of the user experience is powered by the right tool for the job. We didn't just use one model; we matched the competition guidelines by being intentional with model selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Gemma 4 31B Dense (Core Reasoning):&lt;br&gt;
&lt;em&gt;Why&lt;/em&gt;: We used the 31B Dense model for the complex Reason field in our recommendations. Connecting an abstract user preference like "Cyberpunk Noir with a hint of hope" to a specific classic film requires deep world knowledge and superior reasoning capabilities that only a server-grade dense model can provide.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemma 4 26B Mixture-of-Experts (High-Throughput Extraction):&lt;br&gt;
&lt;em&gt;Why&lt;/em&gt;: For the "Trending Vibes" and "Cinematic Memes" section, we utilized the MoE architecture. Identifying cultural micro-trends across YouTube, blogs, and social media requires high throughput and the ability to process diverse, switching contexts rapidly. MoE allowed us to extract these vibes without sacrificing inference speed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemma 4 4B Small (Edge-Optimized Copy):&lt;br&gt;
&lt;em&gt;Why&lt;/em&gt;: We offloaded the generation of "Punchy Taglines" to the 4B model. These are concise, micro-copy elements where speed is prioritized over deep reasoning, simulating a workflow where this model could eventually run locally on a user's device for instant UI feedback.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most compelling aspect of this project is the Inference Log visible in the app's footer, which shows the system "choosing" the path (Dense vs. MoE) based on the complexity of the user's movie prompt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Highlight: Prompt Engineering for Model Rationale
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From src/services/geminiService.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  REASONING ARCHITECTURE (Gemma 4 31B Dense + 4B Small Hybrid):
  - Use the 31B Dense reasoning path for the 'reason' field to provide deep cinematic links.
  - Use the 4B Small path for 'tagline' to ensure punchy, micro-copy style output.
  - Include a 'modelRationale' explaining how the Dense model connected this movie to the user's mood.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Why "Mood" Relevancy Matters&lt;br&gt;
The "Mood" relevancy is the heart of why this stands out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Beyond Metadata: Traditional search looks for "Action" or "1990s." Gemma 4 allows us to search for vibe. It understands that a "Neon-Noir Cyberpunk" mood isn't just about the setting—it's about the pacing, the color palette, and the philosophical undertones.&lt;/li&gt;
&lt;li&gt;The Model Rationale: By surfacing the modelRationale in the UI (e.g., "System Rationale: Gemma 4 31B Dense connected your 'melancholy optimism' to this film's use of natural light and non-linear storytelling"), I give the user insight into the AI's "thought process," turning a black-box recommendation into a collaborative discovery.&lt;/li&gt;
&lt;li&gt;Cultural Context: Integrating memes and vibes alongside movie picks makes the app feel "alive." It treats cinema as a living cultural conversation rather than a static database.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Core Feature Breakdown
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Craft Your Scene (Mood Recommender)
Uses Gemma 4 31B Dense to perform deep-link reasoning, connecting complex emotional prompts to obscure cinematic parallels.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// System instruction for Dense Reasoning
REASONING ARCHITECTURE (Gemma 4 31B Dense):
- Use the 31B Dense reasoning path for the 'reason' field to provide deep cinematic links.
- Use the 4B Small (Edge-optimized) path for the 'tagline' to ensure punchy, micro-copy style output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Trend Intelligence (Vibe Analysis)
Utilizes Gemma 4 26B MoE to process massive amounts of cultural data and identify shifting "Filmy Vibes" on social platforms.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// System instruction for MoE Throughput
INTELLIGENCE LAYER (Gemma 4 MoE 26B Optimized): 
- Since you are performing high-throughput cultural extraction, utilize the Mixture-of-Experts reasoning path. 
- Explain why a high-throughput social model was needed to catch this specific trend.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Trending Memes (Cultural Extraction)
Extracts the "current humor" of the film industry, turning critique into content by identifying micro-trends in cinematic discourse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;// Task: Identify 4 trending movie-related "Memes" &lt;br&gt;
// that define the current cinematic humor or critique style.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Highlight: Multi-Model Prompting&lt;/strong&gt;&lt;br&gt;
This snippet from &lt;code&gt;geminiService.ts&lt;/code&gt; shows how I orchestrate the different models within a single user flow to provide a "system rationale" for every recommendation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/geminiService.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  REASONING ARCHITECTURE (Gemma 4 31B Dense + 4B Small Hybrid):
  - Use the 31B Dense reasoning path for the 'reason' field to provide deep cinematic links.
  - Include a 'modelRationale' for EACH recommendation explaining how the Dense model 
    connected this specific movie to the user's complex mood.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>Build an AI Job Tracker With Gmail + Claude published: Auto-extracts applications, statuses, and interview dates from your inbox.</title>
      <dc:creator>Purva Bangad</dc:creator>
      <pubDate>Mon, 04 May 2026 18:08:15 +0000</pubDate>
      <link>https://dev.to/bangadpurva/build-an-ai-job-tracker-with-gmail-claude-published-auto-extracts-applications-statuses-and-1hnl</link>
      <guid>https://dev.to/bangadpurva/build-an-ai-job-tracker-with-gmail-claude-published-auto-extracts-applications-statuses-and-1hnl</guid>
      <description>&lt;p&gt;I applied to 40 jobs. I tracked exactly 3 of them. The other 37 lived rent-free in a spreadsheet I stopped opening after week two.&lt;/p&gt;

&lt;p&gt;So I did what any reasonable engineer would do: spent a weekend building a robot to do it for me.&lt;/p&gt;

&lt;p&gt;The result: an open-source Flask app that reads your Gmail, uses Claude AI to figure out what happened in each email, and builds you a full job search dashboard. Zero manual entry. Here's how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with every existing tool
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Huntr, Teal&lt;/strong&gt; — beautiful Kanban boards. You still move the cards yourself. That's just a prettier spreadsheet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplify&lt;/strong&gt; — great Chrome extension for autofilling applications. Does nothing after you hit submit. The moment you need it most: silence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LazyApply, LoopCV&lt;/strong&gt; — auto-applies to hundreds of jobs for you. Congratulations, you've been rejected at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;G-Track&lt;/strong&gt; — actually syncs Gmail! But uses keyword/domain heuristics. It doesn't &lt;em&gt;understand&lt;/em&gt; your emails, it pattern-matches them.&lt;/p&gt;

&lt;p&gt;None of them read "We'd love to move forward with your application" and know that means &lt;code&gt;in_process&lt;/code&gt;. That's where an LLM earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole system in one diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gmail API → raw emails → Claude (tool use) → structured data → SQLite → dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The AI part is one function call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core trick: give Claude one tool and let it decide
&lt;/h2&gt;

&lt;p&gt;Instead of asking Claude to "return JSON," you give it a function to call — and tell it to only call it for job-related emails. Claude classifies, extracts, and structures all in one pass. Non-job emails? It just... doesn't call the tool. Beautiful.&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;_SAVE_APPLICATION_TOOL&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;name&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;save_job_application&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;description&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;Save a job application. Only call this for actual job emails, not newsletters.&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;input_schema&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;object&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;properties&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;company&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&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;role&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&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;status&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&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;enum&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;applied&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;in_process&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;interview_scheduled&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;rejected&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;offer&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;applied_date&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&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;description&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;YYYY-MM-DD&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;interview_date&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&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;description&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;YYYY-MM-DD if scheduled&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;skills&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;array&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;items&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;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&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;description&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;Tech skills mentioned. Max 8.&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="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;required&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;gmail_message_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company&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;status&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;status&lt;/code&gt; enum is load-bearing — Claude cannot invent a value outside those five. Schema as guardrails. &lt;/p&gt;

&lt;p&gt;Now batch up to 50 emails and fire:&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_SAVE_APPLICATION_TOOL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;messages&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Each tool_use block = one detected job application
&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool_use&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;One API call. 50 emails in, structured records out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one data model rule that saves everything
&lt;/h2&gt;

&lt;p&gt;Status only moves forward. Always.&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;STATUS_RANK&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;applied&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;in_process&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;interview_scheduled&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;offer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rejected&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;TERMINAL_STATUSES&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;rejected&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;offer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# once here, you're done (sadly or happily)
&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TERMINAL_STATUSES&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;STATUS_RANK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_status&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;STATUS_RANK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, your daily scan would see an old "application received" email and cheerfully overwrite the interview you scheduled last Tuesday. Don't let it.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;UniqueConstraint&lt;/code&gt; on &lt;code&gt;(user_id, gmail_message_id)&lt;/code&gt; means the same email can never create two records. Idempotent scans, no drama.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bonus features (the fun stuff)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Auto follow-ups&lt;/strong&gt; — application sitting in &lt;code&gt;applied&lt;/code&gt; for 14+ days? Claude writes you a polite "just checking in" email. You decide whether to send it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interview prep&lt;/strong&gt; — status flips to &lt;code&gt;interview_scheduled&lt;/code&gt;? Claude immediately generates 5 likely questions, 4 things to research, 3 talking points, and 1 tip. Stored on the record, shown on the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily scheduler&lt;/strong&gt; — three background jobs, staggered 5 minutes apart so each one has fresh data:&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="nc"&gt;CronTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# scan Gmail
&lt;/span&gt;&lt;span class="nc"&gt;CronTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# generate follow-ups
&lt;/span&gt;&lt;span class="nc"&gt;CronTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# generate interview prep
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wake up at 8am. Everything's already done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-repo/job-tracker
&lt;span class="nb"&gt;cd &lt;/span&gt;job-tracker
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env  &lt;span class="c"&gt;# add your Anthropic + Google keys&lt;/span&gt;
python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Google keys? Set &lt;code&gt;GOOGLE_CLIENT_ID=&lt;/code&gt; to blank. The app loads 8 demo applications so you can click around immediately.&lt;/p&gt;

&lt;p&gt;Cost: Claude Haiku at ~$0.25/million tokens. Scanning 50 emails costs less than 0.01 cents. You spend more on the tab you accidentally left open.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not just ask Claude for JSON?
&lt;/h2&gt;

&lt;p&gt;Three reasons tool use wins:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One bad email doesn't kill the batch.&lt;/strong&gt; Tool use is per-item. A JSON array fails all-or-nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Skip non-job emails" is natural.&lt;/strong&gt; Claude just... doesn't call the tool. No null entries, no filtering logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The enum is enforced at the API level.&lt;/strong&gt; Claude cannot return &lt;code&gt;"ghosted"&lt;/code&gt; as a status, no matter how accurate that would be.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  That's it
&lt;/h2&gt;

&lt;p&gt;Under 300 lines of Python for the core pipeline. Runs on your laptop. Your data stays local. Claude Haiku keeps costs laughably low.&lt;/p&gt;

&lt;p&gt;Full write-up with architecture deep-dive → &lt;a href="https://medium.com/@bangadpurva/i-built-an-ai-that-reads-my-gmail-and-tracks-every-job-application-automatically-a9dafca7a66b" rel="noopener noreferrer"&gt;Medium article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo → &lt;code&gt;https://github.com/bangadpurva/job-tracker&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Go forth and be rejected in an organized fashion. 🚀&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>career</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
