📥 Parse receipt emails into meaningful metadata in Lunch Money
Email to Lunch Money is a small Cloudflare worker application that accepts various receipt emails via an email trigger and associates metadata from the receipt to transactions in Lunch Money.
Tip
Want to deploy this service yourself? There is a detailed guide in SETUP.md with step-by-step instructions for setting up your own instance.
This may be useful for those who want to answer questions like:
What did I buy on Amazon for $75? Where did I go on that $12.50 Lyft ride?
| Payee | Amount | Notes |
|---|---|---|
| Amazon | $43.21 | |
| United Airlines | $355.66 | |
| Cloudflare | $25.20 | |
| Lyft | $8.50 | |
| Apple | $9.99 | |
| Steam | $5.43 |
| Payee | Amount | Notes |
|---|---|---|
| Amazon | $28.22 | Mise En Scene Hair Serum (113-7795219-8445010) |
| Amazon | $14.99 | CERRXIAN power adapter (113-5327144-6942647) |
| United Airlines | $355.66 | EWR → SFO (ORCQG9) |
| Cloudflare | $25.20 | prolink.tools renewal (IN-48951432) |
| Lyft | $8.50 | 186 States St, San Francisco, CA → 882 Sutter St, San Francisco, CA [16:40, 27m] |
| Apple | $9.99 | iCloud+ with 2TB storage |
| Steam | $5.43 | Geometry Dash |
The general idea is that you send various types of receipts to this service. Support for the following emails is currently implemented
-
Airline receipts. Extracts flight details from confirmation/receipt emails for United Airlines, Delta Air Lines, Alaska Airlines, American Airlines, and Southwest Airlines. Adds route (origin → destination) and confirmation code as transaction notes.
-
Amazon order emails. Orders with multiple items have their associated Lunch Money transactions split into a single transaction for each item. A note is added to transactions with a shortened item name and order number.
-
Booking.com receipts (USD only). Adds property name, check-in/check-out dates, and number of nights as a note to Booking.com transactions. Currently only processes USD receipts.
-
Cloudflare invoices. Extracts invoice details from PDF invoice attachments. Domain renewals and other services are added as transaction notes with invoice IDs.
-
Lyft rideshare and Bike rides. A note is added to each transaction with the start and end location, time of ride, and duration.
-
Apple receipts. Adds the name of the app, subscription, or in-app purchase as a note to the Apple transaction.
-
Steam purchase emails. Extracts the game name from purchase confirmation emails and adds it as a note to the Steam transaction.
-
Twitch invoice receipts. Extracts the paid amount and purchased item description (for example, subscriptions or Bits) and adds it as a note to the Twitch transaction.
-
Uber rideshare receipts. A note is added to each transaction with the start and end location, time of ride, and duration.
Some emails I would like to add
- All square receipt emails. Since many places use Square's point of sales terminals, we can record receipt details on transactions.
-
Receive receipt emails for some service such as Amazon or Lyft. These typically are sent automatically, though if disable, you can likely re-enable them in the settings of whatever the service is.
-
Setup Gmail filters to automatically label receipt emails to be forwarded. I use gmailctl to declare my filters, though you can just as easily configure this in the Web UI. Emails to be forwarded are labeled with
Fwd / Lunch Money. You can find my filters here -
A Google App Script is used to look for emails labeled as
Fwd / Lunch Moneyand it POSTs the entire raw email to the Cloudflare Worker's HTTP endpoint.The App Script is in
./google-app-script. -
The worker receives the email and determines which available Email Processor is able to parse the email. Processors typically look at the from header and subject, though they can use any part of the email to determine if they can handle the email.
-
Each processor may do their own processing:
-
amazonparses out the items ordered from the plain text email and passes the text to the OpenAI API to get a shortened version of the item names (since amazon items typically have very long names). -
cloudflareextracts text from PDF attachments using pdfjs-serverless and uses OpenAI to parse structured invoice data including line items and costs. -
lyft-bikeconverts the HTML email to text (as they do not send plain text variants) and simply uses regex to extract the ride metadata. -
steamconverts the HTML email to text and uses regex to extract the game name and purchase total.
-
-
A "Lunch Money Action" is stored in a database to be processed once the transaction posts and is synchronized to Lunch Money.
-
The worker has a scheduled execution that looks for pending Lunch Money Actions and uses matching rules in the action (typically payyee name and amount) to match transactions in Lunch Money. Once found the transaction is updated according to the action
Actions currently support:
- Splitting transactions
- Adding notes to transactions
Currently, processors use exact amount matching to find corresponding transactions in Lunch Money. This works well when the receipt amount matches the transaction amount exactly (e.g., USD transactions in USD accounts).
However, this approach fails for:
- Foreign currency transactions: A Booking.com receipt in CAD/EUR/JPY won't match USD transactions in Lunch Money
- Currency conversion fees: The final transaction amount may differ slightly from the receipt due to exchange rates and fees
- Dynamic exchange rates: Exchange rates change between booking and charging
Proposed solution: Implement flexible matching strategies per processor that can:
- Accept an approximate amount range (e.g., ±5% for currency conversions)
- Use currency conversion APIs to estimate expected amounts
- Match based on date proximity + payee when exact amounts aren't available
- Allow processors to specify their matching confidence level
This would enable processors like booking to handle international receipts by:
- Detecting non-USD currency in receipt (CAD $756.16)
- Converting to approximate USD range using current rates (~$540-560 USD)
- Matching transactions within that range + date proximity
- Attaching receipt details even when exact amounts don't match
INGEST_TOKEN- Authentication token for the /ingest endpoint (generate a secure random token)LUNCHMONEY_API_KEY- Get this in your lunchmoney settingsOPENAI_API_KEY- Needed for processors that talk to OpenAITELEGRAM_TOKEN- Optional Telegram bot token for old-action notificationsTELEGRAM_CHAT_ID- Optional Telegram chat ID for old-action notificationsDISCORD_WEBHOOK_URL- Optional Discord webhook URL for old-action notificationsSENTRY_DSN- Optional Sentry DSN for centralized error tracking