# Mailbox Report Tutorial This tutorial shows how to generate an email-channel evidence report from a return mailbox or from the bundled fixture mailbox. ## 1. Verify The Scanner Run the tests and print the adapter descriptor: ```bash PYTHONPATH=src python3 -m unittest discover -s tests PYTHONPATH=src python3 -m email_connect.cli adapter-descriptor ``` ## 2. Start With Fixtures The example config uses `tests/fixtures/mailbox`: ```bash PYTHONPATH=src python3 -m email_connect.cli scan-mailbox \ --config config/mailbox.example.yml \ --full-rescan \ --out reports/ ``` The scanner writes a timestamped CSV file to `reports/`. ## 3. Configure A Live IMAP Mailbox Copy `config/mailbox.example.yml` and set: ```yaml mailbox: protocol: imap host: imap.example.com port: 993 tls: true username_env: EMAIL_CONNECT_IMAP_USER password_env: EMAIL_CONNECT_IMAP_PASSWORD folder: INBOX ``` Then export credentials: ```bash export EMAIL_CONNECT_IMAP_USER='mailbox@example.com' export EMAIL_CONNECT_IMAP_PASSWORD='app-password' ``` IMAP scans select the folder read-only and fetch messages with `BODY.PEEK[]`. The scanner does not mark messages seen, move messages, or delete messages. ## 3b. Scan A Maildir Instead When mail is delivered locally — an MTA writing to disk, or the test harness — point the scanner at the Maildir root instead of an IMAP server: ```yaml mailbox: protocol: maildir folder: INBOX source: maildir_dir: /var/mail/returns/Maildir ``` The directory is the mailbox root containing `new/` and `cur/`; both are read. No credentials are involved. Message identity is the Maildir unique name without its `:2,FLAGS` suffix, so a message stays the same message when the MTA or a mail client moves it from `new/` to `cur/`. Like IMAP, Maildir scans are read-only: `mark_seen` is rejected, and the scanner never moves, flags, or deletes a message. `include_seen: false` skips messages already flagged `S`. For a local Maildir or IMAP server to test against, see `docs/test-harness-tutorial.md`. ## 4. Add Expected Recipients Expected recipients are optional. A newline-separated file can look like: ```text missing@example.com recipient@example.com ``` Run: ```bash PYTHONPATH=src python3 -m email_connect.cli scan-mailbox \ --config config/mailbox.example.yml \ --expected-recipients recipients.txt \ --out reports/ ``` CSV input is also supported: ```csv email,name missing@example.com,Missing User recipient@example.com,Known Recipient ``` Run: ```bash PYTHONPATH=src python3 -m email_connect.cli scan-mailbox \ --config config/mailbox.example.yml \ --expected-recipients recipients.csv \ --expected-recipient-column email \ --out reports/ ``` Invalid recipient rows are ignored and printed as warnings. ## 5. Limit The Time Range Use an inclusive datetime range: ```bash PYTHONPATH=src python3 -m email_connect.cli scan-mailbox \ --config config/mailbox.example.yml \ --from 2026-06-02T10:00:00Z \ --to 2026-06-02T11:00:00Z \ --out reports/ ``` `--since` remains a compatibility alias for the lower bound. When a range is active, messages with no parseable `Date` header are excluded because the scanner cannot confirm that they originated inside the requested window. ## 6. Read The Report Key columns: - `known_recipient`: `true` when the address was supplied in the expected list. - `normalized_event_type`: the email evidence or diagnostic event. - `assessment_category` and `assessment_subclass`: advisory interpretation. - `affected_email_address`: the endpoint the row is about. Known recipients appear first by default so spreadsheet filtering is easy. Expected recipients with no mailbox evidence appear as: ```text normalized_event_type: diagnostic.expected_recipient.no_evidence assessment_category: undef assessment_subclass: undef.no_signal evidence_strength: none known_recipient: true ``` That row means only that no mailbox evidence was found for the supplied address inside the inspected range. It is not evidence of delivery success, delivery failure, recipient awareness, or legal acceptance. ## 7. Troubleshooting - Empty report: check folder, time range, and whether incremental cursor state already skipped older messages. Try `--full-rescan`. - IMAP credential error: verify the environment variable names and values. - Missing expected rows: check the recipient file path and CSV column name. - Unexpected no-evidence rows: confirm that the relevant mailbox evidence is inside the configured datetime range.