Two bugs found by the setup check right after the last deploy:
- every domain's BIMI record named the deployment's own mail host, and the
server picked the domain from the host name, so only the primary domain's
logo could be fetched: ciberlab.seg.br's record pointed at a 404. The logo
and the certificate are now served at /.well-known/bimi/<domain>/..., the
record names that, and the webmail routes it. The old path still answers
- SetBimiPublisher ran before SetBimiAPI, found no BIMI API to attach to and
was dropped, so saving a logo never published the DNS record by itself. It is
kept on the router now, so the order does not matter
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A Verified Mark Certificate cannot be made here: an authority issues it after
checking the organisation. What the service can do is hold the file the
authority returns, serve it where the DNS record points, and refuse one that
would make receivers ignore BIMI without a word.
- migration 0015: domain_bimi keeps the PEM chain next to the logo
- bimi.InspectCertificate reads a chain and reports every reason it would not
work: private key blocks refused outright, the brand-indicator usage, the
validity period, the domain (exact, wildcard or organisational), and that
the logo embedded in the certificate (gzipped data URI in the logotype
extension) is byte for byte the logo being published. Tested against two
real public VMCs (eBay via DigiCert, CNN via Valimail) plus mutations
- GET /.well-known/bimi/vmc.pem (public, conditional, cacheable for an hour),
and PUT/DELETE /api/v1/admin/bimi/certificate; the GET of the logo now
reports the certificate as it stands against the logo being served, and
replacing a logo under a held certificate says it no longer matches
- GET /api/v1/admin/bimi/check looks at what a receiver would: the logo, DMARC
enforcement, the BIMI record, the public logo URL, the certificate and its
public URL. Its HTTP client is HTTPS only and refuses non-public addresses
after DNS, since the URLs come from DNS and an operator field
- the DNS record points a= at the held certificate when there is one
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
O painel respondia "API endpoint not found" e o logotipo devolvia a pagina de
404 do Next. As rotas estavam registadas no servico protocols, mas o proxy do
webmail decide o upstream por uma lista propria, e eu nao acrescentei a "bimi"
-- exactamente o erro contra o qual o comentario dessa lista avisa.
Ficam ambas cobertas por testes: cada area administrativa que o protocols
possui, e o caminho well-known do logotipo. Esquecer isto nao volta a ser
silencioso.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Added `MoveToTrash` method in `WebmailRepository` to move messages to the Trash folder instead of deleting them permanently.
- Introduced `handleDelete` method in `mailAPI` to handle message deletion requests from the client.
- Updated router to include a new route for message deletion.
- Enhanced the compose page to manage draft integrity, ensuring drafts are correctly associated with sent messages.
- Implemented UI changes to allow users to delete messages from the mail folder view.
- Added tests to verify the correct behavior of message deletion and draft handling.
- Updated database migration scripts to reconcile folder counters and ensure accurate unread counts.
- Added tests for TLS-RPT report parsing and webmail repository functionalities.
F4 lists real-time updates and the interface had none: an open inbox only
changed when the user reloaded it.
Events come from the transactional outbox rather than from an in-process
callback, which is what the outbox is for (PLAN.md section 9.4): the row
is written in the same transaction as the message, so an event can never
describe a delivery that rolled back, and a crash between commit and
publish loses nothing because the row is still there on restart. The relay
claims rows with SKIP LOCKED so a second instance can help without
publishing anything twice.
The hub fans events out per mailbox, keyed by the mailbox the session
resolved to, so one account's arrivals are never visible to another even
though they share the process. A subscriber that stops reading is dropped
rather than allowed to stall the relay; it reconciles on reconnect.
The client reconnects with capped exponential backoff and treats every
successful connect as a resync, refetching the folder. Without that, a
socket that dropped for a minute would leave the list quietly stale, which
is worse than having no real-time updates at all.
The upgrade goes through a Next rewrite, not the API route handler: a
route handler sees a finished request and cannot upgrade a connection.
Verified against the running stack - the handshake returns 101 through the
Next server, and a message delivered on port 25 arrives as a frame on an
already-open socket without the page asking for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.