Skip to main content

Command-line client for Italian PEC (Posta Elettronica Certificata) — built for AI agents and developers.

Project description

CI PyPI version Python versions Checked with mypy License: MIT Built for AI agents

Italiano: README.it.md — versione ridotta.

pec-cli

Command-line client for PEC (Posta Elettronica Certificata — Italian certified email), built for both humans and AI agents. Designed to be context-efficient: the default output strips empty fields, and --json produces NDJSON suitable for piping into LLMs or jq.

Talks to the standard IMAP/SMTP endpoints exposed by Italian PEC providers (Aruba, Legalmail/InfoCert, Namirial, Register.it, Poste Italiane, Pec.it), all over SSL/TLS.

Why this exists

PEC (Posta Elettronica Certificata) is the legal-email standard for Italian businesses and professionals — used daily for invoices, official notices, contracts, public administration communications. Every Italian SME has one.

Programmatic access is fragmented: each provider (Aruba, Poste, Legalmail, Namirial, ...) ships its own SDK or webmail-only interface. Open-source tools that let AI agents send, receive, and track PEC messages are essentially non-existent.

pec-cli fills that gap:

  • Agent-friendly: NDJSON output, stable exit codes, errors on stderr — pipe it into Claude, jq, or any LLM workflow.
  • Human-friendly: compact text output, one command per common task (send, list, fetch, trace).
  • Italian-native: built for the way Italian businesses actually use PEC — formal communications, legal evidence, document workflows.

Part of MayAI.

Engineering notes

  • Deterministic Message-ID for idempotent sends. SHA-256 of (from, to, cc, subject, body, minute-of-send) — accidental retries collapse to the same id and providers deduplicate.
  • Multi-provider abstraction. Aruba, Legalmail, Namirial, Register.it, Poste, Pec.it share one CLI; presets in docs/PROVIDERS.md.
  • Retry with exponential backoff. Transient IMAP / SMTP failures retry up to 3 times (1s, 2s, 4s, ... capped at 30s). Permanent failures (auth, SMTP 5xx) propagate immediately.
  • defusedxml for daticert.xml parsing. Neutralizes XXE / billion-laughs even though the certification chain is trusted — the input is still network-sourced.
  • Mypy strict, zero unmotivated ignores. Strict baseline + disallow_subclassing_any, strict_equality, extra_checks. The only # type: ignore comments live in test code, each with a specific code and a documented reason.

Quality bar

  • 151 tests, multi-OS / multi-Python (Ubuntu / macOS / Windows × 3.11 / 3.12 / 3.13) via GitHub Actions.
  • ~86 % branch coverage on the production module (pec_cli/), tracked in CI.
  • Credentials in the system keyring (macOS Keychain, Linux Secret Service, Windows DPAPI). Fernet-encrypted file as a headless fallback.

Requirements

Installation

pip install mayai-pec-cli

Installs a single pec command (plus pec-mcp for MCP servers) on your PATH.

MCP Server

pec-cli ships with a native MCP server, letting AI agents like Claude access your PEC inbox directly — no subprocess, no JSON parsing.

MCP demo

Setup with Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "pec": {
      "command": "/path/to/pec-mcp"
    }
  }
}

Find your path with which pec-mcp.

Compatible MCP clients

Client Status
Claude Desktop Tested
Cursor Compatible (same stdio config)
Continue (VS Code) Compatible (same stdio config)
Zed Compatible (same stdio config)
ChatGPT MCP support coming soon

Available tools

Tool Description
pec_list List messages (folder, unread_only, limit)
pec_get Get full message with body and cert
pec_search Search by query in subject / from / body / all + optional date
pec_list_folders List folders, optionally with message and unseen counts
pec_send Send a PEC (requires confirm_legal_send=True)
pec_mark_read Mark a message as read
pec_mark_unread Mark a message as unread
pec_move Move a message between folders (validates destination)
pec_trace Trace receipt chain by message ID
pec_auth_status Check authentication status

Quick start

# Authenticate — password prompted interactively, never passed as a flag.
pec auth login --address mia@pec.it --provider aruba

# List recent PECs as NDJSON; filter unread / by date.
pec --json list --unread --from 2025-01-01 --limit 50

# Read one message and save its attachments.
pec get 1234 --save-attachments ./attachments

# Include the parsed daticert.xml certification.
pec get 1234 --cert --json

# Trace the acceptance / delivery chain for an original message id.
pec trace 'opec123.20260321102500.12345.67.1.1@pec.it'

# Send a PEC with attachment (interactive confirmation by default).
pec send --to dest@pec.it --subject "Oggetto" --file body.txt --attach doc.pdf

# Find PECs from INPS in April 2026, then archive one of them.
pec search "INPS" --field from --from-date 2026-04-01
pec move 1234 --to "[PEC]/Archivio"

Command reference

Command Description
pec auth login --address ADDR --provider P Prompt for password, verify via IMAP, save credentials in the system keyring (Fernet-encrypted file as fallback).
pec auth status Show whether credentials are present.
pec auth logout Delete saved credentials (keyring entry + any local encryption key).
pec list [--folder F] [--unread] [--from YYYY-MM-DD] [--limit N] List PEC messages (default folder inbox, default limit 20).
pec get <id> [--folder F] [--save-attachments DIR] [--cert] Fetch a single PEC by IMAP UID; --cert includes the parsed daticert.xml certification; --save-attachments writes attachments to DIR.
pec trace <message-id> [--folder F] [--limit N] Find every receipt in the folder whose daticert.xml references this message id, ordered chronologically (accettazionepresa-in-caricoavvenuta-consegna / errore-consegna).
pec send --to ADDR --subject S (--body T | --file F) [--attach F] [--cc ADDR] [--dry-run] [--yes] Send a PEC; --to, --cc, --attach are repeatable. See safety note below.
pec search QUERY [--field subject|from|body|all] [--folder F] [--limit N] [--from-date YYYY-MM-DD] Search messages by content (default --field all = subject ∪ from ∪ body).
pec list-folders [--counts] List available IMAP folders. --counts adds message and unseen counts per folder.
pec mark-read <id> [--folder F] Mark a message as read (set \Seen). Idempotent.
pec mark-unread <id> [--folder F] Mark a message as unread (clear \Seen). Idempotent.
pec move <id> --to FOLDER [--from FOLDER] Move a message between folders. Uses IMAP MOVE when available, otherwise COPY + STORE \Deleted + EXPUNGE.

Safety note on pec send

PEC has the legal value of a registered letter (raccomandata). To avoid accidental sends:

  • Interactive TTY: pec send prompts for confirmation before contacting SMTP.
  • Non-TTY (CI, pipes, scripts): requires --yes explicitly. Exit code 3 otherwise.
  • --dry-run validates the message without contacting SMTP.
  • The MCP pec_send requires confirm_legal_send=True and is rate-limited to 3 sends per recipient per 5 minutes per session.

The deterministic Message-ID means an immediate retry of identical content collapses to one logical email — pair with pec trace to follow the chain.

Resilience

Transient IMAP / SMTP failures (socket timeouts, server [TRYAGAIN], SMTP 4xx) retry up to 3 times with 1s, 2s, 4s, ... backoff capped at 30s. Permanent failures (auth, SMTP 5xx) propagate immediately. SMTP retries reuse the same MIME envelope built before the loop, so the Message-ID is stable across attempts and providers deduplicate correctly. Pass --verbose for retry events on stderr.

Global flags

These work in any position (before or after the subcommand):

Flag Effect
--json Emit one JSON object per line (NDJSON).
--verbose Log IMAP/SMTP timings, retry events, and certification metadata to stderr.
-h, --help Show help for the current command.

Exit codes

Code Meaning
0 Success
1 Application error (network, send failure, bad arguments)
2 Not authenticated — run pec auth login
3 Refused to send: non-interactive shell without --yes

Supported providers

Aruba PEC, Legalmail (InfoCert), Namirial, Register.it, Poste Italiane, Pec.it. Endpoints and --provider values: docs/PROVIDERS.md.

Authentication

pec auth login prompts for the password on stderr (never echoed, never on argv), verifies it via IMAP, and stores it in the system keyring under service name mayai-cli-pec. On headless boxes without a keyring backend, it transparently falls back to a Fernet-encrypted file at ~/.config/mayai-cli/pec/credentials.json (mode 0600).

Full details — keyring fallback, key migration, file locations: docs/AUTHENTICATION.md.

Output format

  • Default — compact human-readable text. Empty / null fields are stripped so terminal output stays scannable.
  • --json — NDJSON. One object per line; lists stream one element per line so consumers can process incrementally.
  • --verbose — adds protocol timing lines on stderr, surfaces hidden certification attachments (daticert.xml, postacert.eml, smime.p7s/p7m), and emits retry events from the pec.retry logger.

Errors always go to stderr, prefixed with error:.

Development

git clone https://github.com/mayai-it/pec-cli.git
cd pec-cli
make dev       # install -e .[dev]
make test      # pytest
make lint      # ruff

Mypy is run via mypy pec_cli/ (configured in pyproject.toml). Contributing guide and PR checklist: CONTRIBUTING.md.

Help

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mayai_pec_cli-0.2.0.tar.gz (50.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mayai_pec_cli-0.2.0-py3-none-any.whl (38.5 kB view details)

Uploaded Python 3

File details

Details for the file mayai_pec_cli-0.2.0.tar.gz.

File metadata

  • Download URL: mayai_pec_cli-0.2.0.tar.gz
  • Upload date:
  • Size: 50.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for mayai_pec_cli-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e16af42e708c880262df1cf1054f86f7966f18e02d8977413e86b60878c38b1c
MD5 c3c0f07aa44239822cf3a2781cc10fd5
BLAKE2b-256 a8f44fd347b49bc05d562c0dfdfd5593fe8335039975dacdea0d2a5245650358

See more details on using hashes here.

File details

Details for the file mayai_pec_cli-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: mayai_pec_cli-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 38.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for mayai_pec_cli-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c81b3e9562e4b68cd54fdbff755046d684ea3088c9f4e8dd17b1d820ab6541de
MD5 1cf33fcd38e60c54025f3c440f4adfba
BLAKE2b-256 ada4efd802317ef18ded3fbf5b07bbe682f13023e1d65c9fb2db1a7a65b41508

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page