Transaction → Gmail → Budget
Automatically imports transaction alert emails into Actual Budget. A cron job polls a Gmail label every 5 minutes, routes matching emails through a parser, and posts any transactions it finds to your Actual Budget account.
How It Works
1. Cron trigger
automations/cron/gmail-chase.md fires the gmail-sync routine every 5 minutes. It sets GMAIL_LABEL_FILTER to your Chase label and GMAIL_ROUTINE to actual-budget-parse. Fields prefixed fwd_ are forwarded into each child message — this is how parse_script and account_id travel down the chain.
2. gmail-sync
Fetches any new emails from the Chase label using Gmail's History API (incremental — only emails since the last run). Each email is written as a markdown file to the Decree outbox with routine: actual-budget-parse in the frontmatter.
3. actual-budget-parse
Runs parse-chase.ts against the email body. The parser looks for a dollar amount and payee in the subject line (Chase alert format: A charge of $X.XX at PAYEE). If a transaction is found, it writes a new outbox message targeting actual-budget. If the email isn't a transaction alert, the message is discarded silently.
4. actual-budget
Calls post-transaction.ts using @actual-app/api to post the transaction to your Actual Budget server. Reads credentials from /secrets/actual-budget/credentials.env and the account ID from the message frontmatter.
Setup
Run these steps in order. Each builds on the previous one.
Step 1 — Gmail OAuth
If you haven't already authorized Gmail access:
./existential.sh setup gmail
This grants Decree read-only Gmail access and automatically saves your label list to /secrets/gmail/labels.json. See Gmail for full instructions.
If you've already set up Gmail but need to refresh the label cache (e.g. you added a new label):
./existential.sh setup gmail-labels
Step 2 — Actual Budget credentials
./existential.sh setup actual-budget
Connect to your Actual Budget server, select a budget, and save credentials. At the end, the script prints all your accounts with their IDs and saves them to /secrets/actual-budget/accounts.json for use in the next step.
See Actual Budget for full instructions.
Step 3 — Create the cron
./existential.sh setup gmail-chase-cron
An interactive prompt will:
- List your custom Gmail labels — select the one Chase sends alerts to
- List your open Actual Budget accounts — select the one to post transactions to
- Confirm the schedule (default:
*/5 * * * *) - Write
automations/cron/gmail-chase.md
No restart needed — Decree picks up new cron files on the next tick.
Verifying
Check that the routines are registered and passing pre-checks:
docker exec decree decree routine actual-budget-parse
docker exec decree decree routine actual-budget
Watch the next cron fire and inspect the run log:
docker exec decree decree status
docker exec decree decree log <id-prefix>
To test immediately without waiting for the cron, drop a message manually:
cat > automations/inbox/test-chase.md << 'EOF'
---
routine: actual-budget-parse
parse_script: /work/.decree/lib/actual-budget/parse-chase.ts
account_id: <your-account-id>
---
A charge of $42.00 at WHOLE FOODS has been made on your card ending in 1234.
EOF
Adding More Banks
The parser is generic — actual-budget-parse accepts any parse_script that reads a message file and outputs JSON:
{ "amount": 4200, "payee": "Whole Foods", "date": "2026-04-20", "notes": "..." }
To add a new bank, create automations/lib/actual-budget/parse-<bank>.ts following the same interface as parse-chase.ts, then run setup gmail-chase-cron again pointing to the new label and script.