<?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: Subhendu Das</title>
    <description>The latest articles on DEV Community by Subhendu Das (@sumaninster).</description>
    <link>https://dev.to/sumaninster</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%2F4055832%2Fe0e1fcc3-e36e-4f90-8814-c6aef548f373.jpg</url>
      <title>DEV Community: Subhendu Das</title>
      <link>https://dev.to/sumaninster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sumaninster"/>
    <language>en</language>
    <item>
      <title>Shipping in Silence: Treating Developer Marketing as a Build Artifact</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:05:05 +0000</pubDate>
      <link>https://dev.to/sumaninster/shipping-in-silence-treating-developer-marketing-as-a-build-artifact-32hp</link>
      <guid>https://dev.to/sumaninster/shipping-in-silence-treating-developer-marketing-as-a-build-artifact-32hp</guid>
      <description>&lt;p&gt;Your CI pipeline builds, tests, packages, signs and deploys. It produces a changelog. It notifies a Slack channel. Then it stops, and everything downstream of "the code is live" — the blog post, the release note anyone outside the team will read, the social post that tells the eleven people who care that the thing they asked for now exists — happens by hand, or does not happen.&lt;/p&gt;

&lt;p&gt;For most small teams it does not happen. Not because it is hard, but because it is unowned. There is no failing check for "shipped in silence."&lt;/p&gt;

&lt;p&gt;That gap is worth looking at directly, because the tooling around it has changed in the last two years and most engineering teams have not noticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the gap comes from
&lt;/h2&gt;

&lt;p&gt;Developer marketing has always sat in an awkward place. It requires understanding what the code does, which marketers usually do not, and it requires writing for people, which engineers usually deprioritise. The traditional resolution is a developer-advocate or technical-marketing hire, and that hire is expensive and does not happen until a company is well past product-market fit.&lt;/p&gt;

&lt;p&gt;So the small-team default is a blog with three posts, the newest eighteen months old, and a launch announcement that got written because a launch is an event and events force the issue. Meanwhile the repository has 400 commits, nine releases, and a genuinely useful feature that shipped in March and has never been mentioned anywhere a user would see it.&lt;/p&gt;

&lt;p&gt;The asymmetry is worth stating plainly: the expensive part — building the thing — is done. The cheap part is what is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;Three things, roughly at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drafting stopped being the bottleneck.&lt;/strong&gt; A capable model given a diff, a changelog and some product context produces a serviceable first draft of a release post. Not a finished one. Serviceable — which matters, because the real cost was never the typing, it was the blank page. Editing a draft that is 70% right is a ten-minute job. Starting from nothing is a task that gets deferred indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Publishing became an API problem.&lt;/strong&gt; Dev.to, Hashnode, Bluesky, Mastodon, Medium, WordPress and Buttondown all take programmatic writes. Bluesky's AT Protocol in particular is an open, well-documented, free write API with a developer-dense audience, which is an unusually good combination. Cross-posting stopped being a copy-paste chore and became an integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository events became reliable triggers.&lt;/strong&gt; Webhooks and the commits/releases APIs mean "something shipped" is a signal you can subscribe to rather than a thing you have to remember.&lt;/p&gt;

&lt;p&gt;Individually, none is remarkable. Together they mean the loop from &lt;em&gt;merged&lt;/em&gt; to &lt;em&gt;published&lt;/em&gt; can be closed with ordinary software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable part
&lt;/h2&gt;

&lt;p&gt;Here is the objection, and it is correct: the internet does not need more automatically generated content. LLM-written blog spam is a real and growing problem, search results are measurably worse for it, and "we automated our content" is exactly what someone about to make things worse would say.&lt;/p&gt;

&lt;p&gt;So the distinction has to be load-bearing, and it is this: &lt;strong&gt;automate the pipeline, not the judgement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is a version of this that generates posts from commits and publishes them unattended. It produces content nobody wants, degrades the signal on the platforms it posts to, and is bad for the person doing it. It should not be built.&lt;/p&gt;

&lt;p&gt;The version that works keeps a human in exactly one place — approval — and automates everything on both sides of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detecting that something shipped worth writing about.&lt;/li&gt;
&lt;li&gt;Drafting from real repository activity, not from a topic prompt.&lt;/li&gt;
&lt;li&gt;Putting the draft in a review queue where a human reads it and, importantly, &lt;em&gt;can reject it&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Handling scheduling, cross-posting, canonical URLs and syndication delays.&lt;/li&gt;
&lt;li&gt;Tracking what happened afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pipeline is automated. The publish decision is not. That single retained decision is what separates a tool from a spam cannon, and it is the reason a review queue is a feature rather than friction.&lt;/p&gt;

&lt;p&gt;It also has a practical effect: knowing a human will read the draft changes how you build the generator. You stop optimising for volume and start optimising for the draft being worth approving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Details that turn out to matter
&lt;/h2&gt;

&lt;p&gt;Once you build this, a set of unglamorous problems shows up. They are worth knowing about in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canonical URLs are the whole syndication game.&lt;/strong&gt; Publish the same article to Dev.to, Hashnode and Medium with no canonical and you have three pages competing for the same query, splitting whatever ranking they would have had. Publish the original, take its URL, and set it as the canonical on every copy, and you have one page with three sources of traffic. Every platform in this list supports the tag. Nearly every naive cross-poster ignores it.&lt;/p&gt;

&lt;p&gt;The ordering implication is subtle: the copies cannot go out until the original has a URL. Which means syndication is inherently staggered, and the delay is a feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-network posts should link, not duplicate.&lt;/strong&gt; A Bluesky post carrying an article's first 280 characters is worse than useless — it competes with the article and reads as a bot. A one-line hook plus the canonical link is what a human posts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every platform's limits are different and none of them are characters.&lt;/strong&gt; Bluesky counts graphemes and does not shorten links, so a 90-character URL genuinely consumes 90 of your 300. Its rich-text facets index into &lt;strong&gt;byte&lt;/strong&gt; offsets, so a single em dash before a link shifts everything and the link silently breaks. Get that wrong and you find out from a user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing is per-platform and empirical.&lt;/strong&gt; The best time to post to Dev.to and the best time to post to Bluesky are not the same time, and both are properties of &lt;em&gt;your&lt;/em&gt; audience rather than of the platform. Which means it is a thing to learn from your own history, not a constant to hardcode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dead links block publishing, and should.&lt;/strong&gt; Generated copy invents URLs. A definitive 404 in a published post is embarrassing and permanent. A pre-publish link check that treats a 404 as blocking and a timeout as inconclusive costs one request per link and saves the occasional public mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure, or none of it was worth doing.&lt;/strong&gt; Views and engagement per piece, pulled from the platforms' own APIs, is what tells you whether the tutorials outperform the release notes. Without it you are automating a process with no feedback, which is just producing output faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this a trend or a niche?
&lt;/h2&gt;

&lt;p&gt;Honestly: partly a niche. Plenty of teams have a marketing function and do not need this, and plenty of projects should be promoted by a human who cares, at their own pace, and would be worse for a pipeline.&lt;/p&gt;

&lt;p&gt;But the shape generalises beyond marketing, and that is the part worth noticing. The pattern is &lt;strong&gt;automate the mechanical span around a retained human decision&lt;/strong&gt;. Dependency bots do it — detect, prepare, open a PR, wait for a human to merge. Release tooling does it. The interesting AI-adjacent tools of the last two years mostly do it, and the ones that tried to remove the human decision instead of the work around it have mostly not aged well.&lt;/p&gt;

&lt;p&gt;Content is a good fit for that pattern because the mechanical parts are large and tedious — cross-posting, canonicals, scheduling, metrics — and the judgement part is small and genuinely irreducible. Someone has to decide this is worth publishing. Nobody has to decide what the canonical URL should be.&lt;/p&gt;

&lt;p&gt;The teams that will get value from this are the ones shipping steadily with nobody assigned to say so. That is a lot of teams. It is most side projects, and a good share of small companies that are strong technically and invisible commercially.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://herald.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=herald-2026q3&amp;amp;utm_content=shipping-in-silence-treating-developer-marketing-as-a-build" rel="noopener noreferrer"&gt;Herald&lt;/a&gt;&lt;/strong&gt; is a working implementation of this. It watches project repositories, drafts posts from real commit and release activity, parks them in a review queue for a human, then publishes on a schedule to Dev.to, Hashnode, Bluesky, Mastodon, Medium, WordPress, Buttondown or a commit to your blog repo — handling canonical URLs, syndication delay, per-platform formatting and UTM attribution — and tracks views and engagement afterwards.&lt;/p&gt;

&lt;p&gt;It is FastAPI, PostgreSQL, Celery and React, with an LLM router that falls back across providers and degrades to a static template rather than failing. The whole thing is driven by an HTTP API, which means it can be scripted, or driven from an MCP client, or left to run itself. The review queue stays.&lt;/p&gt;

</description>
      <category>devto</category>
      <category>automation</category>
      <category>marketing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>OPM, Backsolve and PWERM: How Equity Value Gets Allocated Across a Cap Table — 409A Valuation Guide (Part 2)</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:05:04 +0000</pubDate>
      <link>https://dev.to/sumaninster/opm-backsolve-and-pwerm-how-equity-value-gets-allocated-across-a-cap-table-409a-valuation-guide-4fcf</link>
      <guid>https://dev.to/sumaninster/opm-backsolve-and-pwerm-how-equity-value-gets-allocated-across-a-cap-table-409a-valuation-guide-4fcf</guid>
      <description>&lt;p&gt;Part one of this series ended on a claim: for a venture-backed company that recently raised, the enterprise value is not really the hard part. Someone paid a price in an arm's-length transaction and that price is your anchor.&lt;/p&gt;

&lt;p&gt;The hard part is &lt;strong&gt;allocation&lt;/strong&gt; — turning one enterprise value into a per-share value for each of the seven classes on your cap table, each with different preferences, each converting under different conditions.&lt;/p&gt;

&lt;p&gt;This post covers the three methods that do that: the option pricing model, the backsolve, and PWERM. If you are building valuation software, this is the part you implement.&lt;/p&gt;

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

&lt;p&gt;The option pricing method rests on an observation that is obvious once stated and non-obvious before: &lt;strong&gt;every class of equity in a company with a preference stack is a call option on enterprise value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider common stock in a company with a single $10M liquidation preference. If the company sells for $8M, common receives nothing. At $12M, common receives $2M. At $50M, common receives $40M.&lt;/p&gt;

&lt;p&gt;Plot payoff against exit value and you get a hockey stick with a kink at $10M. That is precisely the payoff diagram of a call option struck at $10M. Common stock &lt;em&gt;is&lt;/em&gt; a call option on the enterprise, with the preference stack as its strike.&lt;/p&gt;

&lt;p&gt;Once you see that, Black-Scholes applies. Not by analogy — the payoff structure is genuinely identical, so the same closed-form machinery values it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breakpoints: where the waterfall changes slope
&lt;/h2&gt;

&lt;p&gt;Real cap tables do not have one kink. They have many, and finding them is the first half of the implementation.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;breakpoint&lt;/strong&gt; is an enterprise value at which the marginal distribution of an additional dollar changes. Between breakpoints, every dollar splits the same way. At a breakpoint, someone starts or stops participating.&lt;/p&gt;

&lt;p&gt;They arise from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each liquidation preference being satisfied, in seniority order.&lt;/li&gt;
&lt;li&gt;Participation caps being reached, after which participating preferred stops sharing.&lt;/li&gt;
&lt;li&gt;Each class's &lt;strong&gt;conversion indifference point&lt;/strong&gt; — the value above which converting to common pays more than taking the preference. Above it, that class behaves as common; below it, as debt-like preference.&lt;/li&gt;
&lt;li&gt;Option and warrant strikes coming into the money, which dilutes everyone above that point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting seniority right matters and is easy to get wrong. Stacked preferences pay in strict order — Series C, then B, then A. &lt;em&gt;Pari passu&lt;/em&gt; preferences share pro rata when the pool is short. Term sheets say which; cap-table software does not always record which; and reading the actual documents is unavoidable.&lt;/p&gt;

&lt;p&gt;The output is a sorted list of breakpoints and, for each interval between them, the fraction of a marginal dollar each class receives.&lt;/p&gt;

&lt;h2&gt;
  
  
  From breakpoints to per-class value
&lt;/h2&gt;

&lt;p&gt;With breakpoints in hand, OPM proceeds mechanically.&lt;/p&gt;

&lt;p&gt;Treat each breakpoint as the strike of a call option on enterprise value. Value each with Black-Scholes, using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spot&lt;/strong&gt; — enterprise value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strike&lt;/strong&gt; — the breakpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatility&lt;/strong&gt; — from comparable public companies, typically 50–80% annualised for early-stage technology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to expiry&lt;/strong&gt; — expected time to a liquidity event, usually 2–5 years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk-free rate&lt;/strong&gt; — a government yield at matching tenor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value of the tranche &lt;em&gt;between&lt;/em&gt; two adjacent breakpoints is the difference between the two call values — a call spread. Allocate each tranche's value across the classes participating in it, in their marginal proportions. Sum per class, divide by shares outstanding, apply DLOM.&lt;/p&gt;

&lt;p&gt;Three properties are worth checking in any implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The allocations sum to enterprise value.&lt;/strong&gt; The lowest breakpoint is at zero, and a call struck at zero is worth spot. If your tranches do not total spot, you have a bug — usually a missed breakpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatility is not a minor input.&lt;/strong&gt; It controls how much probability mass sits in the tail that common has a claim on. Moving it 60% → 70% can move common value 15–20%. It deserves documented sourcing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to expiry cuts both ways.&lt;/strong&gt; Longer horizons give the option more time to finish in the money, raising common value, while the discount reduces it. The net effect is model-specific and worth testing rather than assuming.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The backsolve: calibrating to reality
&lt;/h2&gt;

&lt;p&gt;Everything above needs a spot price. Where does enterprise value come from?&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;backsolve&lt;/strong&gt; — formally, the OPM backsolve method — inverts the question. Instead of estimating enterprise value and deriving the round price, take the round price as given and solve for the enterprise value that reproduces it.&lt;/p&gt;

&lt;p&gt;A recent Series B priced at $4.20 per preferred share is not an estimate. It is a transaction. So:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the full breakpoint structure including the new round.&lt;/li&gt;
&lt;li&gt;Guess an enterprise value.&lt;/li&gt;
&lt;li&gt;Run OPM. Read off the implied value of the new preferred class.&lt;/li&gt;
&lt;li&gt;Compare to the actual price paid.&lt;/li&gt;
&lt;li&gt;Adjust and repeat until they agree.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 5 is a one-dimensional root find. The function is monotonic and smooth — implied preferred value rises with enterprise value — so bisection converges reliably and Brent's method converges quickly. Guard the bracket: an implausible upper bound and a badly conditioned cap table can send a naive Newton iteration somewhere silly.&lt;/p&gt;

&lt;p&gt;Once solved, that enterprise value feeds the allocation for every other class, common included.&lt;/p&gt;

&lt;p&gt;This is the most defensible method available for a recently-funded company, and for a simple reason: it starts from a price someone actually paid, rather than from a projection someone actually wrote.&lt;/p&gt;

&lt;p&gt;Its limits are real. It assumes the round was arm's-length — a strategic investor paying for a commercial relationship, or a materially non-pro-rata insider round, breaks that. And it decays. Six months on, with the business materially changed, the backsolve anchor is stale and the market or income approach has to carry more weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  PWERM: when the outcomes are genuinely different
&lt;/h2&gt;

&lt;p&gt;OPM assumes a single continuous distribution of enterprise values. That assumption is fine for a company on an uncertain but continuous trajectory. It is wrong for a company facing a small number of discrete, qualitatively different futures.&lt;/p&gt;

&lt;p&gt;A biotech at a phase-three readout does not have a lognormal outcome distribution. It has two: approval, and not.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;probability-weighted expected return method&lt;/strong&gt; models these directly. Enumerate scenarios — IPO, strategic acquisition, secondary sale, dissolution. For each, specify a value, a date and a probability. Run the waterfall in each scenario to get per-class proceeds. Discount to present at a risk-adjusted rate. Weight by probability. Sum.&lt;/p&gt;

&lt;p&gt;PWERM's strength is that it captures structure OPM smooths away — an acquisition at $80M in eighteen months and an IPO at $400M in four years are different events, not two draws from one distribution, and the preference stack treats them very differently.&lt;/p&gt;

&lt;p&gt;Its weakness is that it is only as good as the probabilities, and the probabilities are judgement. A 40%/60% split is a defensible assumption; it is not an observation. PWERM makes the sensitivity explicit, which is honest, and it makes the number arguable, which is uncomfortable.&lt;/p&gt;

&lt;p&gt;There is also a &lt;strong&gt;hybrid&lt;/strong&gt; in common use: PWERM at the top level for genuinely discrete branches, with OPM allocating within each branch where the outcome inside that branch is continuous. Most methodologically careful reports for later-stage companies with a plausible near-term exit end up here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing, in practice
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Primary method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Priced round in the last few months&lt;/td&gt;
&lt;td&gt;Backsolve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No recent round, continuous uncertainty&lt;/td&gt;
&lt;td&gt;OPM with market/income enterprise value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small number of discrete, dissimilar outcomes&lt;/td&gt;
&lt;td&gt;PWERM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Near-term exit visible, continuous within branches&lt;/td&gt;
&lt;td&gt;Hybrid PWERM/OPM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Holding company, or worth more dead than alive&lt;/td&gt;
&lt;td&gt;Asset approach&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The choice is a disclosure item, not an internal detail. A report that does not say which method it used and why is a report that will not survive review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation notes
&lt;/h2&gt;

&lt;p&gt;Things that are obvious in retrospect and expensive to learn:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Breakpoint discovery is where the bugs live, not Black-Scholes.&lt;/strong&gt; Black-Scholes is four lines and you can test it against published values. Enumerating breakpoints across stacked and pari passu preferences, participation caps, conversion indifference and option strikes is intricate, and every cap table finds a new corner of it. Build it as a pure function from cap table to breakpoint list, and test it against hand-worked examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the invariant, not just the outputs.&lt;/strong&gt; Allocations summing to enterprise value catches a large fraction of real defects, and it holds for every cap table, so you can assert it on generated inputs as well as fixtures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the numerics out of the web layer.&lt;/strong&gt; Allocation is numerical code with strong testability requirements. Run it as a separate, versioned engine — R and Python both have the libraries and the statistical credibility — and let the application call it. You want to be able to re-run a two-year-old valuation with the engine version it originally used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store inputs, not just results.&lt;/strong&gt; Volatility, expiry, risk-free rate, DLOM basis, comparable set and the breakpoint structure are all part of the answer. A report that shows a per-share value without them cannot be defended in diligence, and neither can a database that did not keep them.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://n409.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=n409-2026q3&amp;amp;utm_content=opm-backsolve-and-pwerm-how-equity-value-gets-allocated-acro" rel="noopener noreferrer"&gt;N409&lt;/a&gt;&lt;/strong&gt; implements this stack: an R-based quant engine running Black-Scholes OPM, backsolve and the income, market and asset approaches, with AI-assisted extraction of cap tables and financials feeding it, analyst override with the computed original preserved for audit, and versioned reports published as PDFs.&lt;/p&gt;

&lt;p&gt;Part one of this series covers what Section 409A requires, what safe harbor buys you, and why common stock is worth less than preferred.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Technical explanation, not valuation advice. Method selection for a real 409A is a matter of professional judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>finance</category>
      <category>valuation</category>
      <category>startup</category>
      <category>python</category>
    </item>
    <item>
      <title>Best GST Software for Indian SMBs: A Buyer's Framework, Not a Scorecard</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:01:05 +0000</pubDate>
      <link>https://dev.to/sumaninster/best-gst-software-for-indian-smbs-a-buyers-framework-not-a-scorecard-25k</link>
      <guid>https://dev.to/sumaninster/best-gst-software-for-indian-smbs-a-buyers-framework-not-a-scorecard-25k</guid>
      <description>&lt;p&gt;Search for GST software for a small Indian business and you get two kinds of results: accounting suites that happen to file returns, and filing utilities that happen to store invoices. Both are sold as "GST software". They solve different problems, and the one you need depends on a question most comparison posts skip.&lt;/p&gt;

&lt;p&gt;That question is: &lt;strong&gt;do you have a reconciliation problem, or a bookkeeping problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is a buyer's framework rather than a scorecard, because the scorecard goes stale — pricing and feature lists in this market change every quarter, and a table of prices published today will be wrong by the time you read it. What does not go stale is the shape of the categories and the questions that separate them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure up front: I build &lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-2026q3&amp;amp;utm_content=best-gst-software-for-indian-smbs-a-buyer-s-framework-not-a" rel="noopener noreferrer"&gt;GSTBot&lt;/a&gt;, which sits in the third category below. I have tried to describe the categories the way I would want them described if I were shopping, including where GSTBot is the wrong answer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The four categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Accounting suites (Tally, Zoho Books, Busy, Marg).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are ledgers first. GST is a module. You get invoicing, inventory, payables, receivables, financial statements, and return preparation derived from the books you are already keeping.&lt;/p&gt;

&lt;p&gt;Buy one of these if your actual problem is that you do not have proper books. Nothing else on this list will fix that, and reconciliation on top of bad books produces confident, wrong answers.&lt;/p&gt;

&lt;p&gt;Their reconciliation is generally serviceable and generally shallow: match, list mismatches, done. Rule 37 tracking, proportionate reversal under 42/43 and supplier filing history are usually not there, because those are not bookkeeping features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Compliance platforms (ClearTax, Taxilla, IRIS, Cygnet).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Purpose-built for GST at volume: bulk reconciliation, e-invoicing and IRN generation, e-way bills, multi-GSTIN consolidation, ASP/GSP connectivity to file directly.&lt;/p&gt;

&lt;p&gt;These are genuinely good at the job. They are also built and priced for businesses with a compliance function — multiple GSTINs, dedicated staff, thousands of invoices a month. If that is you, this category is where you should be looking, and you should stop reading comparison posts written by smaller vendors.&lt;/p&gt;

&lt;p&gt;The mismatch for an SMB is not that they are bad. It is that you buy a compliance platform and use 15% of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Focused reconciliation tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Narrow scope: take your purchase register and your GSTR-2B, match them properly, tell you what to do about each exception, and track the reversals. No ledger, no inventory, no e-way bills.&lt;/p&gt;

&lt;p&gt;This category exists because reconciliation is the part of GST that is genuinely hard and genuinely expensive to get wrong, and because a business can have perfectly good books in Tally and still be leaking ITC.&lt;/p&gt;

&lt;p&gt;The honest limitation: if you want one system for everything, this is not it. It is a tool that sits beside your accounting system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Filing utilities and CA-side software.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Return preparation and upload, often built for practitioners filing on behalf of many clients. Cheap, functional, and largely a data-entry surface. Reconciliation, where it exists, is a spreadsheet export.&lt;/p&gt;

&lt;p&gt;Fine if a CA does your compliance and you just need to hand things over. Insufficient if you are trying to find leakage yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that actually separate them
&lt;/h2&gt;

&lt;p&gt;Feature lists all look the same. These are the questions I would ask on a demo call, and roughly what a good answer sounds like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How does your matcher handle &lt;code&gt;INV/2026/0042&lt;/code&gt; versus &lt;code&gt;INV-2026-42&lt;/code&gt;?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "it does an exact match on invoice number", the tool will hand you hundreds of false mismatches every month and you will do the real work by hand. You want normalisation — case folding, separator stripping, leading-zero handling — plus fuzzy scoring across GSTIN, date and amount together, with tolerances you can set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Do you track Rule 37 reversals?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the sharpest single question, because it separates tools that reconcile a return from tools that manage ITC. Rule 37 requires reversing credit where the supplier has not been paid within 180 days. The invoice matched perfectly, so a return-focused tool sees nothing wrong. Answering this requires reading your payables ageing, which means the tool has to care about something outside the return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Do you compute Rules 42 and 43?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only relevant if you have exempt or non-business use, but if you do, proportionate reversal is a monthly computation and doing it annually at audit is how businesses end up paying interest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What do you know about my suppliers across twelve months?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Single-period reconciliation cannot tell you that a supplier files late every quarter. Historical scoring can, and it changes what you do commercially — provisioning, payment holds, renewal decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"When an invoice does not match, what does the screen say?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare "unmatched: 412" with "412 unmatched — 340 not yet filed by 6 suppliers (chase list attached), 51 amount differences under ₹100 (bulk-accept?), 21 need review". Same data. Completely different amount of your time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What happens to my data?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You are uploading purchase registers, GSTINs and supplier relationships. Ask where it is stored, who can read it, what happens on cancellation, and whether you can export everything. Ask specifically whether your documents are used to train anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can I get my data out?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the exit path is a PDF, you are locked in. CSV or JSON export of matched and unmatched lines is the minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking, in three lines
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No proper books yet?&lt;/strong&gt; Accounting suite. Everything else is premature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple GSTINs, a compliance team, e-invoicing at volume?&lt;/strong&gt; Compliance platform. Buy the real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books are fine, but you suspect ITC is leaking and reconciliation eats two days a month?&lt;/strong&gt; Focused reconciliation tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most Indian SMBs I talk to are in the third bucket and shopping in the second, which is how you end up paying enterprise pricing for a feature set you use a tenth of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where GSTBot fits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-2026q3&amp;amp;utm_content=best-gst-software-for-indian-smbs-a-buyer-s-framework-not-a" rel="noopener noreferrer"&gt;GSTBot&lt;/a&gt; is category three, deliberately. It does bulk invoice intake with AI extraction from PDFs and photos, deterministic GSTR-2B matching with configurable tolerances, exception classification with a recommended action per line, Rule 37/42/43 tracking, and supplier health scoring built from filing history.&lt;/p&gt;

&lt;p&gt;It does not do your books, e-way bills, or direct filing to the portal. If you need those, one of the platforms in category two is a better purchase and I would rather say so than sell you the wrong thing.&lt;/p&gt;

&lt;p&gt;The reasoning behind the matching approach — the tolerance tuple, the four exception classes, why the LLM handles extraction rather than matching — is written up in the first post of this series, &lt;em&gt;"GSTR-2B Reconciliation: Where Input Tax Credit Actually Leaks"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rules, rates and due dates in Indian GST change frequently. Nothing here is tax advice; confirm your position with your CA.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>india</category>
      <category>tax</category>
      <category>saas</category>
      <category>startup</category>
    </item>
    <item>
      <title>AI Content Generation for Developer Marketing</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:00:24 +0000</pubDate>
      <link>https://dev.to/sumaninster/ai-content-generation-for-developer-marketing-38mp</link>
      <guid>https://dev.to/sumaninster/ai-content-generation-for-developer-marketing-38mp</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Indie developers and small teams often ship code but struggle to document it. Writing consistent blog posts or social copy requires time that many already lack. The result is missed visibility and a fragmented promotion strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Herald solves this by turning repository activity into publishable content automatically. When a commit lands that changes a readme, a new feature flag is added, or a release tag is pushed, a GitHub webhook triggers a FastAPI endpoint. The request payload is parsed and stored in PostgreSQL via SQLAlchemy. A Celery worker picks up the &lt;em&gt;trigger&lt;/em&gt; record, calls an AI model (OpenAI GPT‑4) to draft a markdown article, and stores the draft.&lt;/p&gt;

&lt;p&gt;The front‑end, built with React and Vite, presents a &lt;strong&gt;content calendar&lt;/strong&gt; where developers can review drafts, edit them, and schedule publication. Tailwind CSS keeps the UI concise and responsive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drafting Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tasks.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Trigger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Draft&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_draft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trigger_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Trigger&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;trigger_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a 700‑word blog post about the change: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; 
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&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;gpt-4&lt;/span&gt;&lt;span class="sh"&gt;"&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;system&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a senior dev‑blogger.&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;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="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Draft&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;trigger_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trigger_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker writes the draft to the database; the React UI fetches it via a FastAPI endpoint:&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="c1"&gt;# api.py
&lt;/span&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/drafts/{draft_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_draft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Draft&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;draft_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publishing Pipeline
&lt;/h3&gt;

&lt;p&gt;When a draft is approved and scheduled, Celery schedules a publish task. The task uses the dev.to API to create the article.&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="c1"&gt;# publish.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;DEVTO_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEVTO_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_to_devto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Draft&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;draft_id&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;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Reschedule until the time arrives
&lt;/span&gt;        &lt;span class="n"&gt;publish_to_devto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;draft_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;eta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DEVTO_TOKEN&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-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;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;data&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;article&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;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&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="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&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;devops&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;automation&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;ai&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="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api/articles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Store the dev.to URL for later metrics
&lt;/span&gt;    &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;devto_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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;-Berkeley the task is idempotent; if the API call fails, Celery retries up to 3 times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Feature
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;цию&lt;/strong&gt; GitHub webhook** – Add the Herald endpoint to your repository’s webhook settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approve drafts&lt;/strong&gt; – In the dashboard, read the AI‑generated markdown, tweak if necessary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt; – Pick a date/time; the calendar shows a 7‑day queue. The worker will publish automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track engagement&lt;/strong&gt; – After publication, Herald polls the dev.to API every 12 hours to pull the &lt;code&gt;read&lt;/code&gt; count and.patterns. The dashboard visualises engagement per post.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The process requires no manual copy‑pasting; the entire workflow runs in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; handles async HTTP requests with low latency, enabling real‑time webhook handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLAlchemy&lt;/strong&gt; models map triggers, drafts, and posts to PostgreSQL tables. A single transient table holds unprocessed triggers, removed by a sweep job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery + Redis&lt;/strong&gt; orchestrates background tasks. Workers limit concurrency to 4 to avoid hitting OpenAI or dev.to rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + Vite + Tailwind CSS&lt;/strong&gt; deliver a lightweight UI; the calendar uses &lt;code&gt;react-big-calendar&lt;/code&gt; for drag‑and‑drop scheduling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous testing&lt;/strong&gt; reaches 100 % coverage; a recent commit raised the floor to 99.9 % and introduced a hard limit on the project list query.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Optimizations
&lt;/h2&gt;

&lt;p&gt;Recent changes addressed bottlenecks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;transient&lt;/em&gt; table now reads in full via a single query, eliminating the unbounded loop that previously inflated latency.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;all my triggers&lt;/em&gt; endpoint was refactored to paginate results, reducing the 20‑fold memory spike.&lt;/li&gt;
&lt;li&gt;A hard limit on project queries prevents accidental overload.&lt;/li&gt;
&lt;li&gt;Unit tests no longer pay bcrypt 2,300 times; a mock replaces the password hash during testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These adjustments keep the system responsive even when a repository receives dozens of commits per day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Herald’s AI‑powered drafting and publishing engine eliminates the friction between code commits and marketing content. By leveraging FastAPI, Celery, and the dev.to API, developers can ship code and let the platform turn each change into a polished, scheduled blog post or social snippet. The result is continuous, automated developer marketing without a dedicated marketing team.&lt;/p&gt;

</description>
      <category>python</category>
      <category>react</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Best GST Software for Indian SMBs: A Buyer's Framework</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:00:24 +0000</pubDate>
      <link>https://dev.to/sumaninster/best-gst-software-for-indian-smbs-a-buyers-framework-1ec</link>
      <guid>https://dev.to/sumaninster/best-gst-software-for-indian-smbs-a-buyers-framework-1ec</guid>
      <description>&lt;p&gt;Search for the best GST software for Indian SMBs and you get two kinds of results: accounting suites that happen to file returns, and filing utilities that happen to store invoices. Both are sold as "GST software". They solve different problems, and the one you need depends on a question most comparison posts skip.&lt;/p&gt;

&lt;p&gt;That question is: &lt;strong&gt;do you have a reconciliation problem, or a bookkeeping problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is a buyer's framework rather than a scorecard, because the scorecard goes stale — pricing and feature lists in this market change every quarter, and a table of prices published today will be wrong by the time you read it. What does not go stale is the shape of the categories and the questions that separate them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure up front: I build &lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-2026q3&amp;amp;utm_content=best-gst-software-for-indian-smbs-a-buyer-s-framework" rel="noopener noreferrer"&gt;GSTBot&lt;/a&gt;, which sits in the third category below. I have tried to describe the categories the way I would want them described if I were shopping, including where GSTBot is the wrong answer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The four categories sold as "best GST software for Indian SMBs"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Accounting suites (Tally, Zoho Books, Busy, Marg).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are ledgers first. GST is a module. You get invoicing, inventory, payables, receivables, financial statements, and return preparation derived from the books you are already keeping.&lt;/p&gt;

&lt;p&gt;Buy one of these if your actual problem is that you do not have proper books. Nothing else on this list will fix that, and reconciliation on top of bad books produces confident, wrong answers.&lt;/p&gt;

&lt;p&gt;Their reconciliation is generally serviceable and generally shallow: match, list mismatches, done. Rule 37 tracking, proportionate reversal under 42/43 and supplier filing history are usually not there, because those are not bookkeeping features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Compliance platforms (ClearTax, Taxilla, IRIS, Cygnet).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Purpose-built for GST at volume: bulk reconciliation, e-invoicing and IRN generation, e-way bills, multi-GSTIN consolidation, ASP/GSP connectivity to file directly.&lt;/p&gt;

&lt;p&gt;These are genuinely good at the job. They are also built and priced for businesses with a compliance function — multiple GSTINs, dedicated staff, thousands of invoices a month. If that is you, this category is where you should be looking, and you should stop reading comparison posts written by smaller vendors.&lt;/p&gt;

&lt;p&gt;The mismatch for an SMB is not that they are bad. It is that you buy a compliance platform and use 15% of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Focused reconciliation tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Narrow scope: take your purchase register and your GSTR-2B, match them properly, tell you what to do about each exception, and track the reversals. No ledger, no inventory, no e-way bills.&lt;/p&gt;

&lt;p&gt;This category exists because reconciliation is the part of GST that is genuinely hard and genuinely expensive to get wrong, and because a business can have perfectly good books in Tally and still be leaking ITC.&lt;/p&gt;

&lt;p&gt;The honest limitation: if you want one system for everything, this is not it. It is a tool that sits beside your accounting system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Filing utilities and CA-side software.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Return preparation and upload, often built for practitioners filing on behalf of many clients. Cheap, functional, and largely a data-entry surface. Reconciliation, where it exists, is a spreadsheet export.&lt;/p&gt;

&lt;p&gt;Fine if a CA does your compliance and you just need to hand things over. Insufficient if you are trying to find leakage yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that actually separate them
&lt;/h2&gt;

&lt;p&gt;Feature lists all look the same. These are the questions I would ask on a demo call, and roughly what a good answer sounds like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How does your matcher handle &lt;code&gt;INV/2026/0042&lt;/code&gt; versus &lt;code&gt;INV-2026-42&lt;/code&gt;?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "it does an exact match on invoice number", the tool will hand you hundreds of false mismatches every month and you will do the real work by hand. You want normalisation — case folding, separator stripping, leading-zero handling — plus fuzzy scoring across GSTIN, date and amount together, with tolerances you can set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Do you track Rule 37 reversals?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the sharpest single question, because it separates tools that reconcile a return from tools that manage ITC. Rule 37 requires reversing credit where the supplier has not been paid within 180 days. The invoice matched perfectly, so a return-focused tool sees nothing wrong. Answering this requires reading your payables ageing, which means the tool has to care about something outside the return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Do you compute Rules 42 and 43?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only relevant if you have exempt or non-business use, but if you do, proportionate reversal is a monthly computation and doing it annually at audit is how businesses end up paying interest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What do you know about my suppliers across twelve months?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Single-period reconciliation cannot tell you that a supplier files late every quarter. Historical scoring can, and it changes what you do commercially — provisioning, payment holds, renewal decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"When an invoice does not match, what does the screen say?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare "unmatched: 412" with "412 unmatched — 340 not yet filed by 6 suppliers (chase list attached), 51 amount differences under ₹100 (bulk-accept?), 21 need review". Same data. Completely different amount of your time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What happens to my data?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You are uploading purchase registers, GSTINs and supplier relationships. Ask where it is stored, who can read it, what happens on cancellation, and whether you can export everything. Ask specifically whether your documents are used to train anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can I get my data out?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the exit path is a PDF, you are locked in. CSV or JSON export of matched and unmatched lines is the minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking, in three lines
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No proper books yet?&lt;/strong&gt; Accounting suite. Everything else is premature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple GSTINs, a compliance team, e-invoicing at volume?&lt;/strong&gt; Compliance platform. Buy the real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Books are fine, but you suspect ITC is leaking and reconciliation eats two days a month?&lt;/strong&gt; Focused reconciliation tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most Indian SMBs I talk to are in the third bucket and shopping in the second, which is how you end up paying enterprise pricing for a feature set you use a tenth of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where GSTBot fits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-2026q3&amp;amp;utm_content=best-gst-software-for-indian-smbs-a-buyer-s-framework" rel="noopener noreferrer"&gt;GSTBot&lt;/a&gt; is category three, deliberately. It does bulk invoice intake with AI extraction from PDFs and photos, deterministic GSTR-2B matching with configurable tolerances, exception classification with a recommended action per line, Rule 37/42/43 tracking, and supplier health scoring built from filing history.&lt;/p&gt;

&lt;p&gt;It does not do your books, e-way bills, or direct filing to the portal. If you need those, one of the platforms in category two is a better purchase and I would rather say so than sell you the wrong thing.&lt;/p&gt;

&lt;p&gt;The reasoning behind the matching approach — the tolerance tuple, the four exception classes, why the LLM handles extraction rather than matching — is written up in the first post of this series, &lt;em&gt;"GSTR-2B Reconciliation: Where Input Tax Credit Actually Leaks"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rules, rates and due dates in Indian GST change frequently. Nothing here is tax advice; confirm your position with your CA.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>india</category>
      <category>tax</category>
      <category>saas</category>
      <category>startup</category>
    </item>
    <item>
      <title>GSTR-2B Reconciliation: Where ITC Actually Leaks (Part 1)</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:59:51 +0000</pubDate>
      <link>https://dev.to/sumaninster/gstr-2b-reconciliation-where-itc-actually-leaks-part-1-28op</link>
      <guid>https://dev.to/sumaninster/gstr-2b-reconciliation-where-itc-actually-leaks-part-1-28op</guid>
      <description>&lt;p&gt;Every month, GSTR-2B reconciliation somewhere in India begins the same way: a finance team opens the downloaded GSTR-2B JSON, opens the purchase register next to it, and starts scrolling. Two columns of invoice numbers, two columns of amounts, and a growing suspicion that the numbers do not agree.&lt;/p&gt;

&lt;p&gt;They usually do not. And the gap between them is money — Input Tax Credit you are entitled to and did not claim, or ITC you claimed and will be asked to reverse with interest.&lt;/p&gt;

&lt;p&gt;This is the first post in a series on &lt;strong&gt;AI in tax compliance&lt;/strong&gt;. It starts with GSTR-2B reconciliation because reconciliation is where the money actually leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GSTR-2B reconciliation is reconciling against
&lt;/h2&gt;

&lt;p&gt;GSTR-2B is an auto-drafted ITC statement. Your suppliers file their outward supplies in GSTR-1; the portal aggregates everything filed against your GSTIN and hands you a &lt;strong&gt;static&lt;/strong&gt; statement for the period.&lt;/p&gt;

&lt;p&gt;Static is the important word. GSTR-2A is dynamic — it keeps changing as suppliers file late. GSTR-2B is frozen once generated, which is exactly what makes it the reference document for a return you are about to file. You are reconciling against a snapshot, not a moving target.&lt;/p&gt;

&lt;p&gt;The consequence that catches people out: since the amendment to Rule 36(4) and the insertion of Section 16(2)(aa), ITC is available essentially only to the extent it appears in GSTR-2B. An invoice sitting in your purchase register that your supplier never filed is not a claim you can make. It is a conversation you need to have with your supplier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four ways a line fails to match
&lt;/h2&gt;

&lt;p&gt;Reconciliation sounds like a single operation. It is really four different problems wearing one name, and they have completely different remedies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Exact match.&lt;/strong&gt; Invoice number, GSTIN, taxable value and tax amounts all agree. Nothing to do. In most books this is 70–85% of lines, and it is the boring majority that manual review still spends most of its time confirming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Present in both, values differ.&lt;/strong&gt; The supplier filed ₹1,18,000 where your books say ₹1,17,940. Rounding, a freight line captured differently, a rate applied at 18% on one side and 12% on the other. Small individually, systematic in aggregate. The remedy is a correction — yours or theirs — and the decision of which depends on which document is right, not which is bigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. In your books, not in GSTR-2B.&lt;/strong&gt; The supplier has not filed, has filed late, or has filed against the wrong GSTIN. This is the expensive one. You cannot claim it this period. You can claim it when it appears, subject to the Section 16(4) time limit for the financial year. Until then it is a receivable from your supplier's compliance behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. In GSTR-2B, not in your books.&lt;/strong&gt; Something was filed against your GSTIN that you did not record. Usually an invoice that never reached accounts payable. Occasionally something more interesting.&lt;/p&gt;

&lt;p&gt;The fourth category is the one people ignore because it looks like free money. It is not — claiming credit for a supply you cannot evidence is the kind of thing that comes back at assessment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching is harder than string equality
&lt;/h2&gt;

&lt;p&gt;The naive implementation compares invoice numbers. It fails immediately, because invoice numbers in the wild look 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;INV/2026/0042      INV-2026-42        inv 2026 42
2026/INV/42        42                 INV/2026/0042 (Rev 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All six can be the same invoice. Your supplier's ERP formats it one way, your data-entry formats it another, and the portal faithfully carries whatever the supplier typed.&lt;/p&gt;

&lt;p&gt;A reconciliation engine that is worth anything therefore matches on a &lt;strong&gt;tuple with tolerances&lt;/strong&gt;, not on a key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supplier GSTIN — the one field that is genuinely canonical, because it is validated at source.&lt;/li&gt;
&lt;li&gt;Normalised invoice number — case-folded, separators stripped, leading zeros removed.&lt;/li&gt;
&lt;li&gt;Invoice date, with a window. A date typed as 03/04/2026 vs 04/03/2026 is a day-month transposition, not a different invoice.&lt;/li&gt;
&lt;li&gt;Taxable value, with a rounding tolerance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it scores candidates rather than accepting or rejecting them. A pair that agrees on GSTIN, amount and date but disagrees on invoice number is almost certainly a match with a typo. A pair that agrees on invoice number and disagrees on amount by 40% is almost certainly not.&lt;/p&gt;

&lt;p&gt;This is where a language model earns its place, and it is worth being precise about where it does not. The LLM is not the matcher — a deterministic scoring function is faster, cheaper and auditable, and you want auditable. The LLM is for the messy edge: reading a scanned invoice into structured fields, deciding that "Reliance Retail Ltd" and "RELIANCE RETAIL LIMITED" are one vendor, telling you in a sentence why line 4,117 was flagged. Extraction and explanation, not arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reversals nobody tracks until the audit
&lt;/h2&gt;

&lt;p&gt;Matching gets you an eligible-ITC number. It is not your final number, because several rules claw credit back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 37 — the 180-day rule.&lt;/strong&gt; If you have not paid your supplier within 180 days of the invoice date, the ITC you claimed on it must be reversed, with interest. This is a payables problem masquerading as a tax problem, and it is invisible to anyone reconciling GSTR-2B alone, because the invoice matched perfectly. The data you need is in your ageing report, not in the return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules 42 and 43 — proportionate reversal.&lt;/strong&gt; Where inputs are used partly for exempt supplies or non-business purposes, credit is reversible in proportion. Rule 42 covers inputs and input services; Rule 43 covers capital goods, spread over their useful life. A business with any exempt turnover at all needs this computed monthly, and it is almost never computed monthly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 16(4) — the deadline.&lt;/strong&gt; Credit for a financial year cannot be claimed indefinitely. An unmatched invoice from April is a problem you can still fix; the same invoice discovered eighteen months later is not.&lt;/p&gt;

&lt;p&gt;A reconciliation that reports "eligible ITC: ₹X" without carrying these is reporting a number that will not survive contact with an assessment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supplier health is a leading indicator
&lt;/h2&gt;

&lt;p&gt;Here is the thing manual reconciliation structurally cannot do: it looks at one month.&lt;/p&gt;

&lt;p&gt;Run the same match across twelve months and per-supplier patterns fall out immediately. Some suppliers file on time, every time. Some file two months late, every time. Some file for other customers and not for you, which is a specific and quite serious signal.&lt;/p&gt;

&lt;p&gt;That history is directly actionable. A supplier who has filed late in nine of the last twelve months will file late again, and you can provision for it rather than discovering it. You can hold payment against filing. You can decide, at renewal, that a 2% better price from a chronic non-filer is not actually a better price.&lt;/p&gt;

&lt;p&gt;This is the argument for keeping reconciliation history in a database rather than in a folder of spreadsheets named &lt;code&gt;Recon_Mar26_final_v3.xlsx&lt;/code&gt;. The value is not in any one month's output. It is in the twelve months of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to automate first
&lt;/h2&gt;

&lt;p&gt;If you are building or buying this, the sequence that pays back fastest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normalise and match.&lt;/strong&gt; Deterministic scoring, tolerances on amount and date, GSTIN as the anchor. This alone removes most of the manual scrolling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify the exceptions.&lt;/strong&gt; Every unmatched line gets one of the four labels above and a suggested action. An exception list without a recommended action is just a shorter list to scroll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carry the reversals.&lt;/strong&gt; Pull payables ageing for Rule 37. Compute 42/43 if you have exempt turnover. Do this before the eligible-ITC number is shown to anyone, not after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score the suppliers.&lt;/strong&gt; Cheap to add once you have history, and it changes commercial decisions rather than just filing ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract with AI last.&lt;/strong&gt; OCR and field extraction from PDFs and photos is the flashiest part and the least load-bearing. It matters when your intake is paper. It does not fix a matcher that does not tolerate &lt;code&gt;INV-42&lt;/code&gt; vs &lt;code&gt;INV/0042&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 through 4 are ordinary engineering. Step 5 is where the model goes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-2026q3&amp;amp;utm_content=gstr-2b-reconciliation-where-itc-actually-leaks-part-1" rel="noopener noreferrer"&gt;GSTBot&lt;/a&gt;&lt;/strong&gt; implements this sequence for Indian SMBs: bulk invoice intake with AI extraction, deterministic GSTR-2B matching with tolerances, exception classification, Rule 37/42/43 tracking, and supplier health scoring built from filing history. It exists because the tools that do reconciliation properly are priced for enterprises, and the tools priced for small businesses mostly do filing.&lt;/p&gt;

&lt;p&gt;Next in this series: &lt;strong&gt;generating GSTR-1 and pre-filling GSTR-3B from matched data&lt;/strong&gt; — what can be derived safely, and what still needs a human before it goes to the portal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Filing rules and due dates change, sometimes retroactively. Treat the rule references here as orientation, not advice, and confirm the current position with your CA or the GST portal before you file.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>india</category>
      <category>tax</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Automating the Indian CA Compliance Calendar</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:59:50 +0000</pubDate>
      <link>https://dev.to/sumaninster/automating-the-indian-ca-compliance-calendar-1ibl</link>
      <guid>https://dev.to/sumaninster/automating-the-indian-ca-compliance-calendar-1ibl</guid>
      <description>&lt;p&gt;Ask a practising Chartered Accountant where the CA compliance calendar lives and you will get one of three answers: a spreadsheet, a WhatsApp group, or a diary. Often all three, disagreeing with each other.&lt;/p&gt;

&lt;p&gt;It works, in the sense that most things get filed. It works by consuming the partner's memory as a database, and it fails in a specific way: not on the big client with the obvious deadline, but on the small client nobody thought about until the 19th.&lt;/p&gt;

&lt;p&gt;This is a guide to modelling the Indian statutory compliance calendar properly — as data with rules, rather than as recurring reminders — for CAs who want to stop holding it in their heads and for developers building practice tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a CA compliance calendar is not a set of reminders
&lt;/h2&gt;

&lt;p&gt;The instinct is to put deadlines in a calendar app. It breaks quickly, for reasons that are structural rather than cosmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obligations are per-client, not per-firm.&lt;/strong&gt; GSTR-1 is the 11th for a monthly filer and the 13th of the quarter-following month under QRMP. A CA with 200 clients has some of each. One firm-wide reminder is wrong for most of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Applicability is derived, not declared.&lt;/strong&gt; Whether a client owes a tax audit depends on turnover against the Section 44AB thresholds. Whether they file ROC returns depends on being a company. Whether TDS returns are due depends on whether they deducted. These are computable from client attributes, and computing them is how you stop missing the obligation nobody remembered to add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dates move.&lt;/strong&gt; Extensions are routine in Indian compliance — sometimes days before the original date, sometimes after it. A hardcoded date is a date that will be wrong, and a system that cannot absorb an extension without editing 200 entries will not be updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A deadline is a workflow, not a moment.&lt;/strong&gt; "GSTR-3B due 20th" is the end of a chain: collect documents, reconcile, compute liability, get client approval, pay, file, confirm. The date is when the chain must have finished. Managing the date without managing the chain is how a filing is discovered to be blocked on a missing bank statement at 6pm on the due date.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data model that works
&lt;/h2&gt;

&lt;p&gt;Four entities carry it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance type&lt;/strong&gt; — the obligation in the abstract. GSTR-1, GSTR-3B, TDS payment, TDS return, ITR, tax audit report, AOC-4, MGT-7. Each has a periodicity (monthly, quarterly, annual), a due-date rule, and an applicability rule expressed against client attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client profile&lt;/strong&gt; — the attributes the applicability rules read. Entity type, GST registration and filing frequency, turnover band, TAN registration, audit applicability, financial year end. This is the input that makes obligations derivable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obligation instance&lt;/strong&gt; — one compliance type, one client, one period, with a computed due date and a status. This is the row that appears on a calendar. Generated, not typed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt; — the work. Assignee, checklist, required documents, current state. Hangs off the obligation instance, usually several per instance.&lt;/p&gt;

&lt;p&gt;The instance layer is what makes the whole thing tractable. Generate instances ahead — a rolling window of a few months, or a full year — and everything downstream becomes a query. "What is overdue" is a filter. "What is Ramesh's workload next week" is a group-by. "Which clients are blocked on documents" is a join. None of it requires anybody to remember anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Due-date rules
&lt;/h2&gt;

&lt;p&gt;Express them as rules, not constants.&lt;/p&gt;

&lt;p&gt;Typical shapes in Indian compliance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed day of the following month.&lt;/strong&gt; TDS payment by the 7th; GSTR-1 by the 11th; GSTR-3B by the 20th for monthly filers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed day after quarter end.&lt;/strong&gt; TDS returns, generally the last day of the month following the quarter, with the Q4 return running to a later date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QRMP variants.&lt;/strong&gt; Quarterly GSTR-1 by the 13th of the month following the quarter, with monthly payment challans in between.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed calendar date, annual.&lt;/strong&gt; ITR due dates differ for audit and non-audit cases; the tax audit report has its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relative to an event.&lt;/strong&gt; ROC filings key off the AGM date, not the calendar — AOC-4 within 30 days of the AGM, MGT-7 within 60. These need the event recorded before the deadline can exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three adjustments apply on top of every rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Holiday and weekend shifting.&lt;/strong&gt; Where a statute allows the next working day, encode it, with a holiday calendar that includes state holidays — a firm with clients in three states has three holiday sets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extensions as overrides.&lt;/strong&gt; An extension is a dated override on the instance, carrying its authority (the notification number) and leaving the original date visible. Never edit the rule; a rule edited for one extension quietly changes history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal buffers.&lt;/strong&gt; The date your team must finish is not the statutory date. A three-working-day internal buffer, configurable per compliance type, is the difference between a calm practice and a monthly emergency.&lt;/p&gt;

&lt;p&gt;I am deliberately not publishing a full table of current due dates. They change often enough that a table in a blog post is a liability — build the rules, and source the dates from the notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status that means something
&lt;/h2&gt;

&lt;p&gt;Four colours is the standard and it is nearly right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upcoming&lt;/strong&gt; — not yet in the working window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Due soon&lt;/strong&gt; — inside the window, work should have started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overdue&lt;/strong&gt; — past date, not filed. Escalate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filed&lt;/strong&gt; — done, with acknowledgement number and date stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a fifth, and it is the most useful one: &lt;strong&gt;blocked&lt;/strong&gt;. Work started, cannot proceed, waiting on something — usually a client document. Blocked is a different management problem from due-soon. Due-soon needs staff time. Blocked needs a phone call, and no amount of staff time will fix it.&lt;/p&gt;

&lt;p&gt;Track &lt;em&gt;why&lt;/em&gt; it is blocked, and the reminder to the client writes itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document collection is the actual bottleneck
&lt;/h2&gt;

&lt;p&gt;Every CA firm I have talked to describes the same failure mode: the filing is not late because the work is hard, it is late because the client sent the bank statement on the 19th.&lt;/p&gt;

&lt;p&gt;The pattern that works has three parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A checklist per obligation type.&lt;/strong&gt; GSTR-3B needs sales register, purchase register, and GSTR-2B. An ITR for a salaried individual needs Form 16, interest certificates, and capital-gains statements. Derive the checklist from the obligation, and you get "what is missing" as a computed field rather than a memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A client-side intake that is not WhatsApp.&lt;/strong&gt; WhatsApp fails on retrieval, not on delivery — the document arrived, and finding it two weeks later in a group with 400 messages is the problem. A per-client portal with magic-link access (no password, because a password is a support ticket waiting to happen) and upload against a named checklist item solves retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalating reminders tied to the blocked reason.&lt;/strong&gt; A reminder that says "please send documents" is ignorable. One that says "still needed for your July GST filing due the 20th: purchase register" is specific, and specificity is what gets a response.&lt;/p&gt;

&lt;p&gt;Document categorisation is a good place for a language model, and a small one is enough. Classifying an upload as bank statement, sales invoice, Form 16 or challan, and pulling out a PAN or GSTIN to attach it to the right client and period, is well within free-tier models. Nothing here needs a frontier model, and the cost per document should round to zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes
&lt;/h2&gt;

&lt;p&gt;The measurable effects, in rough order of size:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing is missed for lack of visibility.&lt;/strong&gt; Derived applicability means an obligation exists because the client's attributes imply it, not because someone remembered to add it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work levels out.&lt;/strong&gt; With instances generated a quarter ahead, the 15th-to-20th crunch is visible in advance and can be started early. Most of the panic is schedulable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client chasing becomes systematic.&lt;/strong&gt; Blocked-with-reason plus automated escalation replaces the partner remembering, on a Sunday, that Sharma &amp;amp; Sons still has not sent anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Billing stops leaking.&lt;/strong&gt; Every obligation instance is billable work. A system that records the work also records what to invoice for, which is how the small extra jobs — the ones done as a favour and never billed — stop being free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practice becomes transferable.&lt;/strong&gt; A firm whose compliance calendar lives in the senior partner's head cannot delegate, cannot scale, and cannot be sold. One that lives in a system can do all three.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://caflow.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=caflow-2026q3&amp;amp;utm_content=automating-the-indian-ca-compliance-calendar" rel="noopener noreferrer"&gt;CAFlow&lt;/a&gt;&lt;/strong&gt; is built on exactly this model: a pre-loaded Indian statutory compliance calendar with per-client derived applicability and computed due dates, tasks generated from obligations with assignment and workload views, a passwordless client portal with AI-categorised document intake, escalating reminders keyed to what is actually blocking a filing, and service-wise billing off the same records.&lt;/p&gt;

&lt;p&gt;It is FastAPI, PostgreSQL and Celery under a React front end, with the compliance-type seed applied at migration time so the calendar exists before anything else runs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Due dates and thresholds in Indian compliance change frequently, including retroactively. Build the rules; source the dates from current notifications.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>india</category>
      <category>automation</category>
      <category>productivity</category>
      <category>saas</category>
    </item>
    <item>
      <title>GSTR-2B Reconciliation: Where Input Tax Credit Actually Leaks — AI in Tax Compliance (Part 1)</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:59:19 +0000</pubDate>
      <link>https://dev.to/sumaninster/gstr-2b-reconciliation-where-input-tax-credit-actually-leaks-ai-in-tax-compliance-part-1-3h4o</link>
      <guid>https://dev.to/sumaninster/gstr-2b-reconciliation-where-input-tax-credit-actually-leaks-ai-in-tax-compliance-part-1-3h4o</guid>
      <description>&lt;p&gt;Every month, a finance team somewhere in India opens a downloaded GSTR-2B JSON, opens the purchase register next to it, and starts scrolling. Two columns of invoice numbers, two columns of amounts, and a growing suspicion that the numbers do not agree.&lt;/p&gt;

&lt;p&gt;They usually do not. And the gap between them is money — Input Tax Credit you are entitled to and did not claim, or ITC you claimed and will be asked to reverse with interest.&lt;/p&gt;

&lt;p&gt;This is the first post in a series on &lt;strong&gt;AI in tax compliance&lt;/strong&gt;. It starts with reconciliation because reconciliation is where the money actually leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GSTR-2B actually is
&lt;/h2&gt;

&lt;p&gt;GSTR-2B is an auto-drafted ITC statement. Your suppliers file their outward supplies in GSTR-1; the portal aggregates everything filed against your GSTIN and hands you a &lt;strong&gt;static&lt;/strong&gt; statement for the period.&lt;/p&gt;

&lt;p&gt;Static is the important word. GSTR-2A is dynamic — it keeps changing as suppliers file late. GSTR-2B is frozen once generated, which is exactly what makes it the reference document for a return you are about to file. You are reconciling against a snapshot, not a moving target.&lt;/p&gt;

&lt;p&gt;The consequence that catches people out: since the amendment to Rule 36(4) and the insertion of Section 16(2)(aa), ITC is available essentially only to the extent it appears in GSTR-2B. An invoice sitting in your purchase register that your supplier never filed is not a claim you can make. It is a conversation you need to have with your supplier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four ways a line fails to match
&lt;/h2&gt;

&lt;p&gt;Reconciliation sounds like a single operation. It is really four different problems wearing one name, and they have completely different remedies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Exact match.&lt;/strong&gt; Invoice number, GSTIN, taxable value and tax amounts all agree. Nothing to do. In most books this is 70–85% of lines, and it is the boring majority that manual review still spends most of its time confirming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Present in both, values differ.&lt;/strong&gt; The supplier filed ₹1,18,000 where your books say ₹1,17,940. Rounding, a freight line captured differently, a rate applied at 18% on one side and 12% on the other. Small individually, systematic in aggregate. The remedy is a correction — yours or theirs — and the decision of which depends on which document is right, not which is bigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. In your books, not in GSTR-2B.&lt;/strong&gt; The supplier has not filed, has filed late, or has filed against the wrong GSTIN. This is the expensive one. You cannot claim it this period. You can claim it when it appears, subject to the Section 16(4) time limit for the financial year. Until then it is a receivable from your supplier's compliance behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. In GSTR-2B, not in your books.&lt;/strong&gt; Something was filed against your GSTIN that you did not record. Usually an invoice that never reached accounts payable. Occasionally something more interesting.&lt;/p&gt;

&lt;p&gt;The fourth category is the one people ignore because it looks like free money. It is not — claiming credit for a supply you cannot evidence is the kind of thing that comes back at assessment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching is harder than string equality
&lt;/h2&gt;

&lt;p&gt;The naive implementation compares invoice numbers. It fails immediately, because invoice numbers in the wild look 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;INV/2026/0042      INV-2026-42        inv 2026 42
2026/INV/42        42                 INV/2026/0042 (Rev 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All six can be the same invoice. Your supplier's ERP formats it one way, your data-entry formats it another, and the portal faithfully carries whatever the supplier typed.&lt;/p&gt;

&lt;p&gt;A reconciliation engine that is worth anything therefore matches on a &lt;strong&gt;tuple with tolerances&lt;/strong&gt;, not on a key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supplier GSTIN — the one field that is genuinely canonical, because it is validated at source.&lt;/li&gt;
&lt;li&gt;Normalised invoice number — case-folded, separators stripped, leading zeros removed.&lt;/li&gt;
&lt;li&gt;Invoice date, with a window. A date typed as 03/04/2026 vs 04/03/2026 is a day-month transposition, not a different invoice.&lt;/li&gt;
&lt;li&gt;Taxable value, with a rounding tolerance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it scores candidates rather than accepting or rejecting them. A pair that agrees on GSTIN, amount and date but disagrees on invoice number is almost certainly a match with a typo. A pair that agrees on invoice number and disagrees on amount by 40% is almost certainly not.&lt;/p&gt;

&lt;p&gt;This is where a language model earns its place, and it is worth being precise about where it does not. The LLM is not the matcher — a deterministic scoring function is faster, cheaper and auditable, and you want auditable. The LLM is for the messy edge: reading a scanned invoice into structured fields, deciding that "Reliance Retail Ltd" and "RELIANCE RETAIL LIMITED" are one vendor, telling you in a sentence why line 4,117 was flagged. Extraction and explanation, not arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reversals nobody tracks until the audit
&lt;/h2&gt;

&lt;p&gt;Matching gets you an eligible-ITC number. It is not your final number, because several rules claw credit back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 37 — the 180-day rule.&lt;/strong&gt; If you have not paid your supplier within 180 days of the invoice date, the ITC you claimed on it must be reversed, with interest. This is a payables problem masquerading as a tax problem, and it is invisible to anyone reconciling GSTR-2B alone, because the invoice matched perfectly. The data you need is in your ageing report, not in the return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules 42 and 43 — proportionate reversal.&lt;/strong&gt; Where inputs are used partly for exempt supplies or non-business purposes, credit is reversible in proportion. Rule 42 covers inputs and input services; Rule 43 covers capital goods, spread over their useful life. A business with any exempt turnover at all needs this computed monthly, and it is almost never computed monthly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section 16(4) — the deadline.&lt;/strong&gt; Credit for a financial year cannot be claimed indefinitely. An unmatched invoice from April is a problem you can still fix; the same invoice discovered eighteen months later is not.&lt;/p&gt;

&lt;p&gt;A reconciliation that reports "eligible ITC: ₹X" without carrying these is reporting a number that will not survive contact with an assessment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supplier health is a leading indicator
&lt;/h2&gt;

&lt;p&gt;Here is the thing manual reconciliation structurally cannot do: it looks at one month.&lt;/p&gt;

&lt;p&gt;Run the same match across twelve months and per-supplier patterns fall out immediately. Some suppliers file on time, every time. Some file two months late, every time. Some file for other customers and not for you, which is a specific and quite serious signal.&lt;/p&gt;

&lt;p&gt;That history is directly actionable. A supplier who has filed late in nine of the last twelve months will file late again, and you can provision for it rather than discovering it. You can hold payment against filing. You can decide, at renewal, that a 2% better price from a chronic non-filer is not actually a better price.&lt;/p&gt;

&lt;p&gt;This is the argument for keeping reconciliation history in a database rather than in a folder of spreadsheets named &lt;code&gt;Recon_Mar26_final_v3.xlsx&lt;/code&gt;. The value is not in any one month's output. It is in the twelve months of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to automate first
&lt;/h2&gt;

&lt;p&gt;If you are building or buying this, the sequence that pays back fastest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normalise and match.&lt;/strong&gt; Deterministic scoring, tolerances on amount and date, GSTIN as the anchor. This alone removes most of the manual scrolling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify the exceptions.&lt;/strong&gt; Every unmatched line gets one of the four labels above and a suggested action. An exception list without a recommended action is just a shorter list to scroll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carry the reversals.&lt;/strong&gt; Pull payables ageing for Rule 37. Compute 42/43 if you have exempt turnover. Do this before the eligible-ITC number is shown to anyone, not after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score the suppliers.&lt;/strong&gt; Cheap to add once you have history, and it changes commercial decisions rather than just filing ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract with AI last.&lt;/strong&gt; OCR and field extraction from PDFs and photos is the flashiest part and the least load-bearing. It matters when your intake is paper. It does not fix a matcher that does not tolerate &lt;code&gt;INV-42&lt;/code&gt; vs &lt;code&gt;INV/0042&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 through 4 are ordinary engineering. Step 5 is where the model goes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://gstbot.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gstbot-2026q3&amp;amp;utm_content=gstr-2b-reconciliation-where-input-tax-credit-actually-leaks" rel="noopener noreferrer"&gt;GSTBot&lt;/a&gt;&lt;/strong&gt; implements this sequence for Indian SMBs: bulk invoice intake with AI extraction, deterministic GSTR-2B matching with tolerances, exception classification, Rule 37/42/43 tracking, and supplier health scoring built from filing history. It exists because the tools that do reconciliation properly are priced for enterprises, and the tools priced for small businesses mostly do filing.&lt;/p&gt;

&lt;p&gt;Next in this series: &lt;strong&gt;generating GSTR-1 and pre-filling GSTR-3B from matched data&lt;/strong&gt; — what can be derived safely, and what still needs a human before it goes to the portal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Filing rules and due dates change, sometimes retroactively. Treat the rule references here as orientation, not advice, and confirm the current position with your CA or the GST portal before you file.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>india</category>
      <category>tax</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Automating the Indian CA Compliance Calendar: GST, TDS, ROC and ITR</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:59:18 +0000</pubDate>
      <link>https://dev.to/sumaninster/automating-the-indian-ca-compliance-calendar-gst-tds-roc-and-itr-2a0h</link>
      <guid>https://dev.to/sumaninster/automating-the-indian-ca-compliance-calendar-gst-tds-roc-and-itr-2a0h</guid>
      <description>&lt;p&gt;Ask a practising Chartered Accountant how they track deadlines and you will get one of three answers: a spreadsheet, a WhatsApp group, or a diary. Often all three, disagreeing with each other.&lt;/p&gt;

&lt;p&gt;It works, in the sense that most things get filed. It works by consuming the partner's memory as a database, and it fails in a specific way: not on the big client with the obvious deadline, but on the small client nobody thought about until the 19th.&lt;/p&gt;

&lt;p&gt;This is a guide to modelling the Indian statutory compliance calendar properly — as data with rules, rather than as recurring reminders — for CAs who want to stop holding it in their heads and for developers building practice tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a recurring reminder is the wrong model
&lt;/h2&gt;

&lt;p&gt;The instinct is to put deadlines in a calendar app. It breaks quickly, for reasons that are structural rather than cosmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obligations are per-client, not per-firm.&lt;/strong&gt; GSTR-1 is the 11th for a monthly filer and the 13th of the quarter-following month under QRMP. A CA with 200 clients has some of each. One firm-wide reminder is wrong for most of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Applicability is derived, not declared.&lt;/strong&gt; Whether a client owes a tax audit depends on turnover against the Section 44AB thresholds. Whether they file ROC returns depends on being a company. Whether TDS returns are due depends on whether they deducted. These are computable from client attributes, and computing them is how you stop missing the obligation nobody remembered to add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dates move.&lt;/strong&gt; Extensions are routine in Indian compliance — sometimes days before the original date, sometimes after it. A hardcoded date is a date that will be wrong, and a system that cannot absorb an extension without editing 200 entries will not be updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A deadline is a workflow, not a moment.&lt;/strong&gt; "GSTR-3B due 20th" is the end of a chain: collect documents, reconcile, compute liability, get client approval, pay, file, confirm. The date is when the chain must have finished. Managing the date without managing the chain is how a filing is discovered to be blocked on a missing bank statement at 6pm on the due date.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data model that works
&lt;/h2&gt;

&lt;p&gt;Four entities carry it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance type&lt;/strong&gt; — the obligation in the abstract. GSTR-1, GSTR-3B, TDS payment, TDS return, ITR, tax audit report, AOC-4, MGT-7. Each has a periodicity (monthly, quarterly, annual), a due-date rule, and an applicability rule expressed against client attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client profile&lt;/strong&gt; — the attributes the applicability rules read. Entity type, GST registration and filing frequency, turnover band, TAN registration, audit applicability, financial year end. This is the input that makes obligations derivable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obligation instance&lt;/strong&gt; — one compliance type, one client, one period, with a computed due date and a status. This is the row that appears on a calendar. Generated, not typed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt; — the work. Assignee, checklist, required documents, current state. Hangs off the obligation instance, usually several per instance.&lt;/p&gt;

&lt;p&gt;The instance layer is what makes the whole thing tractable. Generate instances ahead — a rolling window of a few months, or a full year — and everything downstream becomes a query. "What is overdue" is a filter. "What is Ramesh's workload next week" is a group-by. "Which clients are blocked on documents" is a join. None of it requires anybody to remember anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Due-date rules
&lt;/h2&gt;

&lt;p&gt;Express them as rules, not constants.&lt;/p&gt;

&lt;p&gt;Typical shapes in Indian compliance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed day of the following month.&lt;/strong&gt; TDS payment by the 7th; GSTR-1 by the 11th; GSTR-3B by the 20th for monthly filers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed day after quarter end.&lt;/strong&gt; TDS returns, generally the last day of the month following the quarter, with the Q4 return running to a later date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QRMP variants.&lt;/strong&gt; Quarterly GSTR-1 by the 13th of the month following the quarter, with monthly payment challans in between.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed calendar date, annual.&lt;/strong&gt; ITR due dates differ for audit and non-audit cases; the tax audit report has its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relative to an event.&lt;/strong&gt; ROC filings key off the AGM date, not the calendar — AOC-4 within 30 days of the AGM, MGT-7 within 60. These need the event recorded before the deadline can exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three adjustments apply on top of every rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Holiday and weekend shifting.&lt;/strong&gt; Where a statute allows the next working day, encode it, with a holiday calendar that includes state holidays — a firm with clients in three states has three holiday sets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extensions as overrides.&lt;/strong&gt; An extension is a dated override on the instance, carrying its authority (the notification number) and leaving the original date visible. Never edit the rule; a rule edited for one extension quietly changes history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal buffers.&lt;/strong&gt; The date your team must finish is not the statutory date. A three-working-day internal buffer, configurable per compliance type, is the difference between a calm practice and a monthly emergency.&lt;/p&gt;

&lt;p&gt;I am deliberately not publishing a full table of current due dates. They change often enough that a table in a blog post is a liability — build the rules, and source the dates from the notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status that means something
&lt;/h2&gt;

&lt;p&gt;Four colours is the standard and it is nearly right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upcoming&lt;/strong&gt; — not yet in the working window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Due soon&lt;/strong&gt; — inside the window, work should have started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overdue&lt;/strong&gt; — past date, not filed. Escalate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filed&lt;/strong&gt; — done, with acknowledgement number and date stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a fifth, and it is the most useful one: &lt;strong&gt;blocked&lt;/strong&gt;. Work started, cannot proceed, waiting on something — usually a client document. Blocked is a different management problem from due-soon. Due-soon needs staff time. Blocked needs a phone call, and no amount of staff time will fix it.&lt;/p&gt;

&lt;p&gt;Track &lt;em&gt;why&lt;/em&gt; it is blocked, and the reminder to the client writes itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document collection is the actual bottleneck
&lt;/h2&gt;

&lt;p&gt;Every CA firm I have talked to describes the same failure mode: the filing is not late because the work is hard, it is late because the client sent the bank statement on the 19th.&lt;/p&gt;

&lt;p&gt;The pattern that works has three parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A checklist per obligation type.&lt;/strong&gt; GSTR-3B needs sales register, purchase register, and GSTR-2B. An ITR for a salaried individual needs Form 16, interest certificates, and capital-gains statements. Derive the checklist from the obligation, and you get "what is missing" as a computed field rather than a memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A client-side intake that is not WhatsApp.&lt;/strong&gt; WhatsApp fails on retrieval, not on delivery — the document arrived, and finding it two weeks later in a group with 400 messages is the problem. A per-client portal with magic-link access (no password, because a password is a support ticket waiting to happen) and upload against a named checklist item solves retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalating reminders tied to the blocked reason.&lt;/strong&gt; A reminder that says "please send documents" is ignorable. One that says "still needed for your July GST filing due the 20th: purchase register" is specific, and specificity is what gets a response.&lt;/p&gt;

&lt;p&gt;Document categorisation is a good place for a language model, and a small one is enough. Classifying an upload as bank statement, sales invoice, Form 16 or challan, and pulling out a PAN or GSTIN to attach it to the right client and period, is well within free-tier models. Nothing here needs a frontier model, and the cost per document should round to zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes
&lt;/h2&gt;

&lt;p&gt;The measurable effects, in rough order of size:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing is missed for lack of visibility.&lt;/strong&gt; Derived applicability means an obligation exists because the client's attributes imply it, not because someone remembered to add it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work levels out.&lt;/strong&gt; With instances generated a quarter ahead, the 15th-to-20th crunch is visible in advance and can be started early. Most of the panic is schedulable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client chasing becomes systematic.&lt;/strong&gt; Blocked-with-reason plus automated escalation replaces the partner remembering, on a Sunday, that Sharma &amp;amp; Sons still has not sent anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Billing stops leaking.&lt;/strong&gt; Every obligation instance is billable work. A system that records the work also records what to invoice for, which is how the small extra jobs — the ones done as a favour and never billed — stop being free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practice becomes transferable.&lt;/strong&gt; A firm whose compliance calendar lives in the senior partner's head cannot delegate, cannot scale, and cannot be sold. One that lives in a system can do all three.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://caflow.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=caflow-2026q3&amp;amp;utm_content=automating-the-indian-ca-compliance-calendar-gst-tds-roc-and" rel="noopener noreferrer"&gt;CAFlow&lt;/a&gt;&lt;/strong&gt; is built on exactly this model: a pre-loaded Indian statutory compliance calendar with per-client derived applicability and computed due dates, tasks generated from obligations with assignment and workload views, a passwordless client portal with AI-categorised document intake, escalating reminders keyed to what is actually blocking a filing, and service-wise billing off the same records.&lt;/p&gt;

&lt;p&gt;It is FastAPI, PostgreSQL and Celery under a React front end, with the compliance-type seed applied at migration time so the calendar exists before anything else runs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Due dates and thresholds in Indian compliance change frequently, including retroactively. Build the rules; source the dates from current notifications.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>india</category>
      <category>automation</category>
      <category>productivity</category>
      <category>saas</category>
    </item>
    <item>
      <title>409A Valuation for Founders: Safe Harbor, Fair Market Value and the Strike Price — 409A Valuation Guide (Part 1)</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:57:13 +0000</pubDate>
      <link>https://dev.to/sumaninster/409a-valuation-for-founders-safe-harbor-fair-market-value-and-the-strike-price-409a-valuation-34lm</link>
      <guid>https://dev.to/sumaninster/409a-valuation-for-founders-safe-harbor-fair-market-value-and-the-strike-price-409a-valuation-34lm</guid>
      <description>&lt;p&gt;A founder gets a 409A report back, sees a number, and asks the only question that matters to them: why is the common stock worth so much less than what the investors just paid?&lt;/p&gt;

&lt;p&gt;It is a good question with a real answer, and the answer is most of what a 409A valuation is actually doing.&lt;/p&gt;

&lt;p&gt;This is part one of a series on &lt;strong&gt;409A valuation&lt;/strong&gt;, written for founders and the engineers who end up building valuation systems. It covers what the rule requires, what safe harbor buys you, and what genuinely moves the number.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Section 409A requires
&lt;/h2&gt;

&lt;p&gt;IRC Section 409A governs nonqualified deferred compensation. Stock options land inside it because an option granted with a strike price below the fair market value of the underlying stock on the grant date is, in the eyes of the code, deferred compensation.&lt;/p&gt;

&lt;p&gt;If an option is caught by 409A, the consequences fall on the &lt;strong&gt;employee&lt;/strong&gt;, not the company: immediate income recognition on vesting rather than exercise, an additional 20% federal penalty tax, and interest charges. You have handed someone a tax bill for stock they cannot sell.&lt;/p&gt;

&lt;p&gt;So the compliance requirement is narrow and specific: grant at or above fair market value, and be able to defend that FMV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe harbor is about burden of proof
&lt;/h2&gt;

&lt;p&gt;There is no rule that says you must hire an appraiser. What the regulations provide are &lt;strong&gt;safe harbor presumptions&lt;/strong&gt; — methods where, if you use them, the IRS must prove your valuation was grossly unreasonable rather than you proving it was reasonable.&lt;/p&gt;

&lt;p&gt;That inversion is the entire commercial value of a 409A report. You are not buying a number. You are buying the position that the number is presumed correct.&lt;/p&gt;

&lt;p&gt;Three safe harbors exist in practice. The one nearly everyone uses is the &lt;strong&gt;independent appraisal&lt;/strong&gt; safe harbor: a valuation performed by a qualified independent appraiser, as of a date no more than twelve months before the grant.&lt;/p&gt;

&lt;p&gt;Two conditions keep it alive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The twelve-month clock.&lt;/strong&gt; A valuation older than twelve months no longer supports a grant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The material event rule.&lt;/strong&gt; Any event that would materially affect value invalidates the valuation &lt;em&gt;immediately&lt;/em&gt;, regardless of the clock. A priced round is the obvious one. So are an acquisition offer, losing your largest customer, a major regulatory decision, or a pivot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second is where companies get hurt. A round closes in month four, grants keep going out against the month-zero valuation, and eight months of option grants are sitting on a strike price the company can no longer defend.&lt;/p&gt;

&lt;p&gt;The practical rule: refresh annually, and refresh on any material event, whichever comes first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why common is worth less than preferred
&lt;/h2&gt;

&lt;p&gt;Back to the founder's question.&lt;/p&gt;

&lt;p&gt;Your Series A investors paid ₹100 — or $100, the arithmetic is currency-agnostic — for preferred stock. Your 409A comes back at $25 for common. Nothing has been done to you. The two securities are genuinely not worth the same thing, and the difference is the preferences you negotiated.&lt;/p&gt;

&lt;p&gt;Preferred stock in a typical venture deal carries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A liquidation preference.&lt;/strong&gt; In a sale, preferred gets its money back before common gets anything. At 1x non-participating on a $10M preference, the first $10M of any exit belongs to preferred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion rights.&lt;/strong&gt; Preferred converts to common when that pays better — which is to say, preferred takes whichever of the two outcomes is higher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dividend and protective provisions&lt;/strong&gt;, which matter less to the arithmetic but still carry value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Participation&lt;/strong&gt;, where negotiated, letting preferred take its money back &lt;em&gt;and&lt;/em&gt; share the remainder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common stock is the residual. It receives what is left after every preference is satisfied. In a large exit that residual is most of the value. In a modest exit it is nothing.&lt;/p&gt;

&lt;p&gt;So common is a claim on the upper tail of the outcome distribution, and preferred is a claim on the whole distribution with downside protection. A lower price for common is not a discount. It is a different security.&lt;/p&gt;

&lt;p&gt;On top of that sits a &lt;strong&gt;discount for lack of marketability&lt;/strong&gt;. Your common stock cannot be sold. There is no market, transfers need board consent, and a buyer would have to wait years for an exit. That illiquidity is worth something, and it is subtracted. DLOMs in the 20–35% range are common for early-stage private companies, derived rather than picked — typically from put-option models such as Finnerty or Chaffee, or from restricted-stock and pre-IPO studies.&lt;/p&gt;

&lt;p&gt;The combination of preference waterfall plus DLOM is what produces the gap founders find surprising. The often-quoted "common is roughly a third of preferred" is a folk rule with no authority behind it, but it exists because the mechanics repeatedly land in that neighbourhood at early stages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three approaches, and which one you will use
&lt;/h2&gt;

&lt;p&gt;The AICPA practice aid for valuing privately-held-company equity issued as compensation frames three approaches. Every 409A report uses at least one, and says which.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Market approach.&lt;/strong&gt; Value by reference to comparable transactions — either public companies trading at observable multiples, or M&amp;amp;A transactions in your sector. Requires selecting comparables that are genuinely comparable, which is where most of the judgement lives. A pre-revenue company has no meaningful multiple to apply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Income approach.&lt;/strong&gt; Discounted cash flow. Project free cash flows, discount at a rate reflecting the risk. Theoretically the purest method and practically the most sensitive to assumptions: a discount rate moved by two points and a terminal growth rate moved by one can move enterprise value by half. Useful for companies with real, forecastable revenue; close to fiction for a seed-stage company whose projections are a fundraising artifact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asset approach.&lt;/strong&gt; Net asset value. Relevant for holding companies and for businesses worth less as a going concern than in liquidation. Rarely the answer for an operating startup.&lt;/p&gt;

&lt;p&gt;For a venture-backed company that recently raised, none of these is usually the primary driver. The &lt;strong&gt;backsolve&lt;/strong&gt; is — calibrating enterprise value so that the model reproduces the price actually paid in the round. Someone with real money just told you what the equity was worth in an arm's-length transaction; the most defensible thing a valuation can do is start there.&lt;/p&gt;

&lt;p&gt;That is part two of this series: how the option pricing model, backsolve and PWERM allocate an enterprise value across a cap table, and why the same enterprise value produces different common prices under each.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually moves your number
&lt;/h2&gt;

&lt;p&gt;Ranked by how much they matter in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A priced round.&lt;/strong&gt; Dominates everything. Backsolve to the round price and the rest is allocation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preference stack structure.&lt;/strong&gt; A 1x non-participating stack and a 2x participating stack on identical enterprise value produce materially different common values. This is negotiated at the term sheet, and its effect on your employees' strike price is rarely discussed there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to liquidity.&lt;/strong&gt; The expiry input in the allocation model. A company modelled two years from exit prices common higher than one modelled five years out, all else equal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatility.&lt;/strong&gt; Sourced from comparable public companies. Higher volatility fattens the tail that common has a claim on, which raises common value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DLOM.&lt;/strong&gt; Directly subtractive, and one of the more defensible places for reasonable people to disagree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Financial performance.&lt;/strong&gt; Matters, but at early stage it mostly matters through its effect on 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice what is not on the list: how much you would like the strike price to be. The temptation to push for a low 409A so options look attractive is real and it is a bad trade. A number your appraiser cannot defend is a number that fails at diligence, and it fails at exactly the moment — an acquisition, an IPO — when you can least afford a compensation issue in the data room.&lt;/p&gt;

&lt;h2&gt;
  
  
  For the engineers
&lt;/h2&gt;

&lt;p&gt;If you are building any of this, three things are worth knowing before you start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every computed number needs a preserved original.&lt;/strong&gt; Analysts override model output — that is legitimate and expected. What is not acceptable is losing what the model said before the override. Store both, with who changed it and why. That audit trail is what a diligence team asks for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reports are versioned documents, not renders.&lt;/strong&gt; A 409A report gets reviewed, revised and reissued. Treat the deliverable as a versioned object with immutable prior states, not a PDF regenerated from current data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The quant engine should be separable.&lt;/strong&gt; Black-Scholes allocation, waterfall solving and DLOM models are numerical code that must be testable in isolation against known cases. Coupling them to your web layer means you cannot regression-test a valuation, and valuations are exactly the thing you want regression tests for.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://n409.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=n409-2026q3&amp;amp;utm_content=409a-valuation-for-founders-safe-harbor-fair-market-value-an" rel="noopener noreferrer"&gt;N409&lt;/a&gt;&lt;/strong&gt; is a valuation platform built on that structure: AI extraction and normalisation of uploaded cap tables, financials and projections; comparable selection; an R-based quant engine running OPM and the income, market and asset approaches; analyst override with the computed original preserved; and versioned report drafting through to published PDF. It covers 409A alongside ASC 718/820, gift and estate, QSBS and EMI/CSOP.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a technical explanation, not tax or valuation advice. 409A compliance depends on your specific facts — work with a qualified appraiser and your counsel.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>finance</category>
      <category>founders</category>
      <category>valuation</category>
    </item>
    <item>
      <title>409A Valuation</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:56:42 +0000</pubDate>
      <link>https://dev.to/sumaninster/409a-valuation-46jg</link>
      <guid>https://dev.to/sumaninster/409a-valuation-46jg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to 409A Valuation
&lt;/h2&gt;

&lt;p&gt;The 409A valuation process can be complex and time-consuming for startup founders, CFOs, and valuation analysts. Section 409A of the Internal Revenue Code requires companies to determine the fair market value of their stock options and other equity-based compensation. Non-compliance can result in severe penalties, making it essential to get it right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem of Manual Valuation
&lt;/h2&gt;

&lt;p&gt;Manual valuation methods can be prone to errors, and the process can be lengthy. Moreover, the safe harbor provision of Section 409A requires that valuations be performed by a qualified independent appraiser, which can add to the cost and complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  How N409 Solves the Problem
&lt;/h2&gt;

&lt;p&gt;N409 is an AI-powered 409A valuation platform that automates the valuation process using Monte Carlo simulations and DLOM calculations. The platform provides professional valuation reports, ensuring compliance with Section 409A requirements. With N409, startup founders, CFOs, and valuation analysts can streamline the valuation process, reduce errors, and minimize the risk of non-compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using N409
&lt;/h2&gt;

&lt;p&gt;The N409 platform is built with a range of technologies, including TypeScript, Node.js, PostgreSQL, Redis, Terraform, R, and OpenTelemetry. The platform is designed to be user-friendly, with a simple and intuitive interface that makes it easy to navigate and use. The cap table feature allows users to easily manage their equity and track changes over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of N409
&lt;/h2&gt;

&lt;p&gt;By automating the 409A valuation process, N409 saves time and reduces the risk of errors. The platform also provides a secure and reliable way to store and manage valuation data, ensuring that sensitive information is protected. With N409, users can focus on growing their business, rather than getting bogged down in complex valuation calculations.&lt;/p&gt;

</description>
      <category>n409</category>
      <category>valuation</category>
      <category>compliance</category>
    </item>
    <item>
      <title>Marketing Automation for Developers: Draft Preview</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:56:41 +0000</pubDate>
      <link>https://dev.to/sumaninster/marketing-automation-for-developers-draft-preview-51b3</link>
      <guid>https://dev.to/sumaninster/marketing-automation-for-developers-draft-preview-51b3</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Indie developers ship code faster than they write about it. When a commit lands, Herald automatically drafts a blog post and social copy, but the author still needs to review the content before publishing. In a team that may have reviewers without a Herald account, the usual workflow is to email a raw markdown file or push a PR, which is clunky and error‑prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shareable Preview Feature
&lt;/h2&gt;

&lt;p&gt;Herald solves this by generating a one‑time, signed URL that anyone can open in a browser to see a rendered preview of the draft. The link contains no user credentials, so it works even for people who never signed up for Herald. The URL is valid for 24 hours, after which the token expires and the preview is revoked.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Draft Creation&lt;/strong&gt; – When a push event is received, the FastAPI endpoint &lt;code&gt;/api/v1/webhooks/github&lt;/code&gt; parses the payload and creates a &lt;code&gt;Draft&lt;/code&gt; record in PostgreSQL via SQLAlchemy.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhooks/github&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;github_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GitHubPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit_msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# to be filled by AI later
&lt;/span&gt;                     &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       &lt;span class="c1"&gt;# Trigger AI content generation in the background
&lt;/span&gt;       &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI Content Generation&lt;/strong&gt; – A Celery task calls the AI model (e.g., OpenAI GPT‑4) to produce the first‑draft post. The.secondary image is stored in the &lt;code&gt;Draft&lt;/code&gt; table.
Leaks: The Celery worker runs on the same Redis broker used for scheduled communiquer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Generation&lt;/strong&gt; – When the AI task completes, a signed JWT token is created with the &lt;code&gt;draft_id&lt;/code&gt; and an &lt;code&gt;exp&lt;/code&gt; claim of  الو 24 h. The token is stored in the draft record for audit.
&lt;/li&gt;
&lt;/ol&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;create_preview_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="n"&gt;payload&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;draft_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Preview Endpoint&lt;/strong&gt; – The FastAPI route &lt;code&gt;/preview/{token}&lt;/code&gt; decodes the token, fetches the draft, and renders it with a React component.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/preview/{token}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&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="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&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;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
       &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&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;Draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft_id&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;draft&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
           &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TemplateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;preview.html&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;draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;React Preview UI&lt;/strong&gt; – The front‑end uses Tailwind CSS for styling and Vite for fast builds. The preview page shows the title, generated markdown rendered to HTML, the scheduled publish date (content calendar), and a “Publish to Dev.to” button that posts via the Dev.to API.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-6 max-w-4xl mx-auto bg-white shadow rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold mb-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"prose max-w-none"&lt;/span&gt; &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-6 flex justify-between items-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-gray-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Scheduled for &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;publishToDevto&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Publish to Dev.to&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integration with the Rest of Herald
&lt;/h2&gt;

&lt;p&gt;The preview link is part of the content calendar workflow. Once a reviewer approves the preview, the author can click the publish button, which triggers a Celery task that calls the Dev.to API to create a public post. The same workflow is used for Medium or other platforms.&lt;/p&gt;

&lt;p&gt;The feature relies on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; – to expose φω webhooks and preview routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLAlchemy&lt;/strong&gt; – ORM for the &lt;code&gt;Draft&lt;/code&gt; model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; – persistent storage of drafts, tokens, and schedule data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery &amp;amp; Redis&lt;/strong&gt; – background tasks for AI generation and publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + Vite + Tailwind CSS&lt;/strong&gt; – fast, modern preview UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Value to Engineers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero‑friction review&lt;/strong&gt; – Anyone can view a draft without registering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic timing&lt;/strong&gt; – Reviewers can see the exact publish date from the content calendar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; – Tokens are signed and time‑limited, eliminating the need for password sharing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt; – The same API and Celery workers handle dozens of drafts per minute, making it suitable for small teams that ship side projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shareable preview link is a focused, low‑overhead solution that fits neatly into Herald’s AI‑driven marketing automation stack, giving developers a quick way to validate content before it hits Dev.to, Medium, or other channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Herald’s draft preview feature demonstrates how a simple signed‑URL pattern can turn a complex review process into a single click experience, all while staying within the same technology stack that powers the rest of the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/" rel="noopener noreferrer"&gt;FastAPI docs – JWT authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html" rel="noopener noreferrer"&gt;Celery beat – scheduled tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.dev.to/api/"&gt;Dev.to API – articles endpoint&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fastapi</category>
      <category>sqlalchemy</category>
      <category>celery</category>
      <category>react</category>
    </item>
  </channel>
</rss>
