# REPORT — 2026-08-29 17:00 — Crush: mopac-imap-go (Redmine 501) - task: new repo `ukrrs/mopac-imap-go` — stdlib-only Go IMAP client (CLI + library): RFC 2177 IDLE watch, fetch+parse (headers, text body, attachments to spool), mark seen, SMTP passthrough send. Env `IMAP_HOST/IMAP_USER/IMAP_PASS`. Fake-server tests. Feeds the fleet's email-driven intake. - result: DONE — 12 atomic conventional commits pushed to `origin/main` (https://git.knownelement.com/ukrrs/mopac-imap-go, private, UKRRS org), HEAD `66a9135`, branch in sync, tests green, smoke PASS. NO Redmine writes performed. ## 1. What shipped Repo created via `tea` (UKRRS/mopac-imap-go, private). Sibling-layout to `mopac-redmine-go` at `~/projects/meta/MOPAC/mopac-imap-go`: `Makefile` → `dev.sh` → digest-pinned `golang:1.26-bookworm` builder (`golang@sha256:e8c859f5…`) — every build/vet/test ran in Docker; the host never ran a Go toolchain. First commit ships full AGPLv3 LICENSE (661 lines, verbatim from the sibling corpus). Module: `git.knownelement.com/ukrrs/mopac-imap-go`, go 1.26, zero third-party dependencies. ### Library packages - `imap/` — IMAP4rev1 client: implicit-TLS or plain dial with greeting validation; quoted-string LOGIN (password only ever inside the command, never in errors); SELECT parsing EXISTS/RECENT/UNSEEN/UIDVALIDITY; FETCH `(UID FLAGS BODY.PEEK[])` with a literal-inlining wire reader (payloads containing `{N}\r\n`- looking bytes parse correctly — pinned by test); STORE `+FLAGS.SILENT (\Seen)`; RFC 2177 IDLE with typed events (exists/recent/expunge/tick/other), tick-driven re-idle, and a Stop that consumes the tagged DONE completion so the stream stays clean (a subtle readLoop/select race was found by `-race`-adjacent flake hunting and fixed: after stop, readLoop keeps reading until the tagged line). Sentinel errors: unreachable/protocol/auth/mailbox/ timeout, one-line messages. - `mail/` — RFC 5322 + MIME parse: RFC 2047 header decoding, QP/base64 transfer decoding, recursive multipart (mixed/alternative nesting), attachments via Content-Disposition filename or Content-Type name. Non-UTF-8 charsets pass through raw (no x/text in this repo). `Spool()` writes `SPOOL_DIR//` containing `raw.eml`, `meta.json` (seq, ids, decoded headers, attachment index) and `attachments/` — content-hashed dirs make re-spooling idempotent across seq renumbering; hostile filenames reduced to basename (escape attempt pinned by test); 0700 dirs / 0600 files. - `mailer/` — SMTP passthrough: raw bytes on the wire unmodified; STARTTLS when offered, AUTH PLAIN when credentials set; sentinels unreachable/auth/rejected. ### CLI (`mimap`) + config - `internal/config`: `IMAP_HOST/IMAP_USER/IMAP_PASS` required (env wins over an optional 0600 `KEY=VALUE` env file, looser-than-0600 refused before read); defaults 993/TLS/INBOX, `SPOOL_DIR=spool`, SMTP set optional with `SMTP_FROM` falling back to the IMAP user. Errors name keys, never values. - `internal/cli`: `watch` (backlog fetch → IDLE; per message: spool → JSON line on stdout → mark `\Seen`, so a crash re-delivers idempotently instead of dropping; IDLE stopped before any FETCH; ticks re-SELECT so a missed EXISTS self-heals; SIGINT/SIGTERM → clean LOGOUT, exit 0), `check` (mailbox state as JSON), `send` (`--to` repeatable, `--file PATH|-` → SMTP passthrough). Exit codes 0/1/2 (ok/usage/protocol) matching the mred convention. ## 2. Verification - TDD throughout: each feature commit landed test-first against protocol-correct fakes (`internal/fakeimap` self-test pins every wire exchange incl. literal FETCH bodies and IDLE push; `internal/fakesmtp` pins AUTH/530/dot-unstuffing). - `./dev.sh check` (build+vet+test) green; `go test -race -count=3 ./...` green after fixing two real concurrency findings (DONE- completion race in idle.go; unsynchronized test buffer); imap package hammered `-count=10`. - `./dev.sh smoke`: builds the real `bin/mimap` in the pinned builder and drives it across process boundaries against the fakes — `check` reports backlog, `send` relays, `watch` produces backlog + live-IDLE-push JSON lines, 2 spool dirs, 2×`\Seen`, exit 0 on SIGINT. Output: `smoke: PASS`. ## 3. Commits (all on origin/main) 66a9135 docs: README with CLI semantics, spool/intake contract, layout 019be0d test: end-to-end smoke of the real binary against fakes ef2aec2 feat(cli): mimap watch/check/send over the library d0a38b5 feat(config): env + 0600 env-file connection settings 4f5a43a feat(mailer): SMTP passthrough send with fake-server tests 2865718 feat(mail): spool messages and attachments to disk dd1a462 feat(mail): parse RFC 5322 messages with MIME walk 444f297 feat(imap): RFC 2177 IDLE with clean Stop and re-idle 9d4ae78 feat(imap): fetch raw messages and mark seen 435fd81 feat(imap): client core — connect, login, select, logout 8552c9a test: in-process fake IMAP4rev1 server for client tests f6332bf chore: bootstrap module (AGPLv3, docker dev harness) ## 4. Notes / limitations - Charset conversion beyond UTF-8/ASCII is raw passthrough (stdlib only); documented in package doc and README. - Fake IMAP is one-mailbox (INBOX), no EXPUNGE push; the client still handles EXPUNGE events defensively via re-SELECT. - No Redmine API calls were made from this task, per instructions.