DEV Community

Cover image for How I built a free invoice generator in 2 weeks (and launched it on Product Hunt)
Jay Bamroliya
Jay Bamroliya

Posted on

How I built a free invoice generator in 2 weeks (and launched it on Product Hunt)

Two weeks ago I was a developer who could build almost anything — but had never shipped my own product. So I stopped making excuses and built one, start to finish, in public.

It's called Billmint — a free invoice generator. No signup, works in any country with the right tax, and downloads a clean PDF. Here's how I built it, the technical decisions, and what I learned.

The problem

Every invoice tool I tried wanted me to sign up, add a card, or sit through ads — just to send one invoice. And most of them handled taxes like GST and VAT badly. I wanted something instant and free.

The stack (deliberately boring)

  • TypeScript + Vite for the front end — no framework needed for a form-to-PDF tool
  • jsPDF + jspdf-autotable for PDF generation, entirely in the browser
  • Netlify to deploy (drag-and-drop the dist/ folder — live in seconds)
  • Netlify Forms for an email waitlist with zero backend

No React, no server, no database. A single-page tool that does one thing well.

The interesting part: getting tax right

Most generic tools get Indian GST wrong. It needs to split into CGST + SGST for intra-state sales, or a single IGST line for inter-state — while other countries use a single VAT or Sales Tax line. I put all of that behind one pure function so the on-screen preview and the PDF can never disagree:

export function taxBreakdown(inv: Invoice): TaxLine[] {
  const cfg = COUNTRIES[inv.country];
  const { taxTotal } = computeTotals(inv);

  // India, intra-state: split into equal CGST + SGST
  if (cfg.splitTax && !inv.interState) {
    const half = taxTotal / 2;
    return [
      { label: `CGST (${inv.taxRate / 2}%)`, amount: half },
      { label: `SGST (${inv.taxRate / 2}%)`, amount: half },
    ];
  }

  // India inter-state → IGST; everywhere else → one line (VAT / Sales Tax)
  const label = cfg.splitTax ? 'IGST' : cfg.taxLabel;
  return [{ label: `${label} (${inv.taxRate}%)`, amount: taxTotal }];
}
Enter fullscreen mode Exit fullscreen mode

Adding a new country is just one entry in a COUNTRIES config (label, rate, currency, locale). That's what makes it "work anywhere."

A gotcha: the ₹ symbol in PDFs

jsPDF's default font is ASCII-only, so the rupee symbol ₹ renders as garbage. Rather than embed a whole Unicode font on day one, I render amounts using the currency code via Intl.NumberFormat with currencyDisplay: 'code' — so the PDF shows INR 1,234.00 cleanly. On-screen (real fonts) it still shows ₹. A small trade-off that shipped faster.

Little touches that mattered

  • localStorage profile — remembers the sender's details, logo, and accent colour so returning users don't re-type anything
  • Logo upload + accent colours — read the file as a data URL, show it in the preview and drop it into the PDF with doc.addImage
  • A "Made with Billmint" footer on every PDF — a tiny growth loop: every invoice a user sends quietly markets the tool

SEO from day one

I added keyword-targeted static landing pages (/freelancers/, /vat/, /gst/), a sitemap, structured data (JSON-LD), and Open Graph tags — then verified the property in Google Search Console. Won't rank overnight, but the foundation compounds.

The launch

I launched on Product Hunt, and submitted to a few directories for backlinks. In the first week, traffic went up ~5x — real users from India, the US, Nigeria, Pakistan, and more. Modest absolute numbers, but genuine global validation that people will use it.

Lessons

  1. "It already exists" isn't a reason to quit — it's proof people want it. You win by being simpler and actually shipping.
  2. Ship boring, ship fast. No framework, no backend, live in days.
  3. The product isn't the hard part — distribution is. Building took a few days; getting people to find it is the ongoing work.

Try it

getbillmint.netlify.app — free, no signup. I'd genuinely love feedback: what's missing, and would you use it over your current tool? 🙏

Top comments (0)