If you have a Health Savings Account and you’re playing the long game — paying medical expenses out of pocket, letting the HSA compound tax-free, and planning to reimburse yourself years later — you already know the paperwork problem.
The IRS doesn’t require you to submit receipts when you make a withdrawal, but you do need to be able to produce them if you’re ever audited. That means keeping documentation for potentially decades.
Most people handle this with a folder on their desktop and good intentions. I built something better.
What It Does HSA Wizard is a Google Apps Script system that runs entirely inside Google Sheets and Gmail. You forward a receipt or Explanation of Benefits email to a dedicated address, and the system handles everything else: ∙ Saves the attachment to a structured Google Drive folder (organized by year) ∙ Sends it through Gemini AI to parse the relevant details ∙ Logs a row in a Google Sheet: date of service, patient, provider, category, amount, payment card, and a direct link to the file ∙ Handles multi-line receipts (multiple service dates on one document) as separate sheet rows ∙ Distinguishes receipts from EOBs and logs them differently ∙ Deduplicates aggressively — by file hash, file ID, and a composite key of date/patient/amount/category ∙ Sends a daily digest email summarizing what was processed, with a persistent “Needs Review” section for anything the AI flagged as ambiguous ∙ Maintains a Summary tab with year-over-year totals
The whole thing runs on time-based triggers. Once set up, you never touch it. You just forward emails, sit back, and relax.
Why I Built It I’m in the “pay out of pocket and let it grow” camp with my HSA. The math is straightforward: HSA contributions are pre-tax, the money grows tax-free, and qualified withdrawals are tax-free. There’s no deadline to reimburse yourself. You can pay a bill in 2025 and pull the equivalent amount out tax-free in 2065… as long as you have the documentation.
The obvious catch is the documentation. A shoebox of PDFs technically works, but it doesn’t give you any visibility into what you’ve accumulated, what you’ve spent, or whether you could actually produce the records under audit pressure.
I wanted a system that was automatic, searchable, and auditable. Spreadsheets are auditable. Drive is searchable. Gemini can read a PDF. Google Apps Script can wire it all together for free.
How It Works The architecture is a three-stage pipeline:
Stage 1: Email → Drive
A Gmail label HSA_Wizard_Pending acts as the queue. The script checks every minute for emails sent to your forwarding alias (yourname+HSAForwarding@gmail.com), applies the pending label, then every five minutes processes those threads, saving attachments to Drive and moving threads to another label calls HSA_Wizard_Processed.
Filtering happens here: PDFs always pass through, images only pass if they’re over 25KB (this eliminates company logos that get attached to billing emails).
Stage 2: Drive → Sheet (Gemini parsing)
Every 30 minutes, the script scans Drive for files that haven’t been logged yet. Each file gets uploaded to the Gemini Files API, then a structured prompt asks Gemini to return JSON with the parsed fields. The prompt distinguishes between receipts (parse everything including amount) and EOBs (parse dates and provider, leave amount null — EOBs aren’t payments), and handles multi-line items by returning an array.
The parsed JSON is validated and written to the sheet. Duplicate checks run at three levels: file ID, content hash, and composite key. Files get renamed semantically and moved into year subfolders.
Stage 3: Sync and digest
The same 30-minute trigger handles reconciliation: if a file was deleted from Drive, the corresponding row is deleted from the sheet. If a file was renamed, the sheet updates. A Summary tab is refreshed with per-year totals.
The daily digest (7 AM trigger) reads the sheet, pulls everything processed in the last 24 hours, and emails an HTML summary. Any row where Gemini set flag_for_review: true appears in a persistent alert section that recurs every day until you clear the flag in the sheet.
The Stack ∙ Google Apps Script — runtime, triggers, Gmail/Drive/Sheets API access ∙ Gemini 2.5 Flash via the Generative Language API — document parsing (free tier is sufficient) ∙ Google Drive — file storage, organized into year subfolders automatically ∙ Google Sheets — the log and summary, with a sidebar control panel for setup
No external servers. No subscriptions. No data leaving Google’s infrastructure (Gemini is Google). The only cost is the Gemini API key, which at personal-use volume runs well within the free tier.
What Gemini Actually Parses Each document gets sent to Gemini with a structured prompt that asks for a JSON response conforming to this schema:
{ “document_type”: “Receipt | EOB | Unknown”, “line_items”: [ { “appointment_date”: “YYYY-MM-DD”, “payment_date”: “YYYY-MM-DD”, “patient_name”: “string”, “provider_name”: “string”, “description”: “string”, “category”: “Doctor | Vision | Prescription | …”, “amount”: “125.00”, “card_last4”: “string”, “additional_notes”: “string”, “flag_for_review”: true } ] }
The category list is constrained to a fixed set of HSA-eligible categories. Multi-service-date documents produce multiple line items. Unknown documents (the inevitable logo attachment that slips past the size filter) get flagged with a note about what Gemini thinks it is.
In practice the parsing accuracy is high. Medical billing documents are fairly standardized. The main failure mode is handwritten receipts or poorly-formatted provider portals, which Gemini usually handles but flags appropriately.
The Setup Setup takes about 10 minutes and is guided by a sidebar control panel inside Google Sheets:
- Get a free Gemini API key from Google AI Studio
- Open the sheet, paste your API key into the sidebar
- Create or link a Google Drive folder for receipt storage
- Optionally enter your family’s last name(s) for patient name normalization
- Click “Install Triggers” — the four time-based triggers are registered automatically
After that, add yourname+HSAForwarding@gmail.com to your Gmail contacts as “HSA Forwarding” so it’s easy to select when forwarding emails.
What It Doesn’t Do A few honest caveats:
It doesn’t file your taxes or guarantee IRS compliance. This is a documentation and organization tool. What constitutes a qualified HSA expense is a tax question — consult a tax advisor if you’re unsure whether something qualifies.
It doesn’t handle receipts you’ve already paid by the time you set it up. There’s a manual backfill function in the Advanced panel that processes all existing files in your Drive folder, but you’d need to get those files there first.
Gemini occasionally misreads documents. The flag-for-review column exists for a reason. Treat anything flagged as needing manual verification before you rely on it. It’s a power-user tool. If the words “Google Apps Script” and “API key” don’t mean anything to you, the setup will be friction. I’ve tried to minimize this with the sidebar UI, but this isn’t a consumer app.
Getting the Code The full source — Code.gs and Sidebar.html — is available on GitHub: github.com/tylerclass/hsa-wizard
The README has complete setup instructions. If you run into issues, open an issue on the repo.
One Last Thing The HSA “pay and hold” strategy only works if you actually keep the receipts. The friction of maintaining documentation is exactly why most people don’t do it. This system removes that friction completely — forward the email, it’s logged. That’s the whole job.
If you’re already maxing your HSA and investing it, this is the missing piece.