Files
LambdaMail/README.pt-BR.md
T
Lucas RayanandClaude Sonnet 5.5 1e06491019 docs: a README with a face, in English and Portuguese; decode every HTML entity in previews
The README opens like a product page: the mark, a line on what it is, the
links, the language switch and a picture of the real interface running on an
invented mailbox, in dark and light and in both languages. scripts/screenshots
holds that invented mailbox and the script that photographs it, so the pictures
can be redone. Previews now decode any HTML entity through the browser rather
than a short list, so a Portuguese or marketing preview no longer prints
ç or ·.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 07:55:12 -03:00

24 KiB

LambdaMail

LambdaMail

Seu próprio servidor de e-mail, com um webmail que dá gosto de usar.
SMTP, IMAP e POP3 · SPF, DKIM, DMARC e ARC · Webmail e console de administração · Um único docker-compose.yaml, feito para o Coolify.

Webmail · Implantação · Operação · Núcleo da caixa · Licença

English · Português

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
A interface real rodando numa caixa inventada: nomes, endereços e mensagens são fictícios.


O LambdaMail é um servidor de e-mail próprio, com webmail e console de administração integrados, feito para rodar no Coolify 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.

Estado: pré-lançamento. 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 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 (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

A mesma conversa no tema claro Uma newsletter desenhada mantendo a própria folha branca, no tema claro

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.

O menu de comandos aberto sobre uma conversa, listando ações, pastas e aparência
O menu de comandos, em Ctrl K ou Cmd K.

Implantação

Para uma VPS de verdade, siga o 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:

cp .env.production.example .env      # depois troque cada CHANGE
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.
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 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

cp .env.example .env
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/ e aponte o webmail para ela; o mesmo servidor de demonstração gerou as imagens acima.

Testes

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.

Configuração

Cada variável está documentada em linha em .env.production.example. As que não têm padrão seguro:

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:

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 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 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. O logo é gerado por scripts/brand/.
  • 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ê.

Licença

MIT — veja LICENSE.