A simple IMAP client, scanning defined mailboxes for XRechnung attachments
  • Go 96.2%
  • Shell 3.4%
  • Go Template 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Heiko Schlittermann (HS12-RIPE) 540c052d05
All checks were successful
nagonag (Push) / nagonag (push) Successful in 1m20s
new: PDF extraction timeout, api change
2026-10-01 17:10:46 +02:00
.forgejo/workflows ci: drop unsupported permissions block from nagonag callers ai:claude-opus-5 2026-09-25 23:56:40 +02:00
cmd/xr-invoiced refactor: config.Secret type, Options for every app entry point ai:claude-sonnet-5 2026-08-01 18:35:20 +02:00
debian/source build: add lintian override 2026-05-15 22:59:58 +02:00
deploy systemd: comment out default service options 2026-05-12 23:58:23 +02:00
doc refactor: declutter root into internal/ packages, wire hslog ai:claude-sonnet-5 2026-08-01 14:26:16 +02:00
internal new: PDF extraction timeout, api change 2026-10-01 17:10:46 +02:00
scripts imapclient: fix NOTIFY cleanup and reduce capability check duplication 2026-05-12 21:31:40 +02:00
.gitignore ignore the config.yaml 2026-05-11 17:26:13 +02:00
.gogogo.conf refactor: declutter root into internal/ packages, wire hslog ai:claude-sonnet-5 2026-08-01 14:26:16 +02:00
.golangci.yml lint: no golines formatter 2026-05-11 23:14:24 +02:00
AGENTS.md ai: consolidate instructions 2026-10-01 16:11:35 +02:00
config.example.yaml build: new .gogogo.conf 2026-05-13 23:34:24 +02:00
config_path_docs_regression_test.go test: point docs regression test at root AGENTS.md ai:claude-opus-4 2026-10-01 16:54:45 +02:00
go.mod deps(master): update minor/patch dependencies 2026-10-01 14:57:42 +00:00
go.sum deps(master): update minor/patch dependencies 2026-10-01 14:57:42 +00:00
LICENSE add license 2026-05-14 17:58:11 +02:00
PLAN.md cleanup 2026-05-23 16:36:11 +02:00
README.md refactor: replace pkg/imapclient with go.schlittermann.de/heiko/imapclient ai:claude-sonnet-5 2026-08-01 14:37:21 +02:00

XRechnung IMAP Scanner

A Go service that connects to an IMAP server, monitors mailboxes in real-time via IMAP IDLE, detects XRechnung invoices (UBL / UN-CEFACT CII XML and Factur-X hybrid PDFs), and applies a custom IMAP flag to matching messages for easy filtering in mail clients.

User Guide — Thunderbird setup: see and filter tagged invoices

Administration Guide — installation, configuration, deployment

Features

  • IMAP IDLE listener: Real-time monitoring of incoming mail; auto-reconnect on connection drop
  • Multiple mailboxes: Monitor several mailboxes in parallel, each with its own IMAP connection
  • Mailbox heartbeat logs: Every 60 minutes per mailbox, logs a summary window with processed/tagged/errors/reconnects and initial/startup/idle source counters
  • XRechnung detection:
    • UBL Invoice (namespace: urn:oasis:names:specification:ubl:schema:xsd:Invoice-2)
    • UN-CEFACT CII (namespace: urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100)
    • CustomizationID validation against XRechnung profiles
    • Factur-X/ZUGFeRD hybrid PDFs (embedded XML via PDF object model, pdfcpu)
  • Persistent state: Tracks UIDVALIDITY + last-scanned UID per mailbox; no re-scan on restart
  • Custom IMAP keyword: Configurable keyword name (default: ius-xrechnung)
  • Secure deployment: systemd unit with hardening, env var config, graceful shutdown

Quick Start

go build -o xr-invoiced ./cmd/xr-invoiced/
./xr-invoiced --doctor                    # verify IMAP credentials
./xr-invoiced --since "last month"        # preview what would be tagged
./xr-invoiced --apply                     # tag messages for real, enter IDLE

How It Works

  1. Startup: Loads config, connects to IMAP (one connection per mailbox)
  2. Initial scan: On first run or after UIDVALIDITY change, scans all existing messages; logs are marked scan=initial
  3. State/Resume scan: On normal startup, resumes from saved state and scans only messages newer than the last-seen UID; logs are marked scan=startup
  4. State persistence: Saves per-mailbox UIDVALIDITY + LastUID to JSON file
  5. IDLE loop: Enters IMAP IDLE and listens for new messages; after wake-up, every processed message is logged with scan=idle
  6. Message processing: For each new message:
    • Fetches full body + headers
    • Walks MIME attachments
    • Checks each attachment for XRechnung
    • Applies custom flag if match found
  7. Reconnection: On IDLE timeout (≈28 min) or connection drop, auto-reconnects and resumes
  8. Heartbeat: Every 60 minutes per mailbox, emits heartbeat with counters for the previous 60-minute window:
    • processed, tagged, errors, reconnects
    • initial, startup, idle

Logging

All log output goes to stdout only by default. --log-format=syslog/color-syslog is available if you want syslog-style <priority> framing on stdout, but auto-detection never selects it on its own.

The output format is autodetected based on the runtime environment:

│ Condition │ Format │ Description │ │──────────────────────────────────│─────────────────│──────────────────────────────────────│ │ systemd unit (auto) │ journal │ native journal export, structured fields, journalctl KEY=value filterable │ │ stdout is a terminal (auto) │ color-plain │ human-readable, ANSI colors │ │ otherwise, e.g. redirected (auto)│ jsonl │ structured JSON, one object per line │

Override with --log-format <format>: auto (default), plain/color-plain, jsonl/color-jsonl, syslog/color-syslog, journal/color-journal.

Architecture

cmd/xr-invoiced/main.go           Entry point; flag parsing; logging setup; signal handling
internal/app/app.go               Orchestration: doctor/list/remove-keyword, per-mailbox scan+IDLE loop
internal/config/config.go         Viper-based YAML + env override
internal/heartbeat/heartbeat.go   Per-mailbox activity counters + periodic heartbeat log
internal/infomessage/             One-time "tagging is active" info message
internal/journalhandler/          Native systemd journal socket handler (structured fields)
internal/traceio/                 Truncating writer for --debug=imap wire traces
internal/scanner/scanner.go       Orchestrator: fetch → walk → detect → flag
internal/scanner/attachment.go    MIME part walker
internal/state/state.go           Per-mailbox UIDVALIDITY + LastUID persistence
internal/xrechnung/detector.go    XML namespace + CustomizationID validation
internal/xrechnung/pdf.go         Factur-X/ZUGFeRD PDF embedded-file extraction (pdfcpu)

IMAP client (TLS/STARTTLS, IDLE, fetch, search, store) is go.schlittermann.de/heiko/imapclient, an external dependency rather than an in-repo package.

Known Limitations & Future Work

  • SASL mechanisms: Uses plain LOGIN; no OAuth2/SASL XOAUTH2 support
  • Email notifications: No alerting on scan errors; structured log output only
  • Parallel processing: Messages are processed sequentially; parallel fetch/detect is a planned enhancement

License

MIT (or your chosen license)

References

Glossary

Term Meaning
Flag Generic IMAP per-message attribute (RFC 3501). Covers system flags and keywords.
System flag Predefined by the RFC, backslash-prefixed: \Seen, \Answered, \Flagged, \Deleted, \Draft.
Keyword User-defined flag without backslash, e.g. ius-xrechnung. This is what xr-invoiced sets.
Tag Thunderbird's UI name for an IMAP keyword. Same data, different vocabulary.