feat(bimi): logotipo do dominio validado, alojado e publicado no DNS

A foto de perfil nao viaja: nem o Gmail nem o Outlook leem uma imagem por
utilizador do servidor de quem envia, e nao existe protocolo que o permita. O
avatar que esses mostram vem dos contactos de quem recebe ou da conta Google
ou Microsoft do remetente.

O que eles leem e BIMI, que e por dominio. Fica implementado: validacao,
alojamento e o registo.

O validador reporta todas as violacoes de uma vez, e nao a primeira. Um
receptor que recusa a marca nao explica nada -- simplesmente nao desenha -- por
isso descobrir as regras uma viagem de cada vez seria a pior maneira possivel
de aprender este formato. Sao verificados o baseProfile e a versao que os
receptores procuram, o viewBox quadrado (uma marca nao quadrada e cortada de
forma diferente por cada cliente), exactamente um title, e a ausencia de tudo
o que seja activo ou externo: script, handlers de evento, links, imagens
rasterizadas, animacoes e referencias para fora do documento.

O registo so e publicado quando ha mesmo uma marca guardada. Um registo que
aponta para um logotipo inexistente e pior do que nenhum, porque o receptor
para de procurar assim que encontra um.

O logotipo e servido aberto, porque quem o vai buscar sao os fornecedores de
correio a avaliar uma mensagem e esses nunca terao sessao aqui.

Fica escrito no README que o Gmail e o Outlook so desenham a marca com um VMC,
e o painel avisa quando nao ha nenhum configurado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-09-07 08:12:11 -03:00
1 parent fcfee6a3f9
commit 6a602ab88f
16 files changed
+955 -5

No files matched your search

+28 -1
View File
@@ -24,6 +24,7 @@ paid tiers and no artificial limits on domains or mailboxes.
| **Delivery** | Durable outbound queue in Postgres with the retry ladder and RFC 3464 bounces; smarthost fallback for hosts with port 25 blocked |
| **DNS** | Cloudflare reconciliation of the expected records for every active domain, idempotent, with per-record verification against public resolvers |
| **Filing** | Sieve rules built in the UI, custom folders, and an RFC 5230 vacation responder that replies in thread and once per sender per week |
| **Identity** | Per-user profile photos with a circular crop editor, shown inside the webmail; BIMI logo hosting and validation for the mark external clients render |
| **Web** | Webmail at `/user/*` and an admin console at `/admin/*`, isolated by session audience, with TOTP two-factor, recovery codes, app passwords, and English, Portuguese and Spanish |
| **Console** | Server-side paging and filtering, user editing (role, quota, language, password, second factor), per-user aliases, and a diagnostics page at `/admin/tests` |
@@ -183,6 +184,32 @@ reads as a certificate problem and is usually a permissions problem.
`/admin/security` names the issuer and says outright when the certificate is
self-signed, which is the fastest way to tell the two apart.
### Profile photos and BIMI
These are two different things, and the difference is the whole story.
A **profile photo** is per user and is shown inside LambdaMail. No external
mail client reads it, and no protocol exists that would let one: the avatar
Gmail or Outlook shows beside a message comes from the *recipient's* own
contacts, or from the sender's Google or Microsoft account. A self-hosted
server cannot put an image there.
**BIMI** is what external clients do read, and it is per domain — one mark for
everyone who sends from it. Publishing one needs:
- DMARC at `p=quarantine` or `p=reject`, which this server already sets;
- a logo in SVG Tiny Portable/Secure — square viewBox, one `<title>`, nothing
active or external. `/admin/domains` validates an upload against every rule
at once and names the ones it fails, because a receiver that rejects a mark
simply shows nothing;
- a **Verified Mark Certificate** for Gmail and Outlook to render it. It is
issued against a registered trademark, costs on the order of US$1000 a year,
and without it the record is still valid — some clients use it, those two do
not.
The logo is served from `https://<mail host>/.well-known/bimi/default.svg` and
the record is published with the rest of the zone.
### DANE
Off by default, and worth understanding before turning on: a TLSA record that
@@ -228,7 +255,7 @@ Stated plainly, because a mail server that half works loses mail:
- **Read-only console panels.** The guided domain onboarding checklist reports
on state rather than driving it.
- **Not implemented.** JMAP, BIMI, CalDAV/CardDAV, multi-node. Backups are
- **Not implemented.** JMAP, CalDAV/CardDAV, multi-node. Backups are
documented but not automated.
- **DANE** is off by default. See [DANE](#dane) for turning it on; under
`TLS_MODE=traefik` it stays off deliberately, because the proxy's key changes