anderslreed/journal-mailer

Generates reports and emails them

★ 0Forks 0PythonGitHub ↗Compare

README

journal-mailer

Imports General Ledger Statement CSV exports from PowerOffice Go into a local SQLite database, builds an Excel spreadsheet from the stored transactions, and emails it to a recipient list via Gmail. Intended to run monthly as a scheduled job.

How it works

  1. Export a General Ledger Statement from PowerOffice Go as a CSV file.
  2. Import the CSV into a local SQLite database; reversed and cancelled entries are automatically excluded so only settled transactions are stored.
  3. Query the database for the previous calendar month's transactions and write them to an .xlsx spreadsheet.
  4. Look up recipients from a Google Contacts label (JustMe) when MAILER_ENVIRONMENT=Production; otherwise fall back to a hardcoded development address.
  5. Send the spreadsheet as an email attachment via Gmail.

Requirements

  • Python >= 3.10
  • Poetry for dependency management

Environment variables

Variable Required Description
GMAIL_SENDER Yes Gmail address used as the sender
GMAIL_APP_PASSWORD Yes Gmail App Password for SMTP
GOOGLE_ACCESS_TOKEN Production only Google People API access token (contacts.readonly scope)
MAILER_ENVIRONMENT No Set to Production to use the Google Contacts recipient list
DB_PATH No Path to the SQLite database file (default: journal.db)

Local development

# Install dependencies
poetry install

# Run tests
poetry run pytest

# Lint and format
poetry run ruff check .
poetry run ruff format .

# Security scan (source only, not tests)
poetry run bandit -r journal_mailer

Importing a CSV

Export a General Ledger Statement from PowerOffice Go (semicolon-delimited, UTF-8). Then import it:

poetry run python -m journal_mailer import --csv "General Ledger - Statement 2026.csv"

Reversed entries (text contains "(Reversed)" or starts with "Reversal of voucher") are silently dropped. Re-importing the same file is safe (idempotent).

Sending the monthly report

export [email protected]
export GMAIL_APP_PASSWORD=...

# Development run (sends to the hardcoded dev address)
poetry run python -m journal_mailer send

# Production run (sends to the JustMe Google Contacts group)
export MAILER_ENVIRONMENT=Production
export GOOGLE_ACCESS_TOKEN=...
poetry run python -m journal_mailer send

Running with Docker

Build the image:

docker build -t journal-mailer .

Run it, passing credentials as environment variables:

docker run --rm \
  -e [email protected] \
  -e GMAIL_APP_PASSWORD=... \
  -e MAILER_ENVIRONMENT=Production \
  -e GOOGLE_ACCESS_TOKEN=... \
  -v /path/to/csvs:/data \
  journal-mailer import --csv /data/ledger.csv

docker run --rm \
  -e [email protected] \
  -e GMAIL_APP_PASSWORD=... \
  journal-mailer send

CI/CD

The GitLab CI pipeline (.gitlab-ci.yml) builds and pushes the Docker image to the GitLab container registry on every push to the default branch.

registry.gitlab.com/<namespace>/journal-mailer:latest
registry.gitlab.com/<namespace>/journal-mailer:<commit-sha>

Project structure

journal_mailer/
  __init__.py        # Norwegian month names
  __main__.py        # Entry point: import CSV or send report
  csv_importer.py    # CSV parsing, reversal filtering, DB upsert
  db.py              # SQLAlchemy ORM model (LedgerEntry) and engine helpers
  contacts.py        # Google People API — resolves contact label to email list
  models.py          # Email and Attachment dataclasses
  sender.py          # Gmail delivery
  report.py          # Builds the .xlsx workbook from ledger entries
  time.py            # Date utilities — previous_month() for report period

Contributors

anderslreed

Issues