<?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: Mudit Palvadi</title>
    <description>The latest articles on DEV Community by Mudit Palvadi (@mudit-palvadi).</description>
    <link>https://dev.to/mudit-palvadi</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4150203%2F5e530f0f-44dc-48ed-9583-76d2180b352d.jpg</url>
      <title>DEV Community: Mudit Palvadi</title>
      <link>https://dev.to/mudit-palvadi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mudit-palvadi"/>
    <language>en</language>
    <item>
      <title>I built an app that reads my dyslexic friend's email for him</title>
      <dc:creator>Mudit Palvadi</dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:57:35 +0000</pubDate>
      <link>https://dev.to/mudit-palvadi/i-built-an-app-that-reads-my-dyslexic-friends-email-for-him-4cb</link>
      <guid>https://dev.to/mudit-palvadi/i-built-an-app-that-reads-my-dyslexic-friends-email-for-him-4cb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Heads Up — an Android home-screen widget that shows at most three emails that actually matter, as three short plain lines, each with a play button so it can be listened to instead of read.&lt;/p&gt;

&lt;p&gt;I built it for a friend of mine.&lt;/p&gt;

&lt;p&gt;He is dyslexic. He is smart and entirely capable of running his own inbox,  he just doesn't, because the inbox is a wall of text and the wall costs him more than the contents are worth. So important things sit there. A college form with a deadline. A question from a friend he wanted to answer. Things that would have taken four minutes, if he'd seen them on the day they arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  The insight
&lt;/h2&gt;

&lt;p&gt;The usual advice for "I can't keep up with email" is filters, rules, priority inboxes, muting senders. All of it still leaves him doing the reading.&lt;/p&gt;

&lt;p&gt;His problem isn't that the inbox is too noisy. It's that the inbox is somewhere he has to go. Nobody walks past their own front door. You have to make a decision, and deciding is the expensive part.&lt;/p&gt;

&lt;p&gt;So: don't make him check it. Bring it to him. Three things on the screen he already looks at forty times a day, written in the fewest words that still mean something, and a button instead of a paragraph.&lt;/p&gt;

&lt;p&gt;Three items maximum, ever. And when there's nothing, it says Nothing needs you today ✓ rather than showing an empty box — an empty widget gets deleted, and a deleted widget gets missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/mudit-palvadi" rel="noopener noreferrer"&gt;
        mudit-palvadi
      &lt;/a&gt; / &lt;a href="https://github.com/mudit-palvadi/heads-up" rel="noopener noreferrer"&gt;
        heads-up
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Heads Up&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;MIT licensed — see &lt;a href="https://github.com/mudit-palvadi/heads-up/LICENSE" rel="noopener noreferrer"&gt;&lt;code&gt;LICENSE&lt;/code&gt;&lt;/a&gt;. That covers this source code only; the
Gemma weights are downloaded at runtime under Google's Gemma Terms of Use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The three things that need you today, on your home screen.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built for a friend who is dyslexic, rarely opens his email, and misses the
things that matter. Heads Up puts at most three flagged emails on his Android
home screen as short plain lines, each with a ▶ button so he can listen instead
of read.&lt;/p&gt;
&lt;p&gt;The widget is the product. The app is only setup and a control panel.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status — what is actually verified&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This is the honest picture, because "it works" is not a useful claim.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rules engine + cleaner&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Verified&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20 hand-written fixtures; every pass/fail row in &lt;code&gt;rules.md&lt;/code&gt; §8 asserted. Pure logic, no device needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-device inference&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Verified on hardware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemma 3 1B-IT via LiteRT-LM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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/mudit-palvadi/heads-up" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. 32 commits, 277 tests, &lt;code&gt;flutter analyze&lt;/code&gt; clean under&lt;br&gt;
&lt;code&gt;strict-casts&lt;/code&gt; / &lt;code&gt;strict-inference&lt;/code&gt; / &lt;code&gt;strict-raw-types&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A["Gmail (IMAP)&amp;lt;br/&amp;gt;EXAMINE + BODY.PEEK"] --&amp;gt; B["Cleaner&amp;lt;br/&amp;gt;HTML to text, strip quotes and signatures"]
    B --&amp;gt; C["RulesEngine&amp;lt;br/&amp;gt;VIP, keywords, deadlines, bulk"]
    C --&amp;gt;|not important| X["dropped"]
    C --&amp;gt;|important| D["Gemma 3 1B-IT&amp;lt;br/&amp;gt;on-device via LiteRT-LM"]
    D --&amp;gt; E["Validator&amp;lt;br/&amp;gt;shape and deadline match"]
    E --&amp;gt;|rejected| F["Template fallback"]
    E --&amp;gt;|accepted| G["ElevenLabs&amp;lt;br/&amp;gt;summary to mp3"]
    F --&amp;gt; H["WidgetSync"]
    G --&amp;gt; H
    H --&amp;gt; I["Home screen widget&amp;lt;br/&amp;gt;max 3 rows"]&lt;/code&gt;&lt;/pre&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://huggingface.co/litert-community/Gemma3-1B-IT" rel="noopener noreferrer"&gt;Gemma 3 1B-IT&lt;/a&gt;&lt;/strong&gt; —
open-weight, running &lt;strong&gt;fully on-device&lt;/strong&gt; via
&lt;a href="https://pub.dev/packages/flutter_gemma" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_gemma&lt;/code&gt;&lt;/a&gt; / LiteRT-LM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt;&lt;/strong&gt; — the open-source agent harness I built
the whole thing with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://elevenlabs.io" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt;&lt;/strong&gt; — voice synthesis for the summaries.&lt;/li&gt;
&lt;li&gt;Flutter, &lt;a href="https://pub.dev/packages/enough_mail" rel="noopener noreferrer"&gt;&lt;code&gt;enough_mail&lt;/code&gt;&lt;/a&gt; for IMAP,
&lt;a href="https://pub.dev/packages/home_widget" rel="noopener noreferrer"&gt;&lt;code&gt;home_widget&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://pub.dev/packages/just_audio" rel="noopener noreferrer"&gt;&lt;code&gt;just_audio&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Two decisions that mattered more than the model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Importance is never inferred by the AI.&lt;/strong&gt; A 1B model asked "is this&lt;br&gt;
important?" will confidently declare a shopping newsletter urgent. So the rules&lt;br&gt;
engine decides — with auditable regex you can read and argue with — and the&lt;br&gt;
model's only job is rewriting text already judged worth showing. If the rules&lt;br&gt;
engine can explain why an email is on the widget, that explanation survives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model is never allowed to invent a date.&lt;/strong&gt; The rules engine extracts&lt;br&gt;
deadlines by regex and hands the value to the prompt. If the model's &lt;code&gt;BY:&lt;/code&gt;&lt;br&gt;
doesn't match, the entire output is &lt;strong&gt;rejected&lt;/strong&gt; and a template is used instead.&lt;br&gt;
For someone who might act on a wrong date, a dull correct line beats a fluent&lt;br&gt;
wrong one every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma running on his phone isn't a nice-to-have. It's the whole&lt;br&gt;
architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;His inbox has personal things in it — medical, family, college. A cloud LLM&lt;br&gt;
would mean every one of those emails crosses a network to be read, and I'd have&lt;br&gt;
to tell him so. Running a 557 MB open-weight model locally means the most&lt;br&gt;
sensitive step never leaves the device. It also means &lt;strong&gt;$0 per email&lt;/strong&gt;, so&lt;br&gt;
there's no meter quietly encouraging him to read fewer of his own messages.&lt;/p&gt;

&lt;p&gt;The interesting part is what a &lt;em&gt;small&lt;/em&gt; model forces you to build. A frontier&lt;br&gt;
model could probably judge importance on its own. A 1B model can't — so the&lt;br&gt;
architecture gets organised around what the model &lt;strong&gt;can't&lt;/strong&gt; do: decide what&lt;br&gt;
matters, and invent a date. Those two jobs are pushed into a deterministic,&lt;br&gt;
readable, unit-tested layer, and the model is left doing the one thing it's&lt;br&gt;
good at, rewriting text into plainer text.&lt;/p&gt;

&lt;p&gt;That's a better shape regardless of model size: the parts that must be right are&lt;br&gt;
verifiable, and the part that's good at pattern-matching prose is allowed to be&lt;br&gt;
fuzzy because a validator stands behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I wasn't open, said plainly:&lt;/strong&gt; ElevenLabs is a proprietary cloud API.&lt;br&gt;
It's the voice — the part he actually experiences — and a synthetic voice that&lt;br&gt;
sounds like a robot would undercut the whole point. It's switchable off, and&lt;br&gt;
when it's off his phone's own TTS reads the line. The network call carries a&lt;br&gt;
~30-word summary, never an email, but it is still the one place content leaves&lt;br&gt;
the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 3 1B-IT runs fully on-device via LiteRT-LM and
rewrites every flagged email, with each output validated against a
rules-engine-extracted deadline before it's allowed to reach the screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — summaries are voiced with ElevenLabs and cached as
mp3s on the device so play is instant and works offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built with
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/litert-community/Gemma3-1B-IT" rel="noopener noreferrer"&gt;Gemma 3 1B-IT&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://elevenlabs.io" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt; · &lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; ·&lt;br&gt;
Flutter · &lt;a href="https://pub.dev/packages/enough_mail" rel="noopener noreferrer"&gt;enough_mail&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/home_widget" rel="noopener noreferrer"&gt;home_widget&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/just_audio" rel="noopener noreferrer"&gt;just_audio&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/flutter_tts" rel="noopener noreferrer"&gt;flutter_tts&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/flutter_secure_storage" rel="noopener noreferrer"&gt;flutter_secure_storage&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
