CI / Lint de idioma (R9) (push) Successful in 5s
CI / Go (protocols) (push) Successful in 2m34s
CI / TypeScript (storage) (push) Successful in 15s
CI / Testes (webmail) (push) Successful in 51s
CI / TypeScript (auth) (push) Successful in 2m39s
CI / Trivy (dependências, segredos e Dockerfiles) (push) Successful in 9s
CI / Imagem (migrations) (push) Successful in 5s
CI / Imagem (auth) (push) Successful in 17s
CI / Imagem (protocols) (push) Successful in 7s
CI / Imagem (rspamd) (push) Successful in 5s
CI / Imagem (storage) (push) Successful in 17s
CI / Imagem (webmail) (push) Successful in 1m52s
CI / Stack completa (push) Successful in 1m11s
- CHANGELOG.md and CHANGELOG.pt-BR.md, from the project history. - .env.example now serves development and production; scripts/init-env.sh (make env) creates .env with random secrets. .env.production.example is gone. - docs/RELEASING.md: the pipeline, how a version is cut and deployed, the runners. - DEPLOYMENT.md and both READMEs updated for Gitea and the new env flow. - Service versions set to 1.0.0. - CI: the proxy network step is idempotent and the stack is torn down always, since the runner's Docker daemon now outlives a job. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
395 lines
24 KiB
Markdown
395 lines
24 KiB
Markdown
<p align="center"><img src="docs/img/lambdamail-logo.svg" width="120" alt="LambdaMail"></p>
|
|
<h1 align="center">LambdaMail</h1>
|
|
<p align="center">Seu próprio servidor de e-mail, com um webmail que dá gosto de usar.<br>SMTP, IMAP e POP3 · SPF, DKIM, DMARC e ARC · Webmail e console de administração · Um único <code>docker-compose.yaml</code>, feito para o Coolify.</p>
|
|
|
|
<p align="center"><a href="docs/WEBMAIL.md">Webmail</a> · <a href="docs/DEPLOYMENT.md">Implantação</a> · <a href="docs/OPERATIONS.md">Operação</a> · <a href="docs/MAILBOX-CORE.md">Núcleo da caixa</a> · <a href="docs/RELEASING.md">Lançamentos</a> · <a href="CHANGELOG.pt-BR.md">Mudanças</a> · <a href="LICENSE">Licença</a></p>
|
|
|
|
<p align="center"><a href="README.md">English</a> · <b>Português</b></p>
|
|
|
|
<p align="center"><img src="docs/img/webmail-dark-pt.png" width="900" alt="O webmail do LambdaMail no tema escuro: a caixa de entrada em conversas à esquerda, e a conversa Launch checklist aberta à direita, com a mensagem mais recente expandida e as anteriores em uma linha cada"><br><sub>A interface real rodando numa caixa inventada: nomes, endereços e mensagens são fictícios.</sub></p>
|
|
|
|
---
|
|
|
|
O LambdaMail é um servidor de e-mail próprio, com webmail e console de
|
|
administração integrados, feito para rodar no [Coolify](https://coolify.io) a
|
|
partir de um único `docker-compose.yaml`.
|
|
|
|
SMTP, IMAP, POP3 e ManageSieve, com SPF, DKIM, DMARC, ARC, MTA-STS e TLS-RPT,
|
|
automação de DNS pela Cloudflare e filtro de spam e de vírus. Sem planos pagos e
|
|
sem limites artificiais de domínios ou caixas.
|
|
|
|
> **Versão 1.0.0** ([mudanças](CHANGELOG.pt-BR.md)). O caminho do e-mail, a postura de segurança e as
|
|
> interfaces web funcionam de ponta a ponta, e há uma suíte de testes
|
|
> automatizados por trás. Leia [O que não está pronto](#o-que-não-está-pronto)
|
|
> antes de colocar e-mail de verdade nele.
|
|
|
|
## O que ele faz
|
|
|
|
| Área | |
|
|
| :--- | :--- |
|
|
| **Protocolos** | SMTP MX (25), Submission (587 STARTTLS, 465 TLS implícito), IMAP (143/993) com UID, flags, IDLE e CONDSTORE, POP3 (110/995), ManageSieve (4190) |
|
|
| **Autenticação** | SPF, DKIM (assinatura dupla Ed25519 + RSA), DMARC, selo ARC, DANE/TLSA e MTA-STS na entrega, ingestão de TLS-RPT |
|
|
| **Filtragem** | Pontuação do Rspamd com greylisting, RBLs e limite de taxa; ClamAV; Sieve |
|
|
| **Entrega** | Fila de saída durável no Postgres, com a escada de tentativas e os retornos da RFC 3464; relay (smarthost) para hospedagens com a porta 25 bloqueada |
|
|
| **DNS** | Reconciliação na Cloudflare dos registros esperados de cada domínio ativo, idempotente, com verificação de cada registro em resolvedores públicos |
|
|
| **Organização** | Regras Sieve criadas na interface, pastas próprias e um respondedor de férias (RFC 5230) que responde dentro da conversa e uma vez por remetente por semana |
|
|
| **Identidade** | Foto de perfil por usuário, com editor de recorte circular, exibida no webmail; hospedagem e validação do logo BIMI, a marca que os clientes externos exibem |
|
|
| **Web** | Webmail em `/user/*` e console de administração em `/admin/*`, isolados pela audiência da sessão, com dois fatores TOTP, códigos de recuperação, senhas de aplicativo, em inglês, português e espanhol |
|
|
| **Sessões** | As sessões do webmail duram 30 dias e se renovam enquanto são usadas; o console continua em 8 horas. Várias contas podem estar abertas ao mesmo tempo e se alternam pela barra lateral, sem sair |
|
|
| **Remetentes confiáveis** | Confiar num remetente tira o e-mail dele do spam e mostra sempre as imagens, guardado no servidor por caixa. "Não é spam" na pasta Spam faz as duas coisas num clique. Um From forjado não é resgatado: a entrega exige DMARC, ou SPF do domínio do From |
|
|
| **Conversas** | O e-mail é listado em uma linha por conversa, com quem escreveu, quantas mensagens e se você já respondeu. As mensagens se empilham no leitor e a resposta é escrita embaixo delas. Uma chave desliga |
|
|
| **Leitura** | Uma mensagem escrita à mão usa as cores do próprio app; uma desenhada (newsletter, recibo) mantém a folha branca. O histórico citado se recolhe, imagens remotas ficam bloqueadas até você permitir, e dá para ver uma mensagem simples como foi enviada |
|
|
| **Aparência** | Temas escuro (padrão) e claro, menu de comandos em `Ctrl K` / `Cmd K`, atalhos `c` e `/`, e um aplicativo instalável com ícone próprio. As fontes são servidas pelo próprio app |
|
|
| **Console** | Paginação e filtros no servidor, edição de usuário (papel, cota, idioma, senha, segundo fator), aliases por usuário e uma página de diagnóstico em `/admin/tests` |
|
|
|
|
## Arquitetura
|
|
|
|
Dois ambientes de execução, divididos pelo que cada um faz melhor:
|
|
|
|
- **`protocols`** (Go) — todos os listeners de protocolo, o worker de entrega, o
|
|
tratamento dos certificados TLS, a reconciliação de DNS e a API de e-mail do
|
|
webmail (mensagens, conversas, rascunhos). É dono da semântica de pastas, UIDs,
|
|
flags e blobs, e decide, na entrega, a qual conversa uma mensagem pertence e
|
|
se o e-mail de um remetente confiável pula o spam.
|
|
- **`auth`** (TypeScript) — logins, sessões e as contas abertas num navegador,
|
|
dois fatores, remetentes confiáveis, preferências e a API do console de
|
|
administração.
|
|
- **`webmail`** (Next.js) — as duas superfícies web e o proxy que dá ao navegador
|
|
uma origem única.
|
|
- **`storage`** (TypeScript) — só a verificação de saúde, por enquanto.
|
|
|
|
O Postgres é a fonte da verdade de tudo, inclusive da fila de saída. O Redis é
|
|
cache e índice de despacho; perdê-lo não perde nenhum e-mail.
|
|
|
|
```
|
|
┌──────────┐
|
|
navegador ───────▶│ webmail │──┬─▶ auth (sessões, contas, remetentes confiáveis, admin)
|
|
└──────────┘ └─▶ protocols (ler/enviar e-mail, conversas, DKIM, TLS)
|
|
│
|
|
internet ──25/587/465/143/993──────────┘
|
|
│
|
|
Postgres ─┴─ Redis ─ Rspamd ─ ClamAV
|
|
```
|
|
|
|
## Usando o webmail
|
|
|
|
A referência é o **[docs/WEBMAIL.md](docs/WEBMAIL.md)** (em inglês). Em resumo:
|
|
|
|
- **Continue conectado.** Uma sessão dura 30 dias e se renova a cada dia de uso.
|
|
Adicione outra conta pelo menu no pé da barra lateral e alterne entre elas;
|
|
sair deixa as outras abertas.
|
|
- **Confie num remetente.** O escudo ao lado do nome do remetente confia nele: o
|
|
e-mail dele deixa de ir para o spam e as imagens carregam. Na pasta Spam,
|
|
**Não é spam** move a mensagem para a caixa de entrada e confia no remetente.
|
|
- **Leia em conversas.** Uma linha por conversa; abra e responda embaixo das
|
|
mensagens. Em Configurações → Idioma dá para desligar.
|
|
- **Aperte `Ctrl K` (ou `Cmd K`).** Pastas, Escrever, Configurações, contas e
|
|
aparência, digitando. `c` escreve uma mensagem e `/` busca.
|
|
- **Escolha um tema** (escuro, claro ou do sistema) no mesmo menu do pé da barra
|
|
lateral. O escuro é o padrão.
|
|
|
|
| Tecla | |
|
|
| :--- | :--- |
|
|
| `Ctrl K` / `Cmd K` | Menu de comandos |
|
|
| `c` | Nova mensagem |
|
|
| `/` | Buscar na lista |
|
|
|
|
<p align="center"><img src="docs/img/webmail-light-pt.png" width="440" alt="A mesma conversa no tema claro"> <img src="docs/img/designed-light-pt.png" width="440" alt="Uma newsletter desenhada mantendo a própria folha branca, no tema claro"></p>
|
|
<p align="center"><sub>O tema claro, e uma mensagem desenhada (uma newsletter, um recibo) que mantém a folha para a qual foi feita. Uma mensagem escrita à mão usa as cores do próprio app.</sub></p>
|
|
|
|
<p align="center"><img src="docs/img/menu-dark-pt.png" width="620" alt="O menu de comandos aberto sobre uma conversa, listando ações, pastas e aparência"><br><sub>O menu de comandos, em <code>Ctrl K</code> ou <code>Cmd K</code>.</sub></p>
|
|
|
|
## Implantação
|
|
|
|
Para uma VPS de verdade, siga o **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)** (em
|
|
inglês). Ele cobre as três coisas que nenhum código faz por você — DNS reverso,
|
|
a delegação DNSSEC e conseguir a porta 25 liberada — e a ordem em que o resto
|
|
precisa acontecer.
|
|
|
|
A versão curta:
|
|
|
|
```bash
|
|
scripts/init-env.sh --production # .env com segredos aleatórios; lista o que definir
|
|
make preflight # confere porta 25, PTR, RBLs, escopo do token
|
|
docker compose up -d
|
|
make create-admin EMAIL=voce@exemplo.com PASSWORD='...'
|
|
```
|
|
|
|
`make create-admin` não é opcional: uma instalação nova não tem nenhuma caixa, e
|
|
todo caminho para o console precisa de uma.
|
|
|
|
### Portas
|
|
|
|
Tudo abaixo é TCP. "Entrada" é o que o firewall do host e o grupo de segurança do
|
|
provedor precisam permitir; "saída" é o que o host precisa conseguir alcançar.
|
|
|
|
| Porta | Direção | Quem conecta | Obrigatória? | Observações |
|
|
| ---: | :--- | :--- | :--- | :--- |
|
|
| 25 | entrada | Qualquer MTA remetente | **Sim**, para receber | Precisa estar aberta ao mundo. Tem limite de taxa e não é autenticada — é assim que o SMTP funciona. |
|
|
| 25 | **saída** | Este host → MTAs remotos | **Sim**, para enviar | A que a maioria dos provedores bloqueia. Sem ela, configure um relay (`RELAY_HOST`); veja [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). |
|
|
| 80 | entrada | Let's Encrypt, navegadores | **Sim** | Desafio ACME HTTP-01 e o redirecionamento para HTTPS. Fechá-la significa não ter certificado. |
|
|
| 443 | entrada | Navegadores | **Sim** | Webmail, console, política MTA-STS, autodiscover. |
|
|
| 587 | entrada | Clientes de e-mail | Recomendada | Submission com STARTTLS. A porta que os clientes devem usar. |
|
|
| 465 | entrada | Clientes de e-mail | Opcional | Submission com TLS implícito, para clientes que insistem. |
|
|
| 143 | entrada | Clientes de e-mail | Opcional | IMAP com STARTTLS. Prefira a 993. |
|
|
| 993 | entrada | Clientes de e-mail | Recomendada | IMAPS. |
|
|
| 110 / 995 | entrada | Clientes de e-mail | Opcional | POP3 / POP3S. Deixe fechada, a menos que algo precise de POP3. |
|
|
| 4190 | entrada | Clientes de e-mail | Opcional | ManageSieve, para editar filtros num cliente de desktop. |
|
|
| 53 | saída | Este host → resolvedor | **Sim** | Consultas de MX, SPF, DKIM, DANE e MTA-STS. UDP **e** TCP — respostas DNSSEC estouram o UDP. |
|
|
| 443 | saída | Este host → APIs | Se usado | Automação de DNS da Cloudflare, busca da política MTA-STS, atualização de assinaturas do ClamAV. |
|
|
|
|
As portas 110, 995 e 4190 são publicadas pelo compose, mas vale fechá-las no
|
|
firewall a menos que algum cliente precise delas — cada porta aberta é mais uma
|
|
coisa para manter atualizada.
|
|
|
|
### O que o host precisa permitir
|
|
|
|
- **DNS reverso (PTR)** do IPv4 público resolvendo para `PRIMARY_MAIL_HOST`, e
|
|
esse nome resolvendo de volta para o mesmo endereço. Quem configura é o dono do
|
|
bloco de IPs — o painel do seu provedor, nunca a Cloudflare. Gmail e Outlook
|
|
mandam para o spam ou recusam e-mail sem isso. Se o host tem IPv6 público e
|
|
publica um registro AAAA, ele também precisa de PTR: remetentes que conectam
|
|
por IPv6 são julgados pelo reverso do IPv6.
|
|
- **Porta 25 de saída liberada.** Peça ao provedor; um timeout, e não uma recusa,
|
|
ao conectar num MX remoto é a assinatura de um bloqueio.
|
|
- **Docker** com o plugin compose, e um usuário no grupo `docker` ou root.
|
|
- **Portas abaixo de 1024** são abertas pelo proxy do Docker no host, então o
|
|
daemon precisa do privilégio — o padrão numa instalação normal. Os contêineres
|
|
em si rodam sem privilégios.
|
|
- **O diretório de certificados do proxy**, só com `TLS_MODE=traefik` — veja
|
|
[Certificados](#certificados) abaixo, e prefira `TLS_MODE=acme` se puder.
|
|
- **Disco** para o spool de e-mail (`protocols_spool`) e o Postgres. O e-mail é
|
|
guardado uma vez, com contagem de referências, mas planeje o crescimento.
|
|
- **Um firewall que não limite a porta 25 até a inutilidade.** Alguns provedores
|
|
aplicam limites de SYN agressivos que parecem falhas de entrega intermitentes.
|
|
|
|
## Rodando localmente
|
|
|
|
```bash
|
|
make env # .env a partir do .env.example, com segredos aleatórios
|
|
make gen-dev-cert # certificado autoassinado para os listeners
|
|
make up # compila a partir do código pelo overlay de build
|
|
make seed # uma caixa postmaster@example.test de teste
|
|
```
|
|
|
|
O webmail fica em `http://localhost:3000`. A senha do seed é `dev-password-only`
|
|
— é um dado de teste, não uma credencial.
|
|
|
|
Para ver ou trabalhar na interface sem nenhum serviço de e-mail, rode a caixa
|
|
inventada de [`scripts/screenshots/`](scripts/screenshots/README.md) e aponte o
|
|
webmail para ela; o mesmo servidor de demonstração gerou as imagens acima.
|
|
|
|
## Testes
|
|
|
|
```bash
|
|
make test-db-up # um Postgres descartável na :55432; imprime as duas variáveis a exportar
|
|
make test # suíte do Go, do auth e do webmail
|
|
make lint # go vet, tsc e o lint de idioma
|
|
```
|
|
|
|
As suítes do Go e do auth incluem testes de integração que precisam de Postgres.
|
|
Eles são pulados sem `TEST_DATABASE_URL` e rodam contra um banco de verdade no
|
|
CI, que é onde as regressões de segurança são garantidas — senha errada, código
|
|
TOTP errado, revogação de sessão, isolamento entre superfícies, leitura entre
|
|
caixas e, agora, sessões que se renovam, contas que se alternam, remetentes
|
|
confiáveis e a qual conversa uma resposta pertence. Uma execução verde **sem** a
|
|
variável pula tudo isso, por isso o `make test` avisa em voz alta; rode com o
|
|
banco antes de confiar nela.
|
|
|
|
A suíte do webmail é de arquivos e lógica (`vitest`): ela protege o sistema de
|
|
cores, o sanitizador, os modos do leitor, o desfazer e o descartar do editor, e
|
|
que toda frase exista nos três idiomas. As telas em si são conferidas num
|
|
navegador de verdade; veja [docs/WEBMAIL.md](docs/WEBMAIL.md#testing-it).
|
|
|
|
## Configuração
|
|
|
|
Cada variável está documentada em linha em
|
|
[`.env.example`](.env.example), o arquivo único para desenvolvimento e produção.
|
|
As que não têm padrão seguro (o `scripts/init-env.sh` gera os segredos):
|
|
|
|
| Variável | |
|
|
| :--- | :--- |
|
|
| `JWT_SECRET` | Assina as sessões. No mínimo 32 caracteres; o serviço se recusa a iniciar abaixo disso, em vez de cair numa constante. |
|
|
| `LAMBDAMAIL_MASTER_KEY` | Cifra as chaves DKIM, os segredos TOTP e os tokens de provedores. **Faça backup dela separado do banco** — perdê-la torna irrecuperável todo segredo guardado. |
|
|
| `POSTGRES_PASSWORD`, `REDIS_PASSWORD` | Sem padrão; o compose falha sem elas. |
|
|
| `MAIL_DOMAIN`, `PRIMARY_MAIL_HOST`, `PUBLIC_IPV4` | Identidade. `PRIMARY_MAIL_HOST` precisa bater com o PTR de `PUBLIC_IPV4`. |
|
|
|
|
### Certificados
|
|
|
|
Dois modos, e a escolha importa mais do que parece.
|
|
|
|
**`TLS_MODE=acme` (recomendado).** O LambdaMail obtém o próprio certificado para
|
|
`PRIMARY_MAIL_HOST`, `mta-sts.<domínio>` e `autoconfig.<domínio>` por DNS-01,
|
|
usando o token da Cloudflare já configurado para o DNS. Precisa de
|
|
`LAMBDAMAIL_MASTER_KEY` para selar as chaves da conta e do certificado, e de
|
|
`ACME_EMAIL`. É o único modo em que o DANE funciona, porque é o único em que a
|
|
chave do próximo certificado existe antes do próprio certificado.
|
|
|
|
**`TLS_MODE=traefik`.** Os listeners de e-mail leem o certificado que o proxy
|
|
reverso já obteve, em `COOLIFY_PROXY_DIR`, montado só para leitura. Duas coisas
|
|
precisam ser verdade, e nenhuma é óbvia quando não são:
|
|
|
|
- o proxy precisa ter uma rota para `PRIMARY_MAIL_HOST`, ou esse nome nunca
|
|
aparece no `acme.json` e não existe certificado para ler;
|
|
- o `acme.json` precisa ser legível pelo usuário não root do contêiner. Deixado
|
|
no modo `600` de propriedade do root — como o proxy o grava — ele não é.
|
|
|
|
As duas falhas são silenciosas do mesmo jeito: o HTTPS continua funcionando,
|
|
porque o proxy o serve, enquanto os listeners de e-mail caem para um **certificado
|
|
autoassinado** e todo cliente que verifica recusa SMTP, IMAP e POP3. Parece
|
|
problema de certificado e quase sempre é problema de permissão.
|
|
|
|
`/admin/security` informa o emissor e diz de forma direta quando o certificado é
|
|
autoassinado, que é o jeito mais rápido de distinguir os dois casos.
|
|
|
|
### Fotos de perfil e BIMI
|
|
|
|
São duas coisas diferentes, e a diferença é a história toda.
|
|
|
|
Uma **foto de perfil** é por usuário e aparece dentro do LambdaMail. Nenhum
|
|
cliente de e-mail externo a lê, e não existe protocolo que permita isso: o avatar
|
|
que o Gmail ou o Outlook mostra ao lado de uma mensagem vem dos contatos do
|
|
*destinatário*, ou da conta Google ou Microsoft do remetente. Um servidor próprio
|
|
não consegue pôr uma imagem ali.
|
|
|
|
O **BIMI** é o que os clientes externos leem, e é por domínio: uma marca para
|
|
todos que enviam dele. Publicar uma exige três coisas, e o console
|
|
(`/admin/domains`, dentro do domínio) cuida de cada uma:
|
|
|
|
1. **O logo.** Envie um PNG ou um SVG. O estúdio o converte para o que o BIMI
|
|
aceita, SVG Tiny Portable/Secure numa tela quadrada de no máximo 32 KB, sem
|
|
nada ativo ou externo dentro, e deixa você posicioná-lo como no editor da foto
|
|
de perfil: arrastar, ampliar, cor de fundo, com uma prévia dentro do círculo
|
|
ou do quadrado arredondado que os clientes desenham. De um SVG é removido o que
|
|
o formato proíbe (scripts, links, bitmaps embutidos, metadados de editor) e
|
|
avisa o que foi; um PNG é vetorizado, o que perde algum detalhe, então envie o
|
|
SVG quando tiver. O servidor valida o resultado de novo contra todas as regras
|
|
de uma vez, porque um receptor que rejeita a marca simplesmente não mostra nada.
|
|
2. **DMARC em `p=quarantine` ou `p=reject`** com `pct=100`, o que este servidor
|
|
já configura.
|
|
3. **Um certificado, para o Gmail e o Apple Mail exibirem.** Um Verified Mark
|
|
Certificate (VMC, para marca registrada) ou um Common Mark Certificate (CMC,
|
|
para marca em uso) é **emitido por uma autoridade certificadora** (DigiCert,
|
|
Entrust, SSL.com e outras) depois que ela verifica a organização. Ele não pode
|
|
ser gerado aqui a partir de uma imagem, e nada assinado aqui seria confiável.
|
|
O que o console faz: entrega o SVG exato para enviar à autoridade, recebe o
|
|
`.pem` que ela devolver e recusa o que não funcionaria (o logo dentro dele
|
|
precisa ser, byte a byte, o publicado; precisa cobrir o domínio, estar na
|
|
validade e nunca conter uma chave privada). Depois o serve em
|
|
`https://<host de e-mail>/.well-known/bimi/vmc.pem` e aponta a tag `a=` para
|
|
lá. Sem certificado o registro continua válido e alguns clientes o usam; o
|
|
Gmail e o Apple Mail, não.
|
|
|
|
"Verificar configuração" no painel olha o que um receptor olharia: o arquivo do
|
|
logo, o DMARC, o registro BIMI no DNS, a URL pública do logo, o certificado e a
|
|
URL pública dele, e aponta o que está errado.
|
|
|
|
O logo é servido em `https://<host de e-mail>/.well-known/bimi/<domínio>/default.svg`
|
|
e o certificado em `.../<domínio>/vmc.pem`: um host de e-mail, um caminho por
|
|
domínio, para que cada domínio hospedado possa publicar o seu. (O antigo
|
|
`/.well-known/bimi/default.svg` escolhia o domínio pelo nome do host, o que só
|
|
conseguia nomear o domínio principal; o registro de todo outro domínio apontava
|
|
para um 404. Ele ainda responde, para os registros já publicados.)
|
|
|
|
**O registro de DNS acompanha a imagem.** Com um token da Cloudflare configurado,
|
|
o registro TXT `default._bimi.<domínio>` faz parte do que o reconciliador de DNS
|
|
publica, e salvar ou remover o logo ou o certificado no console dispara essa
|
|
reconciliação sozinho ("Publicar no DNS agora" faz o mesmo sob demanda). O
|
|
registro é criado quando há um logo, atualizado quando o certificado é
|
|
adicionado e removido quando o logo é removido, mas só um registro que este
|
|
reconciliador publicou: um feito à mão é deixado em paz.
|
|
|
|
Um certificado guardado só é anunciado (`a=`) enquanto for válido para o logo
|
|
servido. O certificado atesta um SVG exato, então trocar o logo o encerra, e um
|
|
registro que continuasse apontando para ele faria todo receptor rejeitar o BIMI,
|
|
inclusive os que mostrariam o logo sem certificado. O registro então volta a só
|
|
`l=`, e o console diz por que o certificado foi retido. O painel, a verificação e
|
|
o reconciliador tomam essa decisão da mesma função (`bimi.Publish`), então não
|
|
podem discordar sobre ela.
|
|
|
|
Uma foto de perfil alterada aparece na hora em todo o webmail: cada foto carrega
|
|
uma versão que é incrementada ao salvar, nesta aba e nas outras. O servidor deixa
|
|
o navegador reaproveitar uma por um minuto e depois revalida.
|
|
|
|
### DANE
|
|
|
|
Desligado por padrão, e vale entender antes de ligar: um registro TLSA que não
|
|
bate com a chave em uso não degrada a entrega, ele a **interrompe** em todo
|
|
remetente que valida. Duas coisas precisam ser verdade primeiro.
|
|
|
|
- **DNSSEC na zona, com o registro DS publicado no registrador.** O DANE confia
|
|
no registro TLSA porque o DNSSEC o assina; sem a cadeia não há o que confiar, e
|
|
os validadores tratam o registro como ausente.
|
|
- **`TLS_MODE=acme`.** O LambdaMail precisa ser dono da chave para publicar a
|
|
associação da *próxima* antes de trocar para ela. Com `TLS_MODE=traefik` o
|
|
proxy renova no próprio ritmo e o registro deixaria de bater em silêncio.
|
|
|
|
Então defina `OUTBOUND_DANE_ENABLED=true`. O gerenciador publica `3 1 1` —
|
|
DANE-EE sobre o SubjectPublicKeyInfo, o único uso que um MTA receptor deveria
|
|
oferecer — e cuida da troca publicando a associação atual e a próxima juntas,
|
|
esperando a propagação antes de emitir contra a chave nova.
|
|
|
|
`/admin/security` mostra o digest do certificado que está de fato em uso. Esse
|
|
número e o registro TLSA no DNS precisam ser iguais; quando divergem, o e-mail é
|
|
recusado e mais nada informa o motivo:
|
|
|
|
```bash
|
|
dig +short TLSA _25._tcp.mail.exemplo.com
|
|
```
|
|
|
|
## Operação
|
|
|
|
| | |
|
|
| :--- | :--- |
|
|
| `make preflight` | Saída pela porta 25, PTR e FCrDNS, listagens em RBLs, escopo do token da Cloudflare, presença do certificado |
|
|
| `make create-admin EMAIL=… PASSWORD='…'` | Primeiro administrador numa instalação nova |
|
|
| `make reset-password EMAIL=… PASSWORD='…'` | Recupera uma conta bloqueada e encerra as sessões dela |
|
|
| `docker compose logs -f protocols` | Entrega, TLS e reconciliação de DNS |
|
|
| `GET /health` no `protocols` | `200` saudável, `200` com `X-LambdaMail-Health: degraded` quando um certificado precisa de atenção, `503` quando uma dependência caiu |
|
|
|
|
O [docs/OPERATIONS.md](docs/OPERATIONS.md) cobre backups, rotação de
|
|
certificados, o que significam os alertas e como ler uma falha de entrega.
|
|
|
|
## O que não está pronto
|
|
|
|
Dito com franqueza, porque um servidor de e-mail que funciona pela metade perde
|
|
e-mail:
|
|
|
|
- **Painéis do console somente de leitura.** O checklist guiado de entrada de
|
|
domínio relata o estado em vez de conduzi-lo.
|
|
- **Anexos do editor.** O seletor lista os arquivos, mas nada é enviado, e o app
|
|
avisa isso na tela. Ler e baixar os anexos recebidos funciona.
|
|
- **Aprendizado do filtro de spam.** "Não é spam" para de condenar aquele
|
|
remetente, mas o Rspamd não aprende com isso, então e-mails parecidos de
|
|
desconhecidos são julgados como antes.
|
|
- **As conversas são agrupadas na entrega.** E-mails antigos são agrupados por
|
|
assunto, e só onde existe uma resposta; uma conversa cujas respostas perderam o
|
|
`References` e o assunto pode aparecer em linhas separadas.
|
|
- **Não implementado.** IMAP `APPEND`, JMAP, CalDAV/CardDAV, multi-nó. Os backups
|
|
estão documentados, mas não automatizados.
|
|
- **DANE** vem desligado. Veja [DANE](#dane) para ligá-lo; com
|
|
`TLS_MODE=traefik` ele fica desligado de propósito, porque a chave do proxy muda
|
|
a cada renovação e o registro TLSA publicado deixa de bater.
|
|
|
|
## Contribuindo
|
|
|
|
- Todo o código, os comentários e os identificadores são em inglês. O
|
|
`make lint` garante isso (e, no código, só ASCII: escreva `é`, não uma
|
|
letra acentuada).
|
|
- As cores vêm das variáveis do tema em `globals.css`, nunca de um valor literal,
|
|
e uma nova é definida nos dois temas; veja
|
|
[docs/WEBMAIL.md](docs/WEBMAIL.md#the-colour-system). O logo é gerado por
|
|
[`scripts/brand/`](scripts/brand/README.md).
|
|
- Migrations novas são numeradas em `migrations/` e rodam no deploy. O código de
|
|
entrega não pode depender de uma que talvez não tenha rodado: os testes de ponta
|
|
a ponta aplicam só a primeira.
|
|
- Os testes acompanham a mudança que precisa deles; as suítes acima precisam
|
|
continuar verdes.
|
|
- Os commits citam a issue: `#N: o que mudou e por quê`.
|
|
- Uma mudança que alguém vai notar entra em *Não lançado* no [CHANGELOG.pt-BR.md](CHANGELOG.pt-BR.md)
|
|
(e no equivalente em inglês). Como se lança uma versão: [docs/RELEASING.md](docs/RELEASING.md).
|
|
|
|
## Licença
|
|
|
|
MIT — veja [LICENSE](LICENSE).
|