Subscription & bill statement for bunko.me — renewal radar, trial watch, cost normalization, what-if
  • Python 50.4%
  • HTML 24.6%
  • CSS 19.6%
  • JavaScript 5.2%
  • Dockerfile 0.2%
Find a file
2026-09-11 21:00:47 -05:00
app Add inbox pipeline for receipt and billing email ingestion. 2026-07-07 14:54:48 -05:00
.env.example Add inbox pipeline for receipt and billing email ingestion. 2026-07-07 14:54:48 -05:00
.gitignore Add Stub — subscription & bill statement (bunko.me/stub) 2026-06-24 11:02:53 -05:00
docker-compose.yml Add Stub — subscription & bill statement (bunko.me/stub) 2026-06-24 11:02:53 -05:00
README.md Note undeployed from bunko.me 2026-09-11 21:00:47 -05:00

Stub

Undeployed 2026-09-11. Not running on the VPS. Code and Forgejo bunko/stub are kept; the container, Caddy /stub route, dashboard tile, and n8n Stub Inbox workflow were removed. SQLite data remains in ./data.

A subscription & bill statement for bunko.me — tracks every recurring charge, keeps the running monthly/annual burn, flags free trials before they convert, and pings you (ntfy + Discord) before each charge lands.

Lives at bunko.me/stub, SSO-gated.

What it does

  • Statement dashboard — monthly burn (everything normalized to $/mo), annual total, what's left to post this month, the next charge, and a category breakdown.
  • Renewal radar — a daily scan (08:00 CT) pushes to ntfy + Discord NOTIFY_DAYS days before each charge, plus the morning of. Idempotent: one push per charge.
  • Trial watch — trials ending soon are pushed before they convert to paid.
  • Cost normalization — a $120/yr and an $11/mo plan are compared on equal footing.
  • One-click cancel — store each service's manage/cancel link; it rides along on the row and in the push.
  • What-if — tick rows in the ledger to model dropping them; the burn recomputes live.
  • Variable bills — utilities/usage bills flagged with ~, counted as estimates.

Design

The "STUB" identity: a manila bill statement — postmark cancellation stamp over the next due date (the signature), perforated tear lines, OCR/mono numerals, condensed bureaucratic display type (Saira Condensed). Deliberately not a generic dashboard.

Stack

FastAPI + SQLite + Jinja2, one in-process asyncio loop for the daily radar scan. No external services beyond ntfy/Discord webhooks.

Configure

Copy .env.example to .env and fill in:

var meaning
NTFY_URL base ntfy URL (e.g. https://ntfy.bunko.me)
NTFY_TOPIC topic to publish to
NTFY_TOKEN bearer token (if your ntfy requires auth)
DISCORD_WEBHOOK Discord webhook URL (optional)

Compose-level knobs: NOTIFY_HOUR (default 8), NOTIFY_DAYS (default 3), STUB_TZ (default America/Chicago).

Use the Test radar button in the header to confirm both channels are wired.

Deploy

cd ~/apps/stub
docker compose up -d --build

Then add the Caddy route (see the proxy repo) and restart caddy.

Inbox pipeline (receipts → review → statement)

n8n watches your billing inbox, forwards matching emails to Stub's internal API, AI extracts recurring charges, and you review them at bunko.me/stub/inbox before anything is saved. A ntfy ping links straight to the review screen.

  1. Set STUB_INBOX_API_KEY in ~/apps/stub/.env (and mirror it in ~/apps/n8n/.env).
  2. Import ~/apps/n8n/stub-inbox.json in the n8n UI and attach your IMAP credentials.
  3. Restart both stacks: docker compose up -d in stub and n8n.

Test without email: POST to the workflow's test webhook:

curl -sS -X POST 'https://n8n.bunko.me/webhook/stub-inbox-test' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Your Netflix subscription was charged $15.49 on June 14. Next billing: July 14, 2026.","source_ref":"Netflix receipt (test)"}'

Internal API (used by n8n on the Docker web network):

curl -sS -X POST 'http://stub:8000/api/inbox' \
  -H 'X-Api-Key: $STUB_INBOX_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"text":"...","source":"email","source_ref":"Subject · sender","kind":"auto","notify":true}'

kind: auto (default), receipt (single billing email), statement (multi-line statement), or data (CSV/JSON — skips AI).

Routes

  • GET / — statement dashboard
  • GET /inbox — pending receipt/email drafts
  • GET /inbox/{id} — review one inbox item
  • POST /inbox/{id}/accept — add selected candidates to the statement
  • POST /inbox/{id}/dismiss — discard
  • POST /api/inbox — ingest (API key; n8n only)
  • GET /new, POST /new — add an entry
  • GET/POST /{id}/edit — edit
  • POST /{id}/status — active / trial / paused / cancelled
  • POST /{id}/paid — advance to the next cycle
  • POST /{id}/delete — remove
  • POST /seed — load a sample statement (empty-state only)
  • POST /api/test-notify — fire a test push
  • POST /api/run-checks — run the radar scan now
  • GET /health — healthcheck