Turn emailed receipts into a single expense-report PDF (cover summary + indexed appendix).
Project description
receipt-report-generator
Turn receipts that land in your inbox into a single, tidy expense-report PDF — a cover sheet that summarises every receipt with a running total, followed by each original receipt as an indexed appendix. Optionally emails the finished PDF onwards and files the processed messages away so they are never counted twice.
Originally built to collect public-transport receipts for monthly bookkeeping, it is metadata-driven: point it at any sender, any IMAP mailbox, and any currency.
What it does
- Connects to your mailbox over IMAP.
- Finds every email from a configured sender that has a PDF attachment.
- Reads the amount and date out of each receipt PDF.
- Builds one PDF:
- Cover sheet — a title, today's date, and a table of
# | Date | Description | (Purpose) | Amount, ending in a Total. - Appendix — every original receipt, stamped
Appendix Nto match its table row, with PDF bookmarks.
- Cover sheet — a title, today's date, and a table of
- Moves the processed emails to a folder so the next run only sees new receipts.
- (Optional) Emails the PDF to an archive address and saves a copy in your Sent folder.
- (Optional, in CI) Emails you if a scheduled run fails.
Because "what's already processed" is represented by moving the emails out of the inbox, there is no database or local state to maintain — the mailbox is the single source of truth, which is what makes running it in the cloud trivial.
Requirements
- Python 3.10+
- An email account reachable over IMAP + SMTP (see supported servers)
Installation
git clone https://github.com/<you>/receipt-report-generator.git
cd receipt-report-generator
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then edit .env
Or install it as a package to get the receipt-report command on your PATH:
pip install git+https://github.com/<you>/receipt-report-generator.git
# then, in a directory containing your .env:
receipt-report --dry-run
Configuration
All configuration is via environment variables (read from .env locally, or injected by
your host in the cloud). The most important ones:
| Variable | Description |
|---|---|
MAIL_PROVIDER |
A preset from providers.json (gmail, outlook, icloud, …). Fills in IMAP/SMTP host, port and security. |
IMAP_USER / IMAP_PASSWORD |
Mailbox login. Use an app password where the provider supports one. |
RECEIPT_SENDER |
Sender address of the receipts. A substring works (e.g. sl.se). |
PROCESSED_FOLDER |
Where processed emails are moved (default Processed/Receipts, created if missing). |
RECIPIENT_NAME / REPORT_TITLE |
Name and cover title ({name} is substituted). |
CURRENCY |
Label after each amount (default kr). |
PURPOSE |
Optional value for a "Purpose" column; empty hides the column. |
DESCRIPTION_MATCH |
Optional regex to extract a description from the receipt text. |
ARCHIVE_RECIPIENT |
If set, the report is emailed here on a real run. |
NOTIFY_RECIPIENT |
Where CI failure notices go (defaults to IMAP_USER). |
To use a server not in the presets, leave MAIL_PROVIDER empty and set IMAP_HOST,
IMAP_PORT, SMTP_HOST, SMTP_PORT and SMTP_SECURITY (ssl or starttls) directly.
See .env.example for the full list.
Supported mail servers
Presets live in providers.json and are easy to extend.
MAIL_PROVIDER |
Service | IMAP | SMTP |
|---|---|---|---|
gmail |
Gmail / Google Workspace | imap.gmail.com:993 | smtp.gmail.com:465 (SSL) |
outlook |
Outlook.com / Hotmail | outlook.office365.com:993 | smtp-mail.outlook.com:587 (STARTTLS) |
office365 |
Microsoft 365 (work/school) | outlook.office365.com:993 | smtp.office365.com:587 (STARTTLS) |
icloud |
iCloud Mail | imap.mail.me.com:993 | smtp.mail.me.com:587 (STARTTLS) |
yahoo |
Yahoo Mail | imap.mail.yahoo.com:993 | smtp.mail.yahoo.com:465 (SSL) |
fastmail |
Fastmail | imap.fastmail.com:993 | smtp.fastmail.com:465 (SSL) |
gmx |
GMX | imap.gmx.com:993 | mail.gmx.com:465 (SSL) |
zoho |
Zoho Mail | imap.zoho.com:993 | smtp.zoho.com:465 (SSL) |
opensrs |
OpenSRS / Tucows Hosted Email | imap.hostedemail.com:993 | smtp.hostedemail.com:465 (SSL) |
Any IMAP/SMTP server works via explicit host settings — the presets are just conveniences.
Setting up your mail account
Most providers need two things:
- IMAP access enabled. On by default for many; for some (Yahoo, GMX, Zoho) you toggle it in settings.
- An app password if the account uses 2-factor authentication. The generator logs in
with a plain username + password, so create a provider-specific app password and use it
as
IMAP_PASSWORD:- Gmail: Google Account → Security → App passwords (requires 2-Step Verification).
- iCloud: appleid.apple.com → Sign-In and Security → App-Specific Passwords.
- Yahoo / Fastmail / Zoho: Account security → generate an app password.
- OpenSRS/hosted email: usually the normal mailbox password works.
Microsoft note: Outlook.com and many Microsoft 365 tenants have disabled basic IMAP/SMTP auth. If password login is refused, an admin may need to allow it, or the account may require OAuth (not handled by this tool).
The same account is used both to read receipts and to send the report, so make sure
SMTP submission is allowed too. The per-provider notes in providers.json summarise this.
Usage
Dry run (build the PDF; touch nothing in the mailbox):
python -m receipt_report.main --dry-run
Real run (build the PDF, move processed emails, and email the report if ARCHIVE_RECIPIENT
is set):
python -m receipt_report.main
Real run without emailing:
python -m receipt_report.main --no-email
Output lands in output/expense-report_<YYYY-MM-DD>.pdf.
Run it in the cloud (GitHub Actions)
.github/workflows/monthly.yml runs the generator on the
last day of each month — no machine of your own needs to be on.
Set these in your repository (Settings → Secrets and variables → Actions):
Secrets (sensitive):
| Secret | Value |
|---|---|
IMAP_USER |
Your mailbox address |
IMAP_PASSWORD |
App password / mailbox password |
ARCHIVE_RECIPIENT |
(optional) where to email the report |
Variables (non-sensitive):
| Variable | Example |
|---|---|
MAIL_PROVIDER |
gmail |
RECEIPT_SENDER |
receipts@vendor.com |
RECIPIENT_NAME |
Jane Doe |
CURRENCY |
kr |
PURPOSE |
(optional) |
Then:
- Test anytime: Actions tab → Monthly expense report → Run workflow. A manual run skips the last-day guard and runs immediately.
- Test the failure alert: run the workflow with the test_fail input checked — it forces
a failure and emails
NOTIFY_RECIPIENT(orIMAP_USER). - The generated PDF is also kept as a run artifact for 90 days.
- Scheduled workflows only run on the repository's default branch.
Customising the parsing
Receipt layouts vary. The parser (receipt_parser.py) is
built to be tuned:
- Amounts are matched in both
1,234.50and1 234,50styles; lines containing keywords like total, amount due, summa are preferred, otherwise the largest amount wins. - Dates are read as ISO,
dd.mm.yyyy, or12 June 2026(English + Swedish month names), falling back to the email's date. - Descriptions default to the first meaningful text line; set
DESCRIPTION_MATCHto a regex to grab something specific (group 1 if present).
Receipts where an amount or date can't be read are still included and listed as warnings so you can check them.
Security
- Credentials live only in
.env(git-ignored) or your host's secret store. Nothing sensitive is committed. - The processed-email move happens after the PDF is written successfully, so an interrupted run never loses receipts.
Publishing to PyPI
.github/workflows/publish.yml publishes the package to
PyPI whenever you create a GitHub Release, authenticating with a PyPI API token. One-time
setup:
- Create an API token. On pypi.org → Account settings → API tokens → Add API token (scope "Entire account" for the first upload; you can scope it to the project afterwards).
- Store it as a secret. Repo → Settings → Secrets and variables → Actions → new secret
named
PYPI_API_TOKENwith the token value. Or via CLI:gh secret set PYPI_API_TOKEN --repo <owner>/<repo> - Release. Bump
versioninpyproject.toml, then create a GitHub Release (e.g. tagv1.0.0). The workflow builds and uploads automatically.
Prefer no stored token? PyPI also supports OIDC trusted publishing — remove the password:
line from the workflow, add permissions: id-token: write to the publish job, and register a
trusted publisher on PyPI (owner, repo, workflow publish.yml, no environment).
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file receipt_report_generator-1.0.0.tar.gz.
File metadata
- Download URL: receipt_report_generator-1.0.0.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0332f541aa66ec601663cd505e72f0b585e885c091837be04b959dc55a36dfe
|
|
| MD5 |
41940c7afd3828f08a84e17238b57d64
|
|
| BLAKE2b-256 |
46c40667b43327012a419828dd4c4ea3d95a1c36bb31332a4bb52ba22a064311
|
File details
Details for the file receipt_report_generator-1.0.0-py3-none-any.whl.
File metadata
- Download URL: receipt_report_generator-1.0.0-py3-none-any.whl
- Upload date:
- Size: 21.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75f9508fe330767c8cf07fa744df21cfe939fe4957c0985c65227e6f7a7ac381
|
|
| MD5 |
38a411a29991aec0ee9329e1c28b1a90
|
|
| BLAKE2b-256 |
35f7509bee913ffd7fa614cde7a6090f17d86a2d6a1dad32c1ac5b84387b79dd
|