Release now pushes to git.lucasrguerra.dev.br instead of ghcr.io, tags each
architecture separately (no artifact upload, no gha cache) and joins them with
imagetools. GITHUB_OUTPUT and GITHUB_STEP_SUMMARY stay: they are the runner's
own interface and the GITEA_* equivalents are not set.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The auth service refuses to start with a JWT_SECRET under 32 characters,
which is the point - but .env.example still carried a 30-character value,
so the stack could not come up with the file it ships with. CI caught it
on the compose-up job: auth reported unhealthy and everything depending
on it stopped.
Reproduced locally against the same file before and after; the stack now
starts from a clean checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"docker compose up" could not work, which means the F0 acceptance
criterion was not being met:
- ghcr.io/rspamd/rspamd:3.9 returns "denied" and clamav/clamav:1.3 does
not exist. Both were verified against the registries; the working
references are rspamd/rspamd:3.9 and clamav/clamav:1.4.
- Rspamd received RSPAMD_REDIS_SERVERS and RSPAMD_REDIS_PASSWORD as
environment variables it never reads. Confirmed in the container that
it does not expand ${VAR} in UCL: the password reached Redis as that
literal string. Greylisting and ratelimit keep their state in Redis,
so both were silently doing nothing while appearing configured. The
credential is now rendered at startup into override.d, a tmpfs, so it
touches neither the image nor a volume.
- The ratelimit module ships enabled with no rates defined, which
accepts everything. ratelimit.conf defines the per-IP, per-user and
per-recipient buckets of section 5.2.
Verified by bringing redis and rspamd up: the real password resolves, the
rates load, and a probe message returns "Action: greylist" with the state
written to Redis.
Services also gained an image: alongside build:, so the deploy host pulls
instead of compiling while "docker compose build" still works locally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a real :25 SMTP listener to the Go protocols service, giving F1's
"Nucleo SMTP" milestone its first working sub-project: application,
infrastructure, and presentation layers on top of F0's domain-only
Clean Architecture.
- ProcessInboundEmailUseCase resolves recipients against Postgres and
persists accepted mail (blob dedup by content hash, transactional
UID allocation + outbox event, never sending 250 OK before durable
commit per PLAN.md section 6.1).
- Postgres repositories via pgx/v5 (replacing lib/pq entirely).
- Local-disk blob storage with content-hash dedup, rename-before-commit
ordering so a DB row can never reference a file that isn't actually
on disk.
- A local-file TLS cert provider for STARTTLS, with an ephemeral
self-signed fallback so a missing/misconfigured cert degrades
gracefully instead of crash-looping the service (PLAN.md section 8.4).
- A github.com/emersion/go-smtp Backend/Session adapting real SMTP
traffic to the use case: unknown recipients get 550 5.1.1 before
DATA, duplicate RCPT TO for the same address is deduplicated, TLS is
hardened to the project's required PFS-only cipher/curve policy.
- A dev-seed script and Makefile target for local testing.
Explicitly out of scope (separate follow-up sub-projects): alias
resolution, SPF/DKIM/DMARC/ARC verification, Rspamd/ClamAV, Submission
ports with SASL auth, DKIM signing, outbound delivery, and the real
Traefik acme.json cert watcher.
Verified live end-to-end via WSL Docker: the full compose stack (db,
redis, migrate, auth, storage, protocols, web) reaches healthy, and a
real swaks SMTP session is accepted and durably persisted with correct
sequential UIDs, while an unknown recipient is correctly rejected with
550 5.1.1 and zero rows persisted. Built via 10 sequential tasks under
subagent-driven development, each independently reviewed, plus a final
whole-branch review that caught and fixed a TLS-crash bug, a blob
commit-ordering bug, missing TLS hardening, and missing CI integration
coverage before merge.
Known follow-up, not blocking: email_messages' partition only covers
July 2026 and needs a partition-management job before 2026-08-01.
Stands up the LambdaMail monorepo per PLAN.md's F0 milestone: local dev
scaffolding (.env.example, Makefile, .gitignore excluding PLAN.md/docs/
.superpowers/), the full initial Postgres schema as a golang-migrate
migration, the complete Go domain layer for services/protocols (value
objects, entities, domain events - zero external imports beyond
github.com/google/uuid) with unit tests, health-checked service skeletons
for auth/storage (TypeScript) and webmail (Next.js), a local
docker-compose.yaml wiring all services together, a language-enforcement
lint script, and a GitHub Actions CI workflow.
Built via 11 sequential tasks under subagent-driven development, each with
an independent implementer + reviewer cycle. Fixes applied during review:
DomainName.IsPunycode() added to satisfy the promised interface; ioredis
default-import typing under NodeNext; next.js bumped 15.0.3->15.5.21 to
resolve high/critical CVEs; lang-lint.mjs corrected to not flag its own
banned-terms list; web service healthcheck added so compose --wait
actually waits on it; webmail Dockerfile's missing public/ directory:
protocols go.mod version aligned with CI's Go 1.22 pin.
Known follow-ups for later phases (not blocking F0): go-arch-lint isn't
wired into CI yet; live docker compose up and migration apply have not
been exercised in this dev environment (no Docker/psql available here)
and will get their first real run in CI, which does have Docker.