2026-06-02 03:07:13 +02:00
|
|
|
# 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.
|
|
|
|
|
|
EMAIL-WP-0005 T05/T06: evidence realism and harness documentation
Adds harness.deliver_raw() to inject crafted .eml fixtures through the harness,
so they pick up the real Received: headers an MTA adds, and
harness.inject_maildir() as the byte-exact offline counterpart.
tests/test_evidence_realism.py asserts all ten recognized evidence classes
after passing through a real MTA, proves MTA delivery does not change which
evidence is produced, and covers Maildir injection offline.
Documents what no local server can honestly produce -- provider-generated DSNs,
real 4xx deferral and retry, ISP feedback loops, provider suppression behavior,
MX acceptance as distinct from provider acceptance -- and names SES simulator
addresses as the staging path. That tier stays out of the test run because it
needs real credentials.
Adds docs/test-harness-tutorial.md covering the three test tiers and the
start/send/scan/assert/reset walkthrough, with an explicit
assertable/not-assertable list so the evidence ceiling is stated where tests
get written. Adds a Maildir section to the mailbox report tutorial.
Completes EMAIL-WP-0005. Suite: 85 passed with the harness up, 64 passed +
21 skipped with it down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 02:00:17 +02:00
|
|
|
## 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`.
|
|
|
|
|
|
2026-06-02 03:07:13 +02:00
|
|
|
## 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.
|