invp connects to IMAP mailboxes, extracts invoice/receipt attachments, and writes them into a predictable directory layout for accounting workflows.
Prerequisites:
- Go 1.25+
- IMAP account credentials
- Chrome/Chromium in PATH (only needed for HTML-email-to-PDF conversion)
Build a local binary named invp:
go build -o invp .Run without building:
go run . <args>Install from module path (binary name: invoice-piper):
go install github.com/avilabss/invoice-piper@latestIf you installed via go install, replace invp with invoice-piper in command examples below.
just shortcuts:
just build
just test
just lint
just run email exportConfig is loaded in this order:
--config /path/to/config.json./config.json<os.UserConfigDir()>/invoice-piper/config.json
Common os.UserConfigDir() examples:
- macOS:
~/Library/Application Support - Linux:
~/.config - Windows:
%AppData%
If nothing is found, invp exits with: config file not found; create ... (see config.example.json).
Example:
{
"output_dir": "./invoices",
"provider_aliases": {
"custom-billing.com": "mycorp"
},
"email": {
"accounts": [
{
"name": "personal-gmail",
"host": "imap.gmail.com",
"security": "imaps",
"port": 993,
"username": "user@gmail.com",
"password_env": "INVP_PERSONAL_GMAIL_PASSWORD",
"mailboxes": ["INBOX"]
}
]
}
}Top-level fields:
output_dir(optional, default./invoices)provider_aliases(optional) — domain → provider-name overrideemail.accounts(required, at least one account)
Per-account fields:
name(optional, default =username)host(required)security(optional when omitted, defaults to"imaps"; if set, must be"imaps","starttls", or"plain")"imaps"— TLS from connect (implicit TLS / IMAPS)"starttls"— starts plain, then upgrades the connection with STARTTLS"plain"— no TLS; use only intentionally for trusted/local bridge-style endpoints
port- for
security: "imaps": optional, defaults to993when omitted - for
security: "starttls"orsecurity: "plain": required
- for
tls_skip_verify(optional, defaultfalse) — only allowed forimaps/starttls; omit this field forplainusername(required)password_envorpassword(exactly one required)mailboxes(optional, default["INBOX"])
Transport note (important):
invpnow uses explicit IMAP transport modes. If you use bridge/local IMAP endpoints, set bothsecurityandportexplicitly.- Older configs that only changed
port(while relying on implicit TLS defaults) should be updated tosecurity: "starttls"or"plain"as appropriate.
Bridge-style STARTTLS account example:
{
"name": "proton-bridge",
"host": "127.0.0.1",
"security": "starttls",
"port": 1143,
"tls_skip_verify": true,
"username": "bridge-user",
"password_env": "INVP_PROTON_BRIDGE_PASSWORD",
"mailboxes": ["INBOX"]
}Use tls_skip_verify only for TLS-based modes (imaps / starttls) when you understand the security tradeoff (for example, a local bridge certificate you do not validate).
Mailbox behavior:
- Omit
mailboxesto search onlyINBOX - Set
mailboxes: ["ALL"]to list and search every mailbox on that account
password_env is preferred over plaintext password.
export INVP_PERSONAL_GMAIL_PASSWORD="app-password"If the env var is missing or empty, config load fails before any IMAP work starts.
invp version
invp email mailboxes
invp email export
invp email export --year 2025 --month 1
invp email export --year 2025 --month 1 --concurrency 1Useful global flags:
--configconfig file path-v,-vv,-vvvincreasing log verbosity
email export defaults:
--yearomitted → current year--monthomitted → current month--concurrencyomitted or<=0→ default concurrency (3)
Attachments are written under:
<output_dir>/<year>/<month>/<provider>/<timestamp>.<ext>
Example:
./invoices/2025/01/amazon/20250115_143022.pdf
Notes:
<provider>is resolved from sender domain (withprovider_aliasesoverrides)- Provider directory names are sanitized to lowercase letters/digits/hyphens
- Filename collisions add suffixes (
..._2.pdf,..._3.pdf, ...) - Attachments without extension are saved as
.bin - On Unix-like systems, directories are created with
0700and files with0600 - Effective privacy controls still depend on OS/filesystem behavior (for example, Windows ACLs are not managed by
invp)
Password-locked PDF handling:
- Password-protected PDFs are still saved
- A provider-local
README.mdis created/appended with extracted password hints:
<output_dir>/<year>/<month>/<provider>/README.md
invp email exportcontinues processing and prints a summary for every account.- It exits non-zero if any account had fetch/parse/write/conversion errors.
invp email mailboxeslists mailboxes account-by-account and exits non-zero if one or more accounts fail.
This means you can still get useful output files even when exit status is non-zero.
- Config not found: pass
--configor create./config.json/ user config path. password_enverror: ensure the env var exists and is non-empty in the same shell/session.- IMAP login/connect errors: verify
security,host, andport(993is the default only forimaps;starttls/plainrequire explicit ports), plus username and app password. - Chrome/Chromium error during export: install Chrome/Chromium and ensure it is available in
PATH. - No files exported: confirm
--year/--monthwindow and mailbox selection (INBOXvs explicit mailboxes vsALL).