fa3c4f2657
Raw-MIME passthrough (STARTTLS, AUTH LOGIN) — separate from the notify HTTP-API alert path (which drops inline CID images). Per-customer token-bucket rate limit, From-header allowlist backstop. Resend key stays hub-side. No new external dep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.5 KiB
3.5 KiB
felhom.eu — task reports
Overwrite this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in hub/CHANGELOG.md.
App-email passthrough — hub leg (POST /api/v1/mail → Resend SMTP), hub v0.18.0
Task: SMTP app-relay (apps → on-box shim → hub → Resend). Implements
documentation/audits/SPIKE-smtp-app-relay-2026-06-28.md (verdict READY).
Baseline (verified live)
- Hub
main@4b97855, version v0.17.0 (the Resend-key rotation already shipped earlier today 2026-06-29) → target v0.18.0. (The prompt assumed v0.16.0→v0.17.0; the rotation took v0.17.0 first, so this leg is v0.18.0.) Prerequisite satisfied: Resend key is out-of-band inSecret/resend-api, read viaRESEND_API_KEY.
Files
- Created
internal/mailrelay/relay.go—ResendSMTP(Sender): STARTTLS tosmtp.resend.com:587,AUTH LOGIN resend/<key>(small stdlibnet/smtp.AuthLOGIN impl), rawMAIL/RCPT/DATApassthrough.FromDomain(From-header parser). No new external dependency. - Created
internal/api/mail.go—handleMail:checkAuthCustomer→ From-domain allowlist (403 backstop) → per-customer token-bucket rate limit (429) → passthrough to Resend (200 / 502).SetMailRelaywiring +mailRateLimiter. - Modified
internal/api/handler.go— sender/limiter/allowlist fields +POST /api/v1/mailroute. - Modified
cmd/hub/main.go—MailConfig(per_customer_per_minute,from_domains) + wireResendSMTPwhen a key is present (else 503). Thenotify/dispatcher.goHTTP-API alert path is untouched.
Green gate (local)
go build ./... && go vet ./... && go test ./... — PASS (6 packages ok, 0 failures).
Tests & §10 companion red-proofs
- Passthrough byte-equality (§7 A / §10):
TestMail_HappyPath_PassthroughRawBytes— theSenderreceives the raw bytes unchanged (not a parsed payload). PASS. - From-reject + companion (§7 B / §10):
TestMail_FromOutsideAllowlist_Rejected_NoSend(403, sender never called) +TestMail_FromReject_CompanionProof(allowing the domain reaches the sender). PASS. - Per-box rate limit + isolation + companion (§7 C / §10):
TestMail_RateLimit_PerCustomer(429 on the 2nd at 1/min; a different customer unaffected) +TestMail_RateLimit_CompanionProof(generous limit lets N+1 through). PASS. - Send-failure→502, 401/503/400 paths, token-bucket unit (injected clock), LOGIN auth + From-domain parse. PASS.
Deployment & live validation
- Deploy: build
felhom-hub:0.18.0on 180 → bumpmanifests/hub.yamlimage → ArgoCD sync (auto-sync off). Themailconfig is optional (defaults 30/min,felhom.eu); the Resend key is already injected viaSecret/resend-api(RESEND_API_KEY), so the relay activates on deploy. - End-to-end (app → shim → hub → Resend → real inbox): result recorded here after the live run; method stated. The Resend key is supplied to the hub out-of-band — never on the guest, never committed.
Observations
- App-relay is a separate code path from the hub's own structured alerts (which keep using the Resend HTTP API) — raw passthrough is required because the API path silently drops inline CID images (spike §4).
- v1: single-shot, no spool, no idempotency key. v2 would add accept-and-spool +
Resend-Idempotency-Key. - Fleet free-tier ceiling is 100 emails/day. No secrets in any committed file.