<?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: Mohit</title>
    <description>The latest articles on DEV Community by Mohit (@flux8labs).</description>
    <link>https://dev.to/flux8labs</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%2F3960092%2F79babcb4-fd1f-4f3e-bb44-8e289a3949cc.jpg</url>
      <title>DEV Community: Mohit</title>
      <link>https://dev.to/flux8labs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/flux8labs"/>
    <language>en</language>
    <item>
      <title>Building Minyut: An Embeddable RAG Chatbot in One Script Tag</title>
      <dc:creator>Mohit</dc:creator>
      <pubDate>Thu, 18 Jun 2026 05:50:00 +0000</pubDate>
      <link>https://dev.to/flux8labs/building-minyut-an-embeddable-rag-chatbot-in-one-script-tag-c1h</link>
      <guid>https://dev.to/flux8labs/building-minyut-an-embeddable-rag-chatbot-in-one-script-tag-c1h</guid>
      <description>&lt;p&gt;A client needed their customers to be able to query a 40-page policy document without reading through it.&lt;/p&gt;

&lt;p&gt;We built the first version of what became &lt;strong&gt;Minyut&lt;/strong&gt; in a weekend.&lt;/p&gt;

&lt;p&gt;It used a basic embedding approach, answered from an OpenAI endpoint, and—in testing—confidently responded to questions that had no answer in the document at all.&lt;/p&gt;

&lt;p&gt;It made things up.&lt;/p&gt;

&lt;p&gt;Fluently.&lt;/p&gt;

&lt;p&gt;Completely wrong.&lt;/p&gt;

&lt;p&gt;That was the founding problem.&lt;/p&gt;

&lt;p&gt;Every document chatbot we tested made things up. Not because the models were bad, but because they weren't constrained to answer only from the documents.&lt;/p&gt;

&lt;p&gt;Minyut is built around a single architectural decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every answer must come from uploaded content—or the chatbot says, "I don't know."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything else follows from that constraint.&lt;/p&gt;

&lt;p&gt;Today, Minyut processes queries for chatbots embedded on Webflow sites, WordPress installs, Shopify stores, React apps, and plain HTML pages.&lt;/p&gt;

&lt;p&gt;Documents are stored in Supabase's Mumbai region, and the widget can be embedded with a single script tag in under ten minutes.&lt;/p&gt;

&lt;p&gt;Here's how it's built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Refused to Go Away: Knowledge Isolation
&lt;/h2&gt;

&lt;p&gt;Standard AI chatbots answer from their training data.&lt;/p&gt;

&lt;p&gt;For general knowledge, that's exactly what you want.&lt;/p&gt;

&lt;p&gt;For support chatbots, legal documents, policy manuals, product specifications, and consultancy websites, accuracy becomes a liability issue rather than a convenience feature.&lt;/p&gt;

&lt;p&gt;A chatbot that invents policy details isn't a support tool.&lt;/p&gt;

&lt;p&gt;It's a liability.&lt;/p&gt;

&lt;p&gt;The solution is &lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At query time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user's question becomes a vector embedding.&lt;/li&gt;
&lt;li&gt;Relevant document chunks are retrieved.&lt;/li&gt;
&lt;li&gt;Only those chunks are sent to the language model.&lt;/li&gt;
&lt;li&gt;The model answers using the retrieved context.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer isn't present in the uploaded documents, the chatbot says so.&lt;/p&gt;

&lt;p&gt;The language model can only answer as well as the passages you retrieve.&lt;/p&gt;

&lt;p&gt;Good retrieval is most of the battle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chunking and Embedding Pipeline
&lt;/h2&gt;

&lt;p&gt;Documents arrive as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF&lt;/li&gt;
&lt;li&gt;Markdown&lt;/li&gt;
&lt;li&gt;Plain text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;File limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free plan: 5 MB&lt;/li&gt;
&lt;li&gt;Paid plans: 25 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After extraction, documents are chunked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 1: Sentence-Level Chunks
&lt;/h3&gt;

&lt;p&gt;Each sentence became its own chunk.&lt;/p&gt;

&lt;p&gt;Retrieval was precise but context disappeared.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; What is the refund window?&lt;/p&gt;

&lt;p&gt;Retrieved:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Refunds are processed in 7 days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Technically correct.&lt;/p&gt;

&lt;p&gt;Practically useless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 2: Full Paragraphs
&lt;/h3&gt;

&lt;p&gt;Context improved.&lt;/p&gt;

&lt;p&gt;Retrieval consistency did not.&lt;/p&gt;

&lt;p&gt;Short and long paragraphs behaved very differently during similarity search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Approach: Fixed Chunks With Overlap
&lt;/h3&gt;

&lt;p&gt;Current strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;600-token chunks&lt;/li&gt;
&lt;li&gt;80-token overlap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overlap ensures sentences crossing chunk boundaries remain complete in at least one retrieved section.&lt;/p&gt;

&lt;p&gt;For Minyut's document types, answer quality improved significantly.&lt;/p&gt;

&lt;p&gt;Each chunk is embedded using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sentence-transformers/all-MiniLM-L6-v2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;via the HuggingFace Inference API.&lt;/p&gt;

&lt;p&gt;The model generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;384-dimensional vectors&lt;/li&gt;
&lt;li&gt;Fast indexing&lt;/li&gt;
&lt;li&gt;Strong semantic search performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vectors are stored in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;pgvector extension&lt;/li&gt;
&lt;li&gt;HNSW index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;inside Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Widget: One Script Tag, No CSS Conflicts
&lt;/h2&gt;

&lt;p&gt;The hard problem wasn't loading a script.&lt;/p&gt;

&lt;p&gt;It was ensuring the widget worked everywhere.&lt;/p&gt;

&lt;p&gt;Different host websites bring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different CSS frameworks&lt;/li&gt;
&lt;li&gt;Different z-index rules&lt;/li&gt;
&lt;li&gt;Different positioning systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our first approach used scoped CSS.&lt;/p&gt;

&lt;p&gt;It failed repeatedly.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress themes overriding positioning&lt;/li&gt;
&lt;li&gt;Global CSS affecting widget layout&lt;/li&gt;
&lt;li&gt;Z-index conflicts hiding the chat button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution was Shadow DOM.&lt;/p&gt;

&lt;p&gt;The widget creates a completely isolated DOM tree.&lt;/p&gt;

&lt;p&gt;Host styles cannot leak in.&lt;/p&gt;

&lt;p&gt;Widget styles cannot leak out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything lives inside the shadow root.&lt;/p&gt;

&lt;p&gt;Style conflicts effectively disappear.&lt;/p&gt;

&lt;p&gt;The widget is delivered as a single async script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://minyut.com/widget.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advanced users can control behavior through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__minyut__&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opening the widget&lt;/li&gt;
&lt;li&gt;Closing the widget&lt;/li&gt;
&lt;li&gt;Prefilling messages&lt;/li&gt;
&lt;li&gt;Listening to events&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Infrastructure
&lt;/h2&gt;

&lt;p&gt;Minyut's stack is intentionally simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supabase
&lt;/h3&gt;

&lt;p&gt;Handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;pgvector&lt;/li&gt;
&lt;li&gt;Edge Functions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Netlify
&lt;/h3&gt;

&lt;p&gt;Hosts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Marketing website&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Widget CDN&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Razorpay
&lt;/h3&gt;

&lt;p&gt;Handles subscription billing.&lt;/p&gt;

&lt;h3&gt;
  
  
  HuggingFace
&lt;/h3&gt;

&lt;p&gt;Provides embedding generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Groq
&lt;/h3&gt;

&lt;p&gt;Handles language model inference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage Security
&lt;/h3&gt;

&lt;p&gt;Documents are stored in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private buckets&lt;/li&gt;
&lt;li&gt;Account-scoped access&lt;/li&gt;
&lt;li&gt;No cross-account visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  BYOK
&lt;/h3&gt;

&lt;p&gt;Bring Your Own Key support allows users to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Groq&lt;/li&gt;
&lt;li&gt;OpenAI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keys are encrypted using AES-256.&lt;/p&gt;

&lt;p&gt;We never need to operate GPU infrastructure ourselves.&lt;/p&gt;

&lt;p&gt;At Minyut's current scale, that's exactly the tradeoff we want.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pricing Decision: No Token Meters
&lt;/h2&gt;

&lt;p&gt;The most common complaint about chatbot SaaS products isn't quality.&lt;/p&gt;

&lt;p&gt;It's billing uncertainty.&lt;/p&gt;

&lt;p&gt;Usage-based pricing creates anxiety.&lt;/p&gt;

&lt;p&gt;A traffic spike should not become a surprise invoice.&lt;/p&gt;

&lt;p&gt;Minyut uses fixed monthly plans.&lt;/p&gt;

&lt;p&gt;Users receive notifications at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80% usage&lt;/li&gt;
&lt;li&gt;100% usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing fails silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tinkerers (Free)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;1 chatbot&lt;/li&gt;
&lt;li&gt;3 documents&lt;/li&gt;
&lt;li&gt;100 queries/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Starter ($5/month)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;3 chatbots&lt;/li&gt;
&lt;li&gt;10 documents&lt;/li&gt;
&lt;li&gt;500 queries/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pro ($12/month)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;10 chatbots&lt;/li&gt;
&lt;li&gt;Unlimited documents&lt;/li&gt;
&lt;li&gt;2,000 queries/month&lt;/li&gt;
&lt;li&gt;Custom domains&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Priority support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  BYOK ($3/month)
&lt;/h3&gt;

&lt;p&gt;Unlimited queries through the user's own OpenAI or Groq key.&lt;/p&gt;

&lt;p&gt;We handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Bandwidth&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users pay model providers directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Things That Would Have Saved Us Time
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Chunking Matters More Than Models
&lt;/h3&gt;

&lt;p&gt;We spent weeks comparing language models.&lt;/p&gt;

&lt;p&gt;The bigger factor was chunk size and overlap.&lt;/p&gt;

&lt;p&gt;Fix retrieval before optimizing inference.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Shadow DOM Solves Widget CSS Problems
&lt;/h3&gt;

&lt;p&gt;Scoped CSS eventually breaks.&lt;/p&gt;

&lt;p&gt;Shadow DOM doesn't.&lt;/p&gt;

&lt;p&gt;Once we switched, CSS-related issues effectively disappeared.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Design for BYOK Early
&lt;/h3&gt;

&lt;p&gt;The users who want BYOK are often the most engaged.&lt;/p&gt;

&lt;p&gt;They build real systems.&lt;/p&gt;

&lt;p&gt;Supporting them from the start avoids painful architectural changes later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Minyut started as an attempt to solve a simple problem:&lt;/p&gt;

&lt;p&gt;How do you build a chatbot that answers only from documents and refuses to invent information?&lt;/p&gt;

&lt;p&gt;The answer ended up being a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieval-Augmented Generation&lt;/li&gt;
&lt;li&gt;Careful chunking&lt;/li&gt;
&lt;li&gt;Semantic search&lt;/li&gt;
&lt;li&gt;Shadow DOM isolation&lt;/li&gt;
&lt;li&gt;A simple deployment model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a chatbot that can be embedded on almost any website using a single script tag and answer only from uploaded content.&lt;/p&gt;

&lt;p&gt;That's exactly what we set out to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What file types does Minyut support?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;PDF&lt;/li&gt;
&lt;li&gt;Markdown (.md)&lt;/li&gt;
&lt;li&gt;Plain text (.txt)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Will it answer questions not present in my documents?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Minyut is designed specifically for document-grounded responses.&lt;/p&gt;

&lt;p&gt;If the information isn't present in uploaded content, the chatbot says it doesn't know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which platforms does the embed support?
&lt;/h3&gt;

&lt;p&gt;The widget has been tested on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress&lt;/li&gt;
&lt;li&gt;Webflow&lt;/li&gt;
&lt;li&gt;Shopify&lt;/li&gt;
&lt;li&gt;Framer&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;Plain HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because it uses Shadow DOM isolation, it works reliably across virtually any platform that permits custom JavaScript.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>How We Automated Purchase Orders From Gmail to Tally Using GPT-4 (98% Extraction Accuracy)</title>
      <dc:creator>Mohit</dc:creator>
      <pubDate>Thu, 11 Jun 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/flux8labs/how-we-automated-purchase-orders-from-gmail-to-tally-using-gpt-4-98-extraction-accuracy-ceg</link>
      <guid>https://dev.to/flux8labs/how-we-automated-purchase-orders-from-gmail-to-tally-using-gpt-4-98-extraction-accuracy-ceg</guid>
      <description>&lt;p&gt;At 9:14am on a Tuesday, the system flagged an incoming purchase order from a large enterprise buyer as a duplicate.&lt;/p&gt;

&lt;p&gt;The PO had arrived in two separate emails over 48 hours — sent by different procurement contacts, both for the same batch of stainless steel flanges, same quantities, same delivery window.&lt;/p&gt;

&lt;p&gt;Under the old system, a staff member would have read both, entered both into Tally, and allocated raw material stock twice. The first sign of the error would have been an inventory shortfall two weeks later.&lt;/p&gt;

&lt;p&gt;The client is a Jaipur-based precision manufacturer serving enterprise buyers in India and overseas. At roughly ₹60Cr annual revenue, their team handled a steady flow of purchase orders across a demanding customer base.&lt;/p&gt;

&lt;p&gt;Every one of those orders arrived as a PDF in a shared Gmail inbox.&lt;/p&gt;

&lt;p&gt;Every one of those PDFs was read and entered into Tally by hand.&lt;/p&gt;

&lt;p&gt;This is the build log for the system built to replace that process.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: PDFs in Gmail, Nobody Watching
&lt;/h2&gt;

&lt;p&gt;The procurement workflow before the build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emails arrive in a shared Gmail inbox.&lt;/li&gt;
&lt;li&gt;A staff member opens each attachment.&lt;/li&gt;
&lt;li&gt;Reads part numbers, quantities, delivery deadlines, and supplier codes.&lt;/li&gt;
&lt;li&gt;Manually enters everything into Tally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a slow day this took around 90 minutes.&lt;/p&gt;

&lt;p&gt;On heavier order days, it could stretch to 3–4 hours.&lt;/p&gt;

&lt;p&gt;The inbox had no workflow state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No processed flag&lt;/li&gt;
&lt;li&gt;No queue&lt;/li&gt;
&lt;li&gt;No audit trail outside Tally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the same PO arrived twice, the team would know only by accident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate Orders
&lt;/h3&gt;

&lt;p&gt;There was no detection mechanism.&lt;/p&gt;

&lt;p&gt;Two contacts at the same enterprise customer could send the same PO independently, and neither Gmail nor Tally would flag it.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Operational Visibility
&lt;/h3&gt;

&lt;p&gt;Knowing which orders were due the following week required opening Tally and manually cross-referencing entries.&lt;/p&gt;

&lt;p&gt;There was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No dashboard&lt;/li&gt;
&lt;li&gt;No queue view&lt;/li&gt;
&lt;li&gt;No workload overview&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Manual Raw Material Calculations
&lt;/h3&gt;

&lt;p&gt;Once a PO was entered, another manual calculation followed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulling specifications from the PDF&lt;/li&gt;
&lt;li&gt;Checking stock levels&lt;/li&gt;
&lt;li&gt;Estimating requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This introduced a second opportunity for human error.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was Built: A Four-Stage Pipeline
&lt;/h2&gt;

&lt;p&gt;Each stage solved a different operational problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Gmail Push Notifications
&lt;/h3&gt;

&lt;p&gt;Instead of polling Gmail every few minutes, the system registers a Google Cloud Pub/Sub topic that triggers a webhook whenever a new email arrives.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Near real-time processing&lt;/li&gt;
&lt;li&gt;Lower infrastructure overhead&lt;/li&gt;
&lt;li&gt;Faster visibility for operations teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A PO is processed before a staff member would have opened the email.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. GPT-4 Structured Extraction
&lt;/h3&gt;

&lt;p&gt;The system sends the purchase order to GPT-4 using a strict JSON schema.&lt;/p&gt;

&lt;p&gt;Required fields include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PO number&lt;/li&gt;
&lt;li&gt;Supplier details&lt;/li&gt;
&lt;li&gt;Delivery date&lt;/li&gt;
&lt;li&gt;Line items&lt;/li&gt;
&lt;li&gt;Quantities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model returns structured JSON directly.&lt;/p&gt;

&lt;p&gt;No:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regex maintenance&lt;/li&gt;
&lt;li&gt;Template mapping&lt;/li&gt;
&lt;li&gt;Field-position assumptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For scanned PDFs, the vision endpoint is used.&lt;/p&gt;

&lt;p&gt;For text PDFs, extracted text is sent directly.&lt;/p&gt;

&lt;p&gt;Both paths produce the same JSON output.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tally Integration
&lt;/h3&gt;

&lt;p&gt;The extracted JSON is converted into a Tally-compatible XML purchase voucher and sent to Tally Prime's local HTTP server.&lt;/p&gt;

&lt;p&gt;Once accepted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The voucher is created automatically.&lt;/li&gt;
&lt;li&gt;Inventory calculations run immediately.&lt;/li&gt;
&lt;li&gt;The order appears exactly as if a user entered it manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Duplicate Detection
&lt;/h3&gt;

&lt;p&gt;Every incoming PO is stored in PostgreSQL.&lt;/p&gt;

&lt;p&gt;A normalized fingerprint is generated using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line items&lt;/li&gt;
&lt;li&gt;Quantities&lt;/li&gt;
&lt;li&gt;Delivery windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows the system to detect duplicate orders before they reach Tally.&lt;/p&gt;

&lt;p&gt;The duplicate mentioned earlier was caught using this mechanism.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technology Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Gmail API&lt;/li&gt;
&lt;li&gt;Google Pub/Sub&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Framer Motion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI Layer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GPT-4 Structured Outputs&lt;/li&gt;
&lt;li&gt;GPT-4 Vision&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ERP Layer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tally Prime via TDL XML imports&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Working with the Tally API
&lt;/h2&gt;

&lt;p&gt;The official documentation leaves a lot to be desired.&lt;/p&gt;

&lt;p&gt;The most reliable approach is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Tally's HTTP server.&lt;/li&gt;
&lt;li&gt;Generate TDL XML vouchers.&lt;/li&gt;
&lt;li&gt;POST them to Tally.&lt;/li&gt;
&lt;li&gt;Parse the acknowledgement response.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Enable Tally HTTP Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gateway of Tally
→ F12
→ Advanced Configuration
→ Enable ODBC/HTTP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Minimal Purchase Voucher Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ENVELOPE&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;HEADER&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TALLYREQUEST&amp;gt;&lt;/span&gt;Import Data&lt;span class="nt"&gt;&amp;lt;/TALLYREQUEST&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/HEADER&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;BODY&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;IMPORTDATA&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;REQUESTDESC&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;REPORTNAME&amp;gt;&lt;/span&gt;Vouchers&lt;span class="nt"&gt;&amp;lt;/REPORTNAME&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;STATICVARIABLES&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;SVCURRENTCOMPANY&amp;gt;&lt;/span&gt;[Company Name]&lt;span class="nt"&gt;&amp;lt;/SVCURRENTCOMPANY&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/STATICVARIABLES&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/REQUESTDESC&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;REQUESTDATA&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;TALLYMESSAGE&lt;/span&gt; &lt;span class="na"&gt;xmlns:UDF=&lt;/span&gt;&lt;span class="s"&gt;"TallyUDF"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;VOUCHER&lt;/span&gt; &lt;span class="na"&gt;VCHTYPE=&lt;/span&gt;&lt;span class="s"&gt;"Purchase"&lt;/span&gt; &lt;span class="na"&gt;ACTION=&lt;/span&gt;&lt;span class="s"&gt;"Create"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;DATE&amp;gt;&lt;/span&gt;[YYYYMMDD]&lt;span class="nt"&gt;&amp;lt;/DATE&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;VOUCHERTYPENAME&amp;gt;&lt;/span&gt;Purchase&lt;span class="nt"&gt;&amp;lt;/VOUCHERTYPENAME&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;PARTYLEDGERNAME&amp;gt;&lt;/span&gt;[Supplier Name]&lt;span class="nt"&gt;&amp;lt;/PARTYLEDGERNAME&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ALLLEDGERENTRIES.LIST&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;LEDGERNAME&amp;gt;&lt;/span&gt;[Ledger]&lt;span class="nt"&gt;&amp;lt;/LEDGERNAME&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;AMOUNT&amp;gt;&lt;/span&gt;[Amount]&lt;span class="nt"&gt;&amp;lt;/AMOUNT&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ALLLEDGERENTRIES.LIST&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/VOUCHER&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/TALLYMESSAGE&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/REQUESTDATA&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/IMPORTDATA&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/BODY&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ENVELOPE&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first working voucher took three days.&lt;/p&gt;

&lt;p&gt;Everything after that was extension work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Admin Dashboard
&lt;/h2&gt;

&lt;p&gt;The dashboard became the team's primary operational interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orders Panel
&lt;/h3&gt;

&lt;p&gt;Shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status&lt;/li&gt;
&lt;li&gt;Supplier&lt;/li&gt;
&lt;li&gt;PO Number&lt;/li&gt;
&lt;li&gt;Delivery Deadline&lt;/li&gt;
&lt;li&gt;Part Count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Duplicate orders are highlighted and blocked from entering Tally until reviewed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tally Panel
&lt;/h3&gt;

&lt;p&gt;Provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revenue trends&lt;/li&gt;
&lt;li&gt;Receivables&lt;/li&gt;
&lt;li&gt;Payment status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without opening Tally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operations Panel
&lt;/h3&gt;

&lt;p&gt;Handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dispatch notes&lt;/li&gt;
&lt;li&gt;QA checklists&lt;/li&gt;
&lt;li&gt;Production milestones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All driven from the same purchase-order data.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Failed First
&lt;/h2&gt;

&lt;p&gt;The original extraction system used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pdf-parse&lt;/li&gt;
&lt;li&gt;OCR&lt;/li&gt;
&lt;li&gt;Regex pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The assumption:&lt;/p&gt;

&lt;p&gt;Purchase order formats would remain stable.&lt;/p&gt;

&lt;p&gt;They didn't.&lt;/p&gt;

&lt;p&gt;Enterprise customers used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple templates&lt;/li&gt;
&lt;li&gt;Different table structures&lt;/li&gt;
&lt;li&gt;Scanned PDFs&lt;/li&gt;
&lt;li&gt;International formats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~60% success rate&lt;/li&gt;
&lt;li&gt;Frequent maintenance&lt;/li&gt;
&lt;li&gt;Silent extraction failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One part-number transposition eventually led to production of an incorrect batch.&lt;/p&gt;

&lt;p&gt;That was the turning point.&lt;/p&gt;

&lt;p&gt;The system was rebuilt around GPT-4 structured outputs.&lt;/p&gt;

&lt;p&gt;Validation against historical purchase orders increased extraction accuracy from roughly 60% to over 98%.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Lessons From the Project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. PDF Extraction Has a Clear ROI
&lt;/h3&gt;

&lt;p&gt;For teams processing purchase orders every week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI costs remain small.&lt;/li&gt;
&lt;li&gt;Manual labor costs do not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The business case becomes obvious once the numbers are compared.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tally Integration Is Easier Than Its Reputation
&lt;/h3&gt;

&lt;p&gt;The difficult part is finding a working example.&lt;/p&gt;

&lt;p&gt;Once you successfully create one XML voucher, the rest is straightforward engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Push Beats Polling
&lt;/h3&gt;

&lt;p&gt;The biggest benefit wasn't lower latency.&lt;/p&gt;

&lt;p&gt;It was trust.&lt;/p&gt;

&lt;p&gt;A dashboard updated within seconds becomes an operational tool.&lt;/p&gt;

&lt;p&gt;A dashboard updated every few minutes becomes a report.&lt;/p&gt;

&lt;p&gt;That difference determines whether teams actually adopt the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Could This Work For Your Business?
&lt;/h2&gt;

&lt;p&gt;If your team receives PDFs in Gmail and manually enters them into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tally&lt;/li&gt;
&lt;li&gt;ERP systems&lt;/li&gt;
&lt;li&gt;Inventory software&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the workflow can likely be automated.&lt;/p&gt;

&lt;p&gt;The core pattern is repeatable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gmail
→ AI Extraction
→ Validation
→ ERP Integration
→ Operations Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This project went from discovery call to production deployment in six weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this work with Tally ERP 9?
&lt;/h3&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;The XML import approach works with both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tally Prime&lt;/li&gt;
&lt;li&gt;Tally ERP 9&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main requirement is enabling Tally's HTTP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can it process scanned PDFs?
&lt;/h3&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Scanned PDFs use GPT-4 Vision.&lt;/p&gt;

&lt;p&gt;Text PDFs use direct extraction.&lt;/p&gt;

&lt;p&gt;Both produce the same structured output.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if the AI extracts incorrect data?
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;Confidence scoring&lt;/li&gt;
&lt;li&gt;Validation rules&lt;/li&gt;
&lt;li&gt;Human review queues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suspicious records are blocked before reaching Tally.&lt;/p&gt;

&lt;p&gt;This prevents errors from entering downstream operations.&lt;/p&gt;

</description>
      <category>casestudy</category>
      <category>oms</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
