Invoice Purgatory: How I escaped the monthly click-fest by building my own invoicing tool

Invoice Purgatory: How I escaped the monthly click-fest by building my own invoicing tool

发票炼狱:我是如何通过自建发票工具逃离每月“点击苦役”的

Every month, same ritual. Open the browser, navigate to the invoice generator, update the date, bump the invoice number, change the line item description, download the PDF, open my email, attach it, send it. It’s maybe five minutes. But it’s five minutes of pure purgatory. So I built my own. And I kind of love it. 每个月,同样的仪式:打开浏览器,进入发票生成器,更新日期,增加发票编号,修改项目描述,下载 PDF,打开邮箱,添加附件,发送。这可能只需要五分钟,但这五分钟简直是纯粹的炼狱。所以我决定自己动手做一个,而且我竟然有点爱上它了。

A little context: I’m a Senior Support Engineer; I’m not a full-blown developer, but I know how to build things — and there’s always the good ol’ Claude Code to help me ship faster. This project started as a weekend idea and turned into something I actually use every month. 简单介绍一下背景:我是一名高级支持工程师;我不是那种全职开发人员,但我知道如何构建东西——而且还有好用的 Claude Code 帮我加速交付。这个项目最初只是一个周末的灵感,最后却成了我每个月真正会使用的工具。

My first instinct was to just replicate the online invoice generator — a clean web UI where I could fill in the details, hit a button, and get a PDF in my inbox. Full control over the template, no third-party tool, no clicking through the same form every month. And I built that. It works great. It remembers everything I’ve ever typed into it, has a light/dark theme, validates the recipient email, and sends the PDF directly to finance with one click. 我的第一直觉是复刻那个在线发票生成器——一个简洁的 Web 界面,我可以在上面填好详情,点击按钮,然后 PDF 就会发送到我的收件箱。这样我可以完全控制模板,无需第三方工具,也不用每个月重复点击同样的表单。我确实做出来了,而且效果很好。它能记住我输入过的所有内容,支持亮/暗色主题,能验证收件人邮箱,并且一键将 PDF 直接发送给财务部门。

But halfway through I realized — I live in the terminal. Why am I opening a browser every month to fill out a form when I could just type invoicer and answer three prompts? So the CLI became the real deliverable. 但在开发到一半时我意识到——我平时大部分时间都在终端里工作。既然我只需要输入 invoicer 并回答三个提示就能搞定,为什么每个月还要打开浏览器去填表呢?于是,CLI(命令行界面)成了我真正的交付成果。

What I actually use: 我实际使用的流程:

invoicer
? Invoice number: 5
? Month: May 2026
? Amount (USD): 1000000000 USD
? From: Eli Payano
? Bill to: Company XYZ
? Bank account: BANK USD
? Send to: finance@company.com
? Send via email? Yes
Generating PDF...
Sending email...
✓ Invoice sent to finance@company.com

That’s it. The month defaults to the current month. The due date is automatically set to the last day of that month. My sender details, client details, and bank accounts are all saved in an invoice.config.json — I pick them from a list with arrow keys. The only things I ever change are the invoice number, the month, and the amount. From zero to sent in about 5 seconds. 就是这样。月份默认为当前月份,截止日期自动设为当月最后一天。我的发件人信息、客户详情和银行账户都保存在 invoice.config.json 中——我只需用方向键从列表中选择即可。我唯一需要修改的只有发票编号、月份和金额。从零到发送完成,大约只需 5 秒。

How it works

工作原理

The stack is a pnpm monorepo with four packages: 技术栈是一个包含四个包的 pnpm monorepo:

  • web — React + Vite invoice UI
  • api — Express backend
  • cli — the CLI tool
  • shared — the HTML template, PDF generation, and email sending

The shared package is my favorite part. The HTML invoice template lives there once and is used by both the web app and the CLI. Puppeteer converts it to a PDF, Resend delivers it. Change the template in one place, everything updates. shared 包是我最喜欢的部分。HTML 发票模板只存放在这里,Web 应用和 CLI 都会调用它。Puppeteer 将其转换为 PDF,Resend 负责发送。只需在一处修改模板,所有地方都会同步更新。

For PDF generation I went with Puppeteer over something like jsPDF because it renders HTML exactly like a browser would — fonts, layout, everything. The output looks exactly like the web UI preview. For email I went with Resend over Gmail + Nodemailer because the developer experience is just better. One API key, done. No App Passwords, no OAuth, no wrestling with Google’s security settings. 在 PDF 生成方面,我选择了 Puppeteer 而不是 jsPDF 之类的工具,因为它能像浏览器一样精确渲染 HTML——包括字体、布局等所有细节。输出结果与 Web UI 的预览完全一致。在邮件发送方面,我选择了 Resend 而不是 Gmail + Nodemailer,因为前者的开发者体验更好。一个 API Key 就搞定,无需应用专用密码,无需 OAuth,也不用去折腾 Google 的安全设置。

The web UI uses IndexedDB instead of localStorage for caching the draft — more storage, async API, survives browser storage pressure. Every keystroke is saved automatically so I never lose a half-filled invoice. Web UI 使用 IndexedDB 而不是 localStorage 来缓存草稿——它拥有更大的存储空间、异步 API,并且在浏览器存储压力较大时依然稳定。每一次按键都会自动保存,所以我再也不会丢失写了一半的发票了。

What I love about it

我喜欢它的原因

I own the whole thing. The template is mine. The PDF looks exactly how I want it to look. There’s no “upgrade to Pro to remove the watermark.” There’s no terms of service change that breaks my workflow. It runs locally, it’s fast, and it does exactly one thing well. And honestly — the fact that I can type invoicer from anywhere on my machine and have an invoice in someone’s inbox less than 5 seconds later still makes me happy every time. 我拥有整个工具的所有权。模板是我的,PDF 的外观完全符合我的要求。没有“升级到专业版以去除水印”的烦恼,也不会因为服务条款的变更而导致工作流中断。它在本地运行,速度极快,并且专注于做好这一件事。老实说,无论我在电脑的哪个位置,只要输入 invoicer,不到 5 秒钟发票就能出现在对方的收件箱里,这种感觉每次都让我很开心。

If you’re the kind of person who’d rather stay in the terminal than open a browser, and you’re still using online tools that force you to context switch — maybe this is a good place to start. 如果你也是那种宁愿待在终端也不愿打开浏览器的人,而且还在使用那些强迫你频繁切换上下文的在线工具——也许这是一个不错的起点。

What’s next

未来计划

I just published it to npm — npm install -g @eli7pm/invoicer and you’re good to go. Try it out and let me know what you think in the comments. The full source is on GitHub: github.com/eli7pm/invoicer 我已经把它发布到了 npm 上——执行 npm install -g @eli7pm/invoicer 即可使用。欢迎试用并在评论区告诉我你的想法。完整源码在 GitHub 上:github.com/eli7pm/invoicer