Bank Alert → 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 bank-specific parser, and posts any transactions it finds to your Actual Budget account.
Bank & Gmail Prerequisites
Before running any setup commands, the email pipeline needs two things in place on the bank and Gmail side.
1. Enable transaction alerts at your bank
Most banks let you configure email alerts for every transaction — check your bank's notification or alert settings. Enable alerts for all transactions (not just large ones), and make sure they go to your Gmail address. Some banks call this "transaction notifications," "spending alerts," or "purchase alerts."
Not all banks support per-transaction email alerts. If yours doesn't, this flow won't work for that account.
2. Create a Gmail label for those alerts
Once alerts are arriving, set up a Gmail filter to label them automatically:
- Open a transaction alert email in Gmail
- Click the three-dot menu → Filter messages like these
- Use the sender address (e.g.
no.reply.alerts@chase.com) as the filter condition - Choose Apply the label → create a new label (e.g.
Chase/Transaction) - Check Also apply to matching conversations to catch existing emails
Give each bank its own distinct label — the label is how Decree knows which emails to pick up and which parse script to use.
How It Works
1. Cron trigger
automations/cron/gmail-transactions-<label>.md fires the gmail-sync routine every 5 minutes. It sets GMAIL_LABEL_FILTER to your bank's alert 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 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 a bank-specific parse-<bank>.ts script against the email. The parser reads the subject line and outputs a transaction JSON object. 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-transactions-cron
An interactive prompt will:
- List your custom Gmail labels — select the one your bank sends alerts to
- List available parse scripts — select the one matching your bank
- List your open Actual Budget accounts — select the one to post transactions to
- Confirm the schedule (default:
*/5 * * * *) - Write
automations/cron/gmail-transactions-<label>.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-transaction.md << 'EOF'
---
routine: actual-budget-parse
subject: 'You made a $42.00 transaction with WHOLE FOODS'
date: 'Mon, 21 Apr 2026 12:00:00 +0000 (UTC)'
parse_script: /work/.decree/lib/actual-budget/parse-chase.ts
account_id: <your-account-id>
---
EOF
Adding More Banks
Each bank gets its own parse script. actual-budget-parse accepts any parse_script that reads the decree message env vars and outputs JSON:
{ "amount": "-42.00", "payee": "Whole Foods", "date": "2026-04-21", "notes": "..." }
To add a new bank:
- Create
automations/lib/actual-budget/parse-<bank>.tsfollowing the same interface asparse-chase.ts - Run
./existential.sh setup gmail-transactions-cronagain — it will discover the new script automatically