Download
Código-fonte:
curl -O https://minirepo.minidc.com.br/download/minimail.tar.gz
O download é liberado apenas para IPs autorizados.
Changelog
-
1.40.0 [] 27/08/2026### Corrigido - **Remover domínio quebrava com `zimbra_syncs`.** Era o mesmo defeito já corrigido do lado de `users` e deixado pela metade: a varredura por metadados valia para as contas, e o lado dos domínios seguia com uma lista escrita à mão. Aí a tabela `zimbra_syncs` (1.36.0) ficou de fora. Agora os dois lados saem dos metadados. Com uma diferença de semântica que a regra genérica erraria: em `mail_lists`, `webhooks` e `api_tokens` o `domain_id` **nulo significa global**. Anular ao remover promoveria a lista de bloqueio, o webhook e o token daquele cliente ao **servidor inteiro**. Para domínio a regra é apagar sempre — o oposto de `users`, onde anular preserva a trilha de auditoria. - **Importação do Zimbra dava 504 em lista grande.** Rodava dentro da requisição; passando do `proxy_read_timeout` do nginx, o navegador recebia erro **enquanto o gunicorn seguia criando contas por trás** — e as senhas geradas, mostradas uma única vez, iam junto com a resposta perdida. Cada conta custa dezenas de segundos (agenda e contatos são duas consultas ao Zimbra, 60 s de timeout cada). Agora roda em segundo plano, com progresso ao vivo e log por conta; dá para fechar a página e voltar. - **Erro numa conta não derruba o lote** — numa lista de cinquenta, uma agenda que falha custava tudo. - **Recarregar não perde as senhas.** Antes o resultado vivia na sessão e era consumido na primeira leitura: um F5 apagava. Fica até o botão "já anotei", que é o que remove o arquivo (0600) do disco. - Exceção na thread deixava o estado em `running` para sempre — a tela mostrava "Importando…" eternamente, sem dizer o que houve. Agora registra o erro e avisa que o já criado vale e que repetir pega o resto. - **Os 12 scripts de cron despejavam traceback de cem linhas quando o banco estava fora** (reinício do Postgres, máquina subindo). O do dashboard roda a cada minuto: uma parada de dez minutos enterra o log em mil linhas e esconde qualquer erro real que venha depois. Falha de **conexão** virou uma linha, com saída 0. Qualquer outra exceção continua saindo inteira — essas são defeito nosso. ### Adicionado - **Sincronização repetida do imapsync durante a migração.** Por conta, na tela de migração: 30 min, 1 h, 3 h, 6 h, 12 h ou 1 dia. Enquanto a migração não vira, o servidor antigo continua recebendo — sem repassar, o que chegou depois da primeira cópia fica para trás. - **A senha deixa de ser pedida a cada volta**: ela era apagada quando a migração terminava; agora fica guardada enquanto a repetição está ligada e some ao desligar (a virada). Migração criada sem "continuar automaticamente" não tem senha guardada, e a tela recusa ligar a repetição em vez de prometer algo que morreria na primeira volta. - O cron passou de 1 h para **15 em 15 minutos**: com o horário, um "repetir a cada 30 min" viraria de hora em hora sem ninguém entender por quê. O `flock` segue impedindo sobreposição, e continuam no máximo 3 por vez — várias conexões simultâneas ao mesmo provedor é o outro jeito de tomar bloqueio. ### Testes - O estado dos trabalhos em segundo plano vazava entre testes por um caminho fixo em `/tmp` (um `running` esquecido travava o teste seguinte). Isolado por teste. E nos testes de alias a importação passou a rodar na hora, no lugar da thread: ela disputava a conexão única do SQLite em memória com a sessão do teste e a suíte falhava em 2 de 3 execuções — por um motivo que não existe em produção, onde cada thread pega uma conexão do Postgres.
-
1.39.0 [] 27/08/2026### Adicionado - **Licença de instalação, assinada (Ed25519).** Sem licença válida, o **`/painel` inteiro** redireciona para a tela de licença — e só ela abre (bloquear também essa deixaria instalação nova inutilizável). **ATENÇÃO AO ATUALIZAR:** servidor que subir para esta versão **sem licença instalada fica com o painel bloqueado**. Emita a licença antes, ou instale pelo comando: `flask licenca-instalar arquivo.lic`. - **O fluxo de e-mail NÃO é tocado.** Postfix, Dovecot, SOGo e o webmail seguem funcionando. Bloquear entrega por causa de verificação de licença transformaria qualquer problema de relógio ou de hostname em interrupção de comunicação — custo maior que o risco coberto. Conferido no servidor: `/webmail20`, `/login` e `/SOGo/` respondem 200 com o painel bloqueado. - Bloqueia por ausente, assinatura inválida, vencida ou emitida para outro hostname. `--hostname '*'` emite sem amarra de servidor (migração). - **Instalação por upload de arquivo** ou colando o texto, na tela de bloqueio e em Configurações → Servidor (renovação, com o painel funcionando). O arquivo tem precedência sobre o texto — quem escolheu um arquivo foi ele que quis; o campo de texto costuma ter sobrado da tentativa anterior. - **Não substitui uma licença válida por uma que não está ok** (vencida, ou de outro servidor): colar a licença do cliente errado trancaria o próprio painel sem a pessoa ter feito nada de errado. Sem licença válida em uso, a assinada é aceita mesmo vencida — nada se perde, e a tela mostra o motivo exato em vez de um erro genérico. - Saída sem painel: `flask licenca` (estado) e `flask licenca-instalar`. - `tools/assinar_licenca.py` emite as licenças. **Roda na máquina do emissor, nunca num servidor de cliente** — precisa da chave privada, que é o que impede o cliente de emitir a própria. - A consulta de atualização passou a levar identificador e status da licença (parâmetro e `X-MiniMail-License`), para o emissor registrar. **Sem licença a consulta continua acontecendo**: quem ficou irregular por engano não pode perder o canal que conserta isso. **O que isto não é:** proteção do código. Quem tem root arranca a verificação — o processo é dele. O que ela dá, e que root nenhum tira: ele não consegue **forjar** uma licença (a assinatura é verificada com a chave pública embutida), então alteração vira deliberada e demonstrável; e a instalação se identifica ao atualizar, então cópia aparece do outro lado. Está escrito assim no cabeçalho de `app/licenca.py`. ### Testes - A suíte assina com um **par de chaves gerado na hora** e troca a chave pública que o app usa: nenhum teste depende da chave privada de verdade, que não vive no repositório e não deve. O fixture instala uma licença válida por padrão — sem isso, todo teste de tela do painel passaria a testar o bloqueio em vez do próprio assunto.
-
1.38.0 [] 27/08/2026### Adicionado - **Logo próprio do servidor** (Configurações → Servidor). Para servidor dedicado a um cliente que quer a marca dele. Aparece no topo do painel, no webmail e nas **telas de login** — que é o que o usuário dele vê primeiro. Remover volta ao logo do miniMAIL. - O arquivo vai para o **banco**, não para `app/static/`: o atualizador aplica a versão nova com `rsync --delete` preservando uma lista curta, e qualquer coisa dentro da árvore do app seria **apagada na primeira atualização**. No banco sobrevive e ainda entra no backup. - A URL leva o **ETag** do arquivo; sem isso o navegador continuaria mostrando o logo antigo do cache e pareceria que não salvou. - Rota **pública** de propósito (a tela de login precisa dela). SVG é servido com `default-src 'none'` e `nosniff`: por `<img>` um SVG não executa script, mas aberto direto na URL vira documento e executaria. - Limite de 512 KB e só formatos de imagem. Trocar é só super-admin. - **Logo ilegível volta ao padrão**: se o conteúdo guardado não decodificar, a tela apontaria para uma imagem que dá 404 e ficaria com o ícone de imagem quebrada — pior que o logo padrão. - **O logo próprio vale também no SOGo** (tela de login). O `apppw.js` é estático e não tem como perguntar ao app se há logo: ele tenta a imagem e, sem logo, o 404 dispara o `onerror` e entra o SVG do miniMAIL. Precisou de dois ajustes: a regra `.sg-logo img{display:none}` — que existe para esconder o logo do SOGo — pegava também o `<img>` do logo próprio, e o cache passou a depender da URL (versionada: 1 dia; sem versão, que é como o SOGo pede: 5 min, senão o SOGo mostraria o logo antigo por 24 h depois da troca). Favicon e ícone do PWA seguem os padrão, como combinado. ### Alterado - **Os arquivos do Drive passaram a ficar dentro da raiz dos e-mails** (`<raiz-do-vmail>/_drive/<id>/<chave>`). É o volume que o cliente dimensiona, e a mudança de diretório dos e-mails já leva o Drive junto; em discos separados, a chance de um encher sem ninguém olhar só dobra. - **Sem dia da virada:** arquivo antigo continua sendo servido de `/var/drive` enquanto não for copiado. Trocar o caminho e pronto tornaria inacessível tudo que já foi enviado. Quando os dois existem, o novo ganha — servir o antigo esconderia uma edição posterior. - `flask drive-mover` (com `--aplicar`) consolida; o diretório antigo **não** é apagado. `DRIVE_ROOT` no ambiente continua mandando: servidor que já escolheu o caminho dele não tem o caminho trocado por baixo. - O nome `_drive` começa com `_` e não tem ponto, então nunca colide com um domínio — quem varre a raiz separa os dois sem adivinhação. Isso importa porque duas medidas passariam a mentir: **"ocupado pelos e-mails"** somaria o Drive (a conta de caixa cresceria sozinha quando alguém subisse arquivo) e a **projeção de crescimento** da tela de Capacidade também — arquivo de Drive cresce por outro motivo e some quando o usuário apaga. As duas agora excluem `_drive`; o card **Disco** segue mostrando a partição inteira, que é o número certo ali.
-
1.37.1 [] 27/08/2026### Corrigido - **"Trocar senha" dentro do painel caía no login do webmail20.** O link apontava para `/account/password`, que lê o cookie `mmsession` (webmail), enquanto a sessão do painel vive no `mmadmin` — chegava lá sem sessão. Mesmo motivo pelo qual a central de ajuda já tinha rota própria sob `/painel`. Agora existe `/painel/senha`, reaproveitando a mesma tela. Duas coisas que vieram junto: - **administrador dedicado passa a trocar a própria senha pela interface** — ele não tem caixa de e-mail, então `/account/password` nunca funcionaria para ele; - entrou na lista de permitidos do **admin de domínio**: trocar a própria senha não pode depender de ser super-admin. - **Buscar na aba Contas devolvia a página em "Visão geral".** Formulário GET **não carrega fragmento**: ao enviar, o navegador substitui tudo depois do `?` e o `#accounts` do `action` desaparece, então o `initTabs` caía no padrão. (Os links de paginação não sofriam disso: são `<a>` com hash de verdade.) O formulário passou a mandar `tab=accounts` na query, lido pelo `initTabs` quando não há hash. O servidor **ignora** o parâmetro — há teste conferindo que o resultado é idêntico com e sem ele, para não virar mais uma entrada que muda o comportamento da rota.
-
1.37.0 [] 27/08/2026### Adicionado - **Lista de contas paginada (15 por página) e com busca** por e-mail ou nome. A busca e a página viajam na URL **com `#accounts`**: sem o hash a página reabre na "Visão geral" e parece que o clique não fez nada. Página além do fim cai na última em vez de 404 — chegar em `?p=9` e depois filtrar é normal, e uma página em branco pareceria que as contas sumiram. O paginador mostra uma janela de 5 páginas em volta da atual (com 200 contas seriam 14 botões numa linha só). - O card **Armazenamento** e a barra de cota passaram a somar o **domínio inteiro**, não a página: somar só o que está à vista faria o número mudar conforme a página aberta. ### Alterado - **O painel "Aliases (encaminhamento)" saiu da aba de contas do domínio.** Alias se gerencia na linha da conta, pela coluna **Aliases** — que é onde a pessoa está olhando quando pensa nisso. A tabela geral repetia a mesma informação num lugar onde ninguém procurava. - **Um caso não caiu junto:** o alias cujo destino **não é conta deste domínio** (endereço externo, grupo, ou conta que foi removida). Ele não aparece em linha nenhuma da lista, então sem nada no lugar ficaria invisível no painel e sem como remover — continuando a entregar mensagem. Entrou um painel **"Aliases sem conta correspondente"** que só existe quando há algum e some sozinho depois da limpeza. Daqui para frente esse caso quase não se cria: tanto o modal quanto a importação do Zimbra prendem o destino a uma conta; sobram os que já existiam.
-
1.36.1 [] 27/08/2026### Corrigido - **Excluir conta (e domínio) morria com erro de chave estrangeira.** Em produção: `ForeignKeyViolation ... (id)=(1) ainda é referenciada pela tabela "known_ips"`. Ao enumerar as chaves estrangeiras, faltavam **14 colunas**, não uma — `known_ips`, `drive_items`, `drive_shares`, `push_subscriptions`, `scheduled_mails`, `snoozed_mails`, `user_labels`, `user_signatures`, `mail_templates`, `api_tokens.created_by_id` e outras. **Não é regressão da remoção de domínio:** o `delete_user` tinha o mesmo defeito desde sempre, e qualquer conta que já tivesse feito login (o que cria `known_ips`) era impossível de excluir. Só apareceu agora porque remover domínio exercita contas reais. A causa era uma lista de tabelas escrita à mão, que envelheceu conforme o sistema cresceu. Agora a limpeza sai dos **metadados**: toda coluna com FK para `users.id`, na ordem filho-antes-do-pai (apagar `drive_items` antes de `drive_shares` violaria a FK entre elas). Coluna que aceita nulo vira nulo — preserva a auditoria; coluna obrigatória some junto. Tabela nova passa a ser tratada sozinha. - **Arquivos do Drive da conta excluída saem do disco.** Os caminhos são lidos **antes** do DELETE: depois não há como saber quais eram, e ficariam ocupando espaço para sempre. ### Testes - **O SQLite dos testes passou a validar chave estrangeira** (`PRAGMA foreign_keys=ON`). É a razão de fundo do defeito acima ter atravessado a suíte inteira: por padrão o SQLite aceita FK quebrada em silêncio, o Postgres não — então **nenhum teste conseguia pegar essa classe de defeito**. Com o pragma ligado, a suíte encontrou na hora um teste que apagava um `User` cru, fazendo o que o app não faz. - Teste novo monta uma conta com registro em **toda** tabela que a referencia (inclusive compartilhamento de Drive) e um que compara a varredura com os metadados — falha se alguém acrescentar tabela com FK para `users` que escape. - **Duas instabilidades da suíte, achadas pelo pragma.** O SQLite em memória sobrevive entre os testes do processo, então linhas de um vazavam para o seguinte (agora o fixture faz `drop_all` no fim); e um webhook **ativo** no cenário disparava uma thread que tentava o POST e gravava o resultado no banco, correndo com a própria exclusão. O arquivo falhava em 2 de 5 execuções. Agora 10 de 10 e a suíte inteira estável em 3 execuções seguidas.
-
1.36.0 [] 26/08/2026### Adicionado - **Espaço em disco no painel.** Card *Disco dos e-mails* com percentual e espaço livre (amarelo a partir de 80%, vermelho a partir de 90%), e os números completos em Configurações → Armazenamento. É `statvfs`, não `du` — metadados do filesystem, instantâneo. **Não é o mesmo que o card "Armazenamento"**: ali é a soma das caixas, aqui é a partição, que enche também por log, banco e backup e é ela que derruba o serviço. O percentual segue a conta do `df`, inclusive arredondando **para cima** (com `round()` dava 75% onde o `df` mostra 76%, e um ponto de diferença basta para alguém achar que um dos dois está mentindo) e usando `f_bfree` para o "usado", que inclui a reserva do root — `f_bavail` faria o painel parecer mais folgado que a realidade. Avisa quando os e-mails dividem o disco com o sistema. Admin de domínio não vê: disco é do servidor inteiro. - **Remover domínio, depois de desativado.** O botão só aparece com o domínio inativo; no ativo fica apagado com o motivo. Exigir a desativação antes não é burocracia: desativado, o domínio para de receber e de autenticar, e quem ainda dependia dele reclama **enquanto tudo ainda existe** — depois não há volta. Pede o domínio digitado e é só super-admin. Remove contas, grupos, aliases, listas, tokens, webhooks, eventos, o vínculo com os administradores, as caixas do disco e a chave DKIM, e regenera rspamd/SNI/SOGo. O disco só é tocado **depois** do commit: se a transação falhar nada foi apagado; se o disco falhar sobra diretório órfão, que é o erro barato dos dois. A trilha de auditoria **sobrevive** — apagá-la junto seria apagar a prova do que foi feito no domínio. - **Sincronização do Zimbra traz os grupos.** As listas de distribuição viram Grupos, com os membros. As mesmas recusas dos aliases (outro domínio, endereço que já é conta, repetido), mas **membro externo é mantido**: lista com participante de fora é uso normal. Repetir não duplica e acrescenta quem entrou na lista no meio-tempo. - **A conexão com o Zimbra fica guardada** (uma por domínio): servidor, portas, usuário e senha do admin, gravada **só depois de funcionar** — guardar credencial que não autentica só confundiria na próxima vez. O formulário vem preenchido e a senha fica opcional. Sincronizar um domínio não é evento único: contas, grupos e aliases mudam no servidor antigo enquanto a migração corre. ### Segurança - **A senha do admin do Zimbra saiu do HTML da página.** Ela era ecoada num `<input type="hidden">` na tela de importação — visível em "ver código-fonte", no cache do navegador e em qualquer proxy que guardasse a página. Agora não sai do servidor. - **Guardada cifrada** (`app/crypto_box.py`, Fernet com chave derivada do `SECRET_KEY`). Hash não serviria: a senha precisa voltar em texto claro para autenticar no Zimbra de novo. O ganho é que um dump do banco **sozinho** não entrega nada — backup que vaza, réplica mal protegida, acesso de leitura ao Postgres. Quem lê o `SECRET_KEY` lê os dois, e isso está escrito no módulo. Com o `SECRET_KEY` trocado a senha simplesmente não abre e o painel pede de novo, em vez de estourar erro. ### Alterado - A limpeza de conta virou `app/admin/purge.py`, compartilhada com a remoção de domínio. Nada aqui tem cascade no banco; duplicar a lista de tabelas em dois lugares garantiria que um dia divergissem, e o sintoma seria erro de chave estrangeira no meio de uma exclusão já começada.
-
1.35.0 [] 26/08/2026### Corrigido - **A política MTA-STS discordava do MX que o painel manda publicar.** A política era montada com `d.mail_hostname or MAIL_SERVER_NAME`, ou seja, preferia o **hostname SNI**; o painel manda publicar o MX com o nome do **servidor**. Num domínio com SNI configurado a política dizia `mx: mail.<dominio-do-cliente>` e o DNS dizia `MX mail2.<servidor>`. Com os dois discordando e a política em `enforce`, quem respeita MTA-STS (Gmail, Microsoft — a maior parte do que chega) **recusa a entrega**; do lado de cá não aparece erro nenhum, a mensagem simplesmente não chega. Agora a política anuncia o nome do servidor, e lista o hostname SNI como **segunda linha `mx:`** — é o mesmo servidor, com certificado válido, e algum domínio pode ter publicado o MX com ele antes desta correção; listar os dois não afrouxa nada e evita recusar entrega legítima na transição. ### Alterado - **O registro do host SNI passou a dizer para que serve — e para que não.** É o nome que o cliente configura no e-mail e no webmail, com o certificado do domínio dele. Mas **alvo de MX não pode ser CNAME** (RFC 2181 §10.3) e vários remetentes recusam, então o MX continua sendo o do servidor. O aviso agora aparece embaixo do registro, na própria tela de DNS. ### Adicionado - **`flask aplicar-config`** põe em dia um servidor **já instalado**. O `install.sh` roda uma vez; quando um arquivo de `deploy/` muda, quem atualiza por `git pull` fica com a versão velha e **nada avisa** — foi assim que a porta 2000 do ManageSieve continuou aberta e o `disable_vrfy_command` não chegou a quem já estava instalado. O comando compara os arquivos de `deploy/` com os instalados (Dovecot, nginx, fail2ban) e os `postconf` esperados. Sem `--aplicar` só relata: ver o que está para trás não pode exigir coragem. Valida antes de recarregar (`nginx -t`, `doveconf -n`) e **reverte** o que for recusado — copiar config inválida e recarregar derruba o serviço. Tudo é reload, sem parada. Fica **de fora** `fail2ban/jail.d/minimail.conf`, que o painel regenera do banco: copiar por cima apagaria os ajustes da interface, inclusive a lista de IPs que não devem ser banidos.
-
1.34.0 [] 26/08/2026### Corrigido - **Trocar a senha de um administrador não derrubava as sessões abertas.** O hash mudava e a senha nova funcionava — o que não acontecia era o resto: a rota fazia `session_token = ""` com o comentário "invalida sessões existentes", e faz o oposto. A verificação é `if st and stoken != st`; com o token **vazio** a condição inteira é pulada e **todas as sessões antigas continuam valendo**. Daí a impressão de que a troca "não funcionava": nada saía do lugar em navegador nenhum. E o efeito de segurança era o pior possível — trocar a senha porque ela vazou deixava o invasor dentro. Agora gera um token novo; quando é a própria conta, o `stoken` da sessão atual é atualizado junto, para quem trocou não se expulsar. - **Um super-admin conseguia deixar o servidor sem nenhum.** Bastava editar a própria linha com a caixa "super-admin" desmarcada — não havia trava. Sem super-admin ninguém cria domínio nem mexe no backend, e a volta exige acesso ao banco. A trava vale para rebaixar, desativar e remover, e conta **as duas portas**: administradores dedicados e as caixas de e-mail que ainda têm super-admin do modelo antigo (se existe uma legada, ela cobre a saída e a limpeza dos administradores não fica travada). ### Adicionado - **`flask admin-senha <usuario>` e `flask admin-2fa-off <usuario>`** — recuperação por linha de comando para quando ninguém entra no painel: senha esquecida, conta desativada por engano ou 2FA sem o aplicativo. Sorteia uma senha forte (ou aceita `--senha`), reativa a conta, encerra as sessões abertas dela, cria com `--criar` se não existir e avisa quando o 2FA continua ligado — senão a senha seria trocada e o acesso continuaria travado no código. ### Alterado - **Listagem de administradores virou tabela** (Usuário · Nome · Papel · Domínios · 2FA · Último acesso). Antes eram formulários empilhados, um bloco por administrador com todos os campos sempre abertos. Passou a mostrar de relance o que não dava para ver: quem tem **2FA**, o **último acesso** (conta de administrador esquecida e sem uso é risco) e um aviso em quem está **sem domínio e sem poder de suporte** — entra no painel e não gerencia nada. Editar abre modal; Remover pede o usuário digitado e fica **apagado** na própria conta e no último super-admin, com o motivo no tooltip.
-
1.33.0 [] 26/08/2026### Segurança - **Auditoria contra webshell e abuso de SMTP** (motivada por invasão em servidor Zimbra de origem). O vetor do Zimbra — subir um arquivo e fazer o servidor executá-lo — não se reproduz aqui: não há PHP nem JSP no servidor, arquivo enviado nunca vira nome de caminho (o Drive grava como `uuid4().hex`), nenhum `shell=True` recebe dado de usuário, e o HTML de e-mail passa por `bleach` com lista de permitidos. **Testado, não só lido:** relay recusado de origem fora de `mynetworks`, AUTH desativado na porta 25, submission/465 exigindo TLS+SASL, `reject_sender_login_mismatch` ativo. Quatro pontos foram fechados: - **VRFY expunha quais contas existem** — respondia `252` para conta real e `550` para inexistente. É a lista de alvos que antecede a força bruta. Desativado (`disable_vrfy_command`), também no `install.sh`. - **Porta 2000 aberta sem uso** — número legado do ManageSieve (o padrão, 4190, já estava aberto). Era mais um serviço autenticado exposto para varrer e tentar senha. Fechada no template do Dovecot. - **Drive: "pode abrir inline" era lista de PROIBIDOS** — barrava `image/svg+xml` e `text/html` exatos e deixava passar variações (`application/xhtml+xml`, `text/xml`). Virou lista de **permitidos**, idêntica à dos anexos do webmail. O `content_type` vem de quem envia o arquivo; renderizar um desses no próprio domínio é XSS com o cookie junto. - **Restauração de caixa aceitava symlink no `.tar.gz`** — o arquivo é enviado pela interface. O GNU tar já barra `..` e não escreve *através* de um symlink (verificado), mas **cria** o link; link e dispositivo não têm uso legítimo num Maildir. Agora só arquivo e diretório passam. - **Auditoria de escalada de privilégio**, com testes que fazem requisições reais. Nenhuma das três escaladas passa: - **usuário de e-mail → painel**: a defesa é estrutural, não uma verificação esquecível — o painel lê o cookie `mmadmin` e o webmail grava `mmsession`; a sessão de e-mail nem é lida do outro lado; - **admin do domínio A → domínio B**: das **85 rotas do painel com id**, nenhuma ficou sem escopo (as quatro sem `require_domain` usam helper próprio). Inclui o seletor de domínio ativo, que confere a autorização na rota **e de novo na leitura** — forjar o id direto na sessão não passa; - **admin de domínio → super-admin**: 14 telas bloqueadas, e promover-se pelo formulário não funciona (o campo não existe desde a 1.30.0). - O token de impersonação (único caminho legítimo de virar outro usuário) resiste a chave errada, salt de outro propósito e expiração. ### Adicionado - **Trocar o nome (hostname) do servidor pelo painel** (Configurações → Servidor). O nome vive em seis lugares e um fora de sincronia quebra em silêncio — HELO divergente do certificado piora a entrega sem nada no log. A tela mostra onde cada peça acha que o servidor se chama, apontando divergência. **O nome antigo continua atendendo**: o certificado é emitido para os dois e o nginx responde pelos dois; sem isso, todo cliente já configurado (Thunderbird, celular) pararia de uma vez. Recusa a troca se o nome novo não resolver para este servidor, emite o certificado **antes** de qualquer alteração, valida cada arquivo (`nginx -t`, `doveconf -n`) e reverte o que o validador recusar, guardando cópia `.antes-da-troca`.
-
1.32.0 [] 26/08/2026### Corrigido - **A importação do Zimbra não criava os aliases que a própria tela mostrava.** Eram duas falhas somadas: a prévia só **exibia** os aliases (não havia campo escondido levando-os no POST, como já havia para nome e cota), e a rotina de importação nunca os criava. Quem importava confiava na tela e descobria a falta quando alguém reclamava de e-mail que não chegou. Três recusas na criação, cada uma por um motivo concreto: - **alias de outro domínio** — o Zimbra permite alias em qualquer domínio dele; gravar aqui um de domínio que não hospedamos cria entrada que nunca é consultada; - **endereço que já é uma CONTA** — o Postfix consulta `virtual_alias_maps` **antes** de `virtual_mailbox_maps`, então o alias passaria na frente e a caixa real pararia de receber. Numa importação seria o pior desfecho, e silencioso; - **alias repetido** — não duplica. A tela de resultado ganhou a coluna **Aliases**, com os criados e, em amarelo, os não criados **com o motivo**. - **Evento recorrente sumia da agenda depois da primeira ocorrência.** O SOGo filtra a grade com `(c_enddate > X OR c_enddate = NULL) OR (c_iscycle = 1 AND (c_cycleenddate > X OR c_cycleenddate = NULL))` — e `= NULL` **nunca** é verdadeiro em Postgres. A importação marcava `c_iscycle=1` e deixava `c_cycleenddate` nulo, então sobrava o primeiro ramo, cujo `c_enddate` é o fim da **primeira** ocorrência: o evento aparecia na semana em que começou e sumia de todas as seguintes, apesar de estar no banco. Agora o campo é calculado do RRULE (`UNTIL` direto, `COUNT` expandido, teto de 2038 quando a série é infinita ou o RRULE é ilegível — a coluna é `integer`). Na dúvida erra **para mais** de propósito: o valor é só um limite superior do filtro, e quem desenha as ocorrências é o VEVENT em `c_content`. - **Nome de contato cortado no índice do SOGo.** O vCard quebra linha longa com continuação começando por espaço, e a leitura pegava uma linha só: um `FN` de "…Oliveira Correia de Albuquerque" virava "…Oliveira Cor" — justamente o texto que o SOGo mostra na lista de contatos. - **Acento de contato virando código.** O Zimbra usa vCard 2.1 com `ENCODING=QUOTED-PRINTABLE`; `João Conceição` era gravado como `Jo=C3=A3o Concei=C3=A7=C3=A3o`. Agora decodifica, e junta também a quebra leve do quoted-printable (linha terminada em `=`), que é um segundo mecanismo de dobra além do padrão. ### Adicionado - **Sincronizar aliases das contas já importadas** (`sincronizar_aliases`), na tela de importação do Zimbra. Reimportar não resolveria: a prévia desabilita quem já existe, para não sobrescrever conta em uso. Relê o Zimbra e cria só o que falta — não cria conta, não remove nada, e é repetível sem duplicar. É um segundo botão no **mesmo formulário** de "Descobrir contas", via `formaction`, para as credenciais já preenchidas irem junto sem depender de JavaScript. - **Reimportar agenda/contatos agora conserta o que ficou torto.** Sozinho não consertaria: `_insert_rows` pula quem já existe pelo `c_name`, então o que foi importado por uma versão com o índice errado ficaria assim para sempre. O reparo recalcula o índice a partir do `c_content` — a fonte de verdade, que nunca esteve errada — e toca **só na pasta "Zimbra (importado)"**.
-
1.31.0 [] 26/08/2026### Adicionado - **O diretório dos e-mails virou um ajuste, mudável pelo painel** (Configurações → Armazenamento). O caminho estava fixo em `/var/vmail` em **onze pontos do código** e dentro da consulta `userdb` do Dovecot; agora sai de um lugar só (`app/admin/vmail_store.py`). Dois desses pontos eram constantes de módulo (`backup.VMAIL_DIR`, `sieve_gen.VMAIL`), lidas na importação — antes de existir contexto da aplicação —, e viraram função. - **A mudança move os dados, não só o ajuste.** Trocar apenas o caminho faria o Dovecot criar caixas **vazias** no destino, sem um erro sequer no log, e todo mundo perderia o histórico. A sequência: 1. `rsync` com **tudo no ar** — leva o grosso, sem parada; 2. para Postfix e Dovecot (só aqui começa a indisponibilidade); 3. `rsync --delete` do delta — segundos; 4. **confere a contagem de arquivos** nos dois lados; 5. só então troca o literal do home na consulta userdb e grava o ajuste; 6. religa e verifica que as duas pontas concordam. Qualquer falha religa o que foi parado. Falha na primeira cópia nem chega a parar nada. - **O diretório antigo nunca é apagado pela mudança.** Fica intacto; a remoção é um botão separado, que só aparece após uma mudança concluída e pede o caminho digitado. - **Validação antes de tocar em qualquer arquivo:** caminho absoluto, existe, vazio, não está dentro nem contém o atual, espaço livre ≥ o que existe hoje + 10%, permissão de escrita. - **Aviso de divergência**: se o ajuste do painel e a consulta do Dovecot apontarem para lugares diferentes, as contas abrem vazias e nada explica o motivo — a tela passa a dizer, com os dois caminhos lado a lado. - Log ao vivo da mudança (a cópia pode levar horas; sem isso a única forma de saber se anda seria recarregar a página na mão). ### Docs - `deploy/DEPLOY.md` e Ajuda com a tela nova e o que ela faz.
-
1.30.0 [] 26/08/2026### Alterado - **Lista de contas: seis botões por linha viraram três.** A coluna de ações ficava mais larga que o resto da tabela. Agora a linha tem **Editar**, **Abrir conta** e **Excluir**; senhas de app, dispositivos, backup e limpeza foram para dentro de um modal em abas (Conta · Aliases · Senhas de app · Dispositivos · Backup · Limpeza). As telas continuam existindo nas URLs de sempre — o modal só as carrega, **uma por vez**, porque o `bindForm` trata um `form.modal-form` de cada vez. Sem JavaScript o botão Editar ainda leva à tela de edição. - **Coluna Aliases na lista de contas.** O número é o próprio botão — um contador que não leva a lugar nenhum obrigaria a descer até a tabela de aliases do domínio e procurar à mão. Abre o modal com os aliases que entregam naquela caixa, com criar e remover; o destino do alias novo já vem preso na conta. A contagem sai de **uma consulta agrupada** (em domínio com centenas de contas seria N+1) e ignora maiúsculas, porque o destino é digitado à mão. A tabela de aliases do domínio continua embaixo: alias pode apontar para endereço externo ou grupo, o que não cabe em nenhuma linha da lista. - **Criar/remover alias pelo modal não fecha mais o modal.** Os formulários da aba postam por fetch e a resposta devolve a própria aba atualizada — antes o POST navegava a página inteira, o modal sumia e o detalhe do domínio reabria na aba "Visão geral", que é a inicial dele. A contagem no botão da linha atrás acompanha na hora; sem isso, fechar o modal e ver o número velho pareceria que a criação não pegou. Sem JavaScript o POST navega de verdade, e aí volta para a aba de contas (`#accounts`), não mais a Visão geral. - **Excluir aparece apagado nas caixas de sistema**, em vez de sumir. Escondido dava a impressão de que a linha tinha menos ações e deixava sem resposta a pergunta "por que esta não dá para excluir?". O botão é solto, **sem formulário**: não há caminho para a rota nem forçando por DevTools. A dica vai num `span` em volta porque `.btn-disabled` usa `pointer-events:none` — no próprio botão o navegador não mostraria o texto. ### Removido - **Não é mais possível marcar uma caixa de e-mail como super-admin.** Quem administra o servidor usa conta dedicada (Administradores). Ficou só o caminho de VOLTA — "Remover o super-admin desta conta" — e ele aparece apenas nas contas que já estão marcadas do modelo antigo; sem ele, essas contas ficariam com acesso global para sempre e só sairiam por SQL. **Nenhum acesso foi retirado de ninguém**: as contas marcadas continuam funcionando e o login por e-mail segue de pé. - A trava do último super-admin passou a contar **as duas portas** (caixas marcadas + administradores dedicados, ambos ativos). Olhar só para as caixas bloquearia justamente a limpeza num servidor já migrado; olhar só para as contas dedicadas permitiria zerar os dois lados e deixar o servidor sem ninguém que crie domínio ou mexa no backend — com a volta exigindo acesso ao banco. ### Corrigido - **Três telas do admin de domínio davam "Acesso restrito ao suporte".** A permissão do painel é uma allowlist de endpoints; quando um endpoint é escrito já pensando no admin de domínio — escopado por dentro — mas ninguém o acrescenta à lista, o botão aparece na tela dele e não abre. Ninguém percebe porque quem opera no dia a dia é super-admin, que passa por tudo. Eram: - `migrate_retry` — iniciava a migração e não conseguia retomá-la; - `reputation` — a rota **já** filtrava pelo domínio dele e escondia os IPs de saída; - `help_center` — link no rodapé de **todas** as telas. Teste novo renderiza as telas do console de domínio e falha se qualquer link apontar para endpoint bloqueado — é a única forma de pegar essa classe de defeito. - A mensagem de erro ao criar alias era um "Informe o apelido." fixo, que aparecia também com token CSRF expirado — mandava conferir o campo certo pelo motivo errado. Agora mostra o erro real do formulário. ### Docs - Ajuda: Administradores é o único lugar onde se cria super-admin; a exclusão de conta é definitiva e as caixas `sistema` são protegidas.
-
1.29.2 [] 25/08/2026### Corrigido - **A coluna "Contas" da listagem de domínios contava as caixas ocultas.** Ela usava `d.users|length`, que soma tudo — as caixas de sistema (postmaster/dmarc/tlsrpt) e também a **caixa de auditoria**, que já inflava a coluna desde antes. Agora usa `account_count`, o mesmo número que o limite do plano cobra: mostrar 4 onde o perfil conta 1 fazia parecer que o domínio tinha estourado o limite. No servidor de teste os quatro domínios caíram de 4/6/5/8 para 1/3/1/4. - **O card "Contas" dentro do domínio tinha o mesmo defeito**, introduzido na 1.29.0 quando as caixas de sistema passaram a aparecer na lista. Dois números diferentes para a mesma coisa, com o card contradizendo o limite do perfil logo abaixo. A lista continua mostrando as caixas marcadas como **sistema** — só a contagem não as inclui.
-
1.29.1 [] 25/08/2026### Corrigido - **Migração IMAP não rodava desde a 1.21.0.** O comando levava um `--retry 2` que **não existe no imapsync**: ele joga a opção no balde de "argumentos suplementares" e sai com **64 (EX_USAGE) antes de sincronizar qualquer coisa**. A mensagem fala em problema de aspas, o que despista — parecia erro de quem chamou. Valia para toda migração (Zimbra, cPanel, IMAP avulso), não só para o botão *tentar novamente*, que foi só onde apareceu. Removida sem substituto: reconectar quando a conexão cai já é padrão do imapsync (`--reconnectretry1/2` = 3 por comando IMAP), e o valor que estava escrito seria menor que o padrão. Novo teste confere **toda** opção que passamos contra a tabela do imapsync instalado. - **Vhost SNI agora é igual ao do host principal.** Quem acessa por `mail.<domínio>` usa o mesmo Drive e leva as mesmas varreduras — mas o vhost gerado pelo painel tinha duas faltas, invisíveis até alguém esbarrar: - **Upload do Drive parava em 413.** O `client_max_body_size 25m` do vhost valia para o Drive também; no host principal o limite é **512m**, com buffering desligado e 300 s de timeout. Agora tem o `location /drive` próprio. - **Varredura de bot chegava no gunicorn.** Sem o `snippets/scanners.conf`, `/wp-login.php`, `/.env` e afins não levavam 444 e não entravam no access.log da forma que a jail `minimail-scan` lê — o IP **não era banido**. Incluído nos dois listeners: no `:80` o redirect gravava 301, que o filtro do fail2ban não casa, e o bot passava impune (visto no log real). O vhost também passou a incluir as opções de TLS do certbot (cifras) e o `proxy_read_timeout 60s`, como o principal. Servidores já instalados regeram ao salvar qualquer hostname SNI no painel.
-
1.29.0 [] 25/08/2026### Adicionado - **Os e-mails do sistema saem do domínio do próprio cliente.** Redefinição de senha e avisos de segurança vinham de `noreply@<domínio do servidor>` — um domínio estranho para quem recebe, sem relação com a marca dele. Agora saem de `postmaster@<domínio do usuário>`, assinados com a chave DKIM **dele**. Duas condições antes de trocar, porque errar aqui deixa a pessoa sem conseguir recuperar a conta: - **DKIM cadastrado no domínio.** Sem chave, a mensagem sairia sem assinatura e de um domínio que provavelmente também não tem SPF apontando para cá — pior que sair do domínio do servidor, que está configurado. Nesse caso mantém o remetente antigo. - **`postmaster@` entregável.** Enviar de um endereço que RECUSA (testado: 550 5.1.1) quebra o retorno de erro — o bounce vira duplo-bounce e some — e quebra quem simplesmente responder ao aviso. - **Três caixas criadas junto com o domínio: `postmaster`, `dmarc` e `tlsrpt`.** O painel manda publicar registros de DNS apontando `dmarc@` e `tlsrpt@` para cá, mas as caixas nunca existiam — os relatórios eram **recusados na entrada**, e ninguém percebia porque a recusa acontece do outro lado. As três aparecem na lista com a etiqueta **sistema**, **não consomem vaga do plano** e não são cobradas por "sem e-mail de recuperação" (ninguém faz login nelas). Domínios que já existiam ganham as caixas no próximo `flask init-db`. - **Botão de excluir conta**, em Domínios → Contas. A rota existia desde sempre mas não estava ligada em lugar nenhum da interface. ### Segurança - **Exclusão de conta pede o endereço digitado.** A rota apaga a Maildir do disco: não há lixeira nem desfazer, e é a única ação de destruição real do painel. Um OK/Cancelar era fraco demais. Agora o admin digita o endereço completo, e o servidor confere de novo o que veio no POST — se o JavaScript não rodar, ou se alguém repetir o POST fora da tela, o pedido é **recusado** em vez de executado. O botão nem aparece nos casos que a rota recusa (caixas de sistema, a própria conta, superadmin visto por admin comum). - **As caixas de sistema não podem ser excluídas nem renomeadas.** Renomear tinha exatamente o mesmo efeito de apagar: o endereço para onde o DNS aponta deixaria de existir, em silêncio. A trava vale para a caixa **oculta que nós criamos** — uma conta real chamada `postmaster@` que o cliente tenha criado continua sendo dele. ### Alterado - Depois de excluir uma conta, o painel volta para o domínio onde você estava, não para a lista de domínios. ### Docs - Ajuda: a exclusão é definitiva (faça backup antes) e as caixas `sistema` são protegidas.
-
1.28.0 [] 24/08/2026### Alterado - **Listagem do webmail muito mais leve.** Duas coisas iam no HTML de toda página e ficavam escondidas: - **As mensagens anteriores de cada conversa.** Numa caixa real eram **1.416 linhas e 2,7 MB** numa página de 100 conversas — o navegador montava e posicionava tudo isso para mostrar 100. Agora só a principal vai na página; as filhas são buscadas ao expandir (`/mail/thread/<id>`). **2.706 → 373 KB, 1.416 → 100 linhas.** A conversa abre com "Carregando…" e, se falhar, avisa e permite tentar de novo — abrir e não acontecer nada seria pior. - **Um formulário por ação de linha** (estrela, arquivar, excluir, lida, soneca), cada um com o seu token CSRF de 64 caracteres. Viraram botões que preenchem o formulário único da página; o caminho no servidor continua sendo POST com redirect, sem `fetch` nem recarga manual. **437 → 381 KB, de 512 formulários para 13.** - **Pico de conexões medido pelo próprio Dovecot.** A coleta observa ~1 min a cada 5, então o pico amostrado é um PISO. Agora o coletor conta as recusas por falta de vaga registradas pelo Dovecot, e isso muda o patamar da recomendação: marca o item como crítico, sobe o alvo a partir do **limite atual** (recomendar a partir do pico amostrado podia sugerir menos do que já existe) e passa a valer mesmo com poucas amostras — quem informou foi o servidor. A expressão cobre as três formas como o Dovecot escreve isso, incluindo a mensagem real encontrada em log, que não é a que a documentação sugere. - **Pool de conexões IMAP configurável** por `IMAP_POOL_MAX_IDLE` e `IMAP_POOL_MAX_AGE` no `.env`. O total de conexões seguradas é "contas ativas × workers do gunicorn"; numa migração com milhares de contas é preciso poder apertar sem editar código. `0` desliga o pool. ### Corrigido - **Botões de janela do compositor apareciam no celular** ao lado da barra nova. `.cmp-desk { display: none }` tem especificidade (0,1,0) e perdia para `.compose-head .x { display: inline-flex }`, que tem (0,2,0) — ordem no arquivo não resolve isso. Corrigido com o seletor completo.
-
1.27.2 [] 24/08/2026### Alterado - **"Esqueci minha senha" devolve a pessoa na porta de onde ela veio.** Havia três logins (SOGo, webmail20, painel) e a recuperação terminava sempre no webmail20 — quem começou no SOGo era largado noutra interface. Cada link carrega a origem (`?de=sogo|wm20|painel`), que precisa sobreviver a três saltos: o **formulário** (campo escondido — sem ele a origem morre no POST), o **link do e-mail** (`/reset/TOK?de=sogo` — quando a pessoa volta pelo e-mail, é a única pista de onde começou, possivelmente noutro navegador e horas depois) e o **formulário de redefinição**. A origem vence o perfil: administrador que entrou pelo SOGo volta ao SOGo, e não ao painel. Sem marcador — link antigo, e-mail encaminhado — cai no perfil da conta, como antes. Marcador desconhecido não vira destino; só os três valores conhecidos são aceitos. O `adm=1` anterior continua valendo, porque pode haver link já enviado com ele.
-
1.27.1 [] 24/08/2026### Corrigido - **"Esqueci minha senha" não funcionava.** A rota `/forgot` começava expulsando quem tivesse sessão (`if current_user.is_authenticated: redirect(...)`), e quem clica no link da tela de login do SOGo costuma ter um `mmsession` antigo no navegador — o clique parecia não fazer nada. O formulário agora aparece nos dois casos; ele só dispara e-mail para o endereço de recuperação da conta digitada, com resposta neutra. - **Redefinir a senha terminava no login do painel.** Um usuário comum de e-mail era mandado para `/login`, tela que não aceita a conta dele. Mesma coisa em "Conta indisponível" e no pós-pedido. Agora o destino é escolhido por quem está sendo mandado: no fim da redefinição já se sabe a conta, então decide-se pelo perfil; antes disso, pelo marcador `adm` que o login do painel carrega. - **Espaçamento desigual entre os cards do painel.** Havia cinco valores concorrendo (16px entre painéis, 14px nos cards de número, 14px embutido em três blocos, 16/18px nas linhas de cards) e, entre uma `.row` de cards e o painel seguinte, **nenhuma regra**. Um token `--gap-cards` passou a valer para tudo. A margem ficou em TODOS os blocos, e não em pares `A + B`: o seletor `+` casa com o irmão imediatamente seguinte, e havia um `<script>` no meio do fluxo entre os cards e o painel de "Spam × Legítimos" — aquele par ficava sem regra e o espaço encolhia. Assim o espaçamento não depende do que houver de invisível no meio. - **Link do login do SOGo**: o clique é interrompido na captura e a navegação é feita por conta própria, como já era no botão do Drive (o formulário do SOGo engole eventos). ### Alterado - **Aviso antes de ativar o 2FA**, na tela e na Ajuda. A documentação dizia apenas que "os programas de e-mail passam a exigir senha de aplicativo" — está incompleto: com 2FA ativo o **SOGo deixa de abrir a caixa**, porque ele lê o e-mail entrando com a senha principal do usuário, que passa a ser recusada. Senha de aplicativo não resolve, porque o login do próprio SOGo também confere a senha principal. Agora a tela diz, em destaque: se você usa o SOGo, não ative.
-
1.27.0 [] 24/08/2026### Corrigido - **Do lado do e-mail nunca mais se cai no login do painel.** O `login_view` do Flask-Login é global e apontava para `/login` (painel): qualquer tela do webmail, agenda, Drive, hub ou Ajuda sem sessão jogava o usuário lá. Agora o destino é escolhido pelo caminho — `/painel` e `/superadmin` vão ao login do painel; todo o resto vai ao `/webmail20`. - **Sair do webmail não encerrava a sessão do webmail.** O `SplitCookieSession` manda `/logout` para o cookie `mmadmin`; sem `wm20` na sessão (caso de quem vinha pelo SSO do SOGo), o link "Sair" encerrava a sessão do PAINEL e deixava a de e-mail de pé — voltar ao webmail não pedia senha. O cliente passa a sair sempre por `webmail.logout`, que age no cookie certo. - **A tela de login não aparecia** para quem tinha sessão de e-mail sem `wm20`: o `base.html` só renderizava o bloco `bare` para não autenticados, então aparecia a moldura da aplicação com o miolo vazio e nenhum formulário — sem saída. As telas de login/recuperação agora marcam `bare_layout`. - **`next` quebrado no login do webmail**: `request.full_path` acrescentava "?" (o destino virava `/mail/?`) e o `/webmail20` sequer lia o parâmetro. Agora funciona e sobrevive ao POST. Como passou a aceitar destino vindo da URL, só caminho deste site é aceito — `https://outro.site` e `//outro.site` são recusados (redirect aberto). - **Hub do SOGo mostrava a conta errada.** `/ajuda` não estava na lista de rotas do SSO, e uma sessão antiga do miniMAIL vencia a do SOGo. Como o mesmo SSO serve Senhas de aplicativo, Meus dispositivos, Minha quarentena e E-mail de recuperação, era **exposição de dados entre contas**. Dentro do hub a sessão do SOGo passa a ser a autoridade; a validação por loopback é feita uma vez por cookie. - **Área branca no SOGo depois de abrir o Drive.** O botão fica dentro da barra do SOGo e só chamávamos `preventDefault()`: o clique subia para os handlers dele e o roteador do Angular esvaziava a view do módulo. Agora o evento é interrompido na captura. Ao fechar, o iframe é solto (seguia carregado atrás de uma camada invisível) e um `resize` devolve o layout. - **"Correio" ficava marcado como selecionado** com um painel do hub aberto: o destaque dos itens nativos é do router do Angular e não mudava. Suprimido por CSS enquanto o hub está ativo (mexer na classe seria desfeito no próximo digest). - **Assuntos da quarentena crus em MIME** (`=?utf-8?q?...`), justamente na tela em que a pessoa decide se libera ou descarta. Assunto dobrado em várias linhas também era cortado. - **"Meus dispositivos" chegava sem estilo** no iframe do hub: com `embed=1` a rota devolvia fragmento sem `<html>` e sem CSS. `embed=1` é iframe e passa a devolver documento completo; o fragmento fica só para injeção via `fetch`. - **Compositor**: o painel some da tela ao enviar (só a barra de desfazer fica) e desfazer o devolve intacto; erro de envio reabre o painel em vez de sumir em silêncio; o aviso "Mensagem enviada" só aparece quando o envio foi confirmado. - **Avisos de sucesso somem sozinhos.** O "Mensagem enviada." enviado pelo painel era um `flash` que ficava guardado na sessão e só aparecia na página seguinte — podia ser minutos depois, num contexto sem relação. Erro e alerta continuam fixos: quem precisa ler um erro não pode perdê-lo de vista. - **Login do SOGo**: o rótulo flutuante sobrepunha o valor preenchido pelo navegador (o Material só sobe o rótulo quando o modelo do Angular muda, e o autofill não dispara evento). ### Adicionado - **IP real de quem envia pelo webmail, no Histórico.** O webmail entrega pela submission local sem autenticar, então o rspamd registrava `127.0.0.1`. A origem é gravada no envio e casada por Message-ID. Feito **sem** tocar no caminho SMTP de propósito: sobrescrever o IP do cliente (XCLIENT) faria o rspamd aplicar DNSBL/reputação/limite ao IP residencial do usuário, e boa parte das faixas domésticas está em lista — passaria a marcar envio legítimo. - **"Esqueci minha senha" e "new webmail" na tela de login do SOGo.** O SOGo não tem recuperação de senha própria. O atalho leva ao LOGIN do webmail novo, e não entra direto: `/mail` exige `wm20`, concedida só pelo login do webmail20 — que é onde o 2FA é exigido. A sessão do SOGo não passa por 2FA. ### Docs - Central de ajuda: 7 áreas do painel que não estavam documentadas (Planos, Administradores, IPs confiáveis, Reputação, IPs de saída, Relays de saída, Rastrear msg), mais Capacidade medida, os crons novos e "onde olhar quando algo falha".
-
1.26.0 [] 24/08/2026### Alterado - **Compositor no celular no estilo do Gmail.** - Barra superior clara: `←` voltar à esquerda; anexar, enviar e "mais" à direita. Some o título "Nova mensagem" e os botões de minimizar/maximizar — não há janela para minimizar quando o compositor ocupa a tela inteira. - Os botões novos **delegam** para os controles que já existem dentro do formulário (`.cmp-send`, `.cmp-files`), em vez de duplicar a lógica: envio, validação e anexo seguem um caminho só, e o agendamento continua valendo. - Corpo sem moldura, com o texto começando direto e placeholder "Escrever e-mail". - A barra inferior (Drive, modelos, formatação, rascunho, descartar, agendar) recolhe atrás do "⋮". Nada foi removido. - Desktop inalterado. ### Adicionado - **`scripts/check_css_mobile.py`** e testes. Media query não soma especificidade: uma regra igual escrita DEPOIS do bloco `@media` vence no cascata e desfaz o ajuste do celular, em silêncio. Esse padrão já causou três defeitos (faixa do compositor presa no rodapé, botão Escrever sumido e as cinco regras que anulariam a barra nova). Agora é verificação automática. Há **dois** testes: um exige zero colisões no `style.css`; o outro verifica se a checagem realmente detecta um caso plantado — sem esse, o primeiro passaria com a ferramenta quebrada, que foi exatamente o que aconteceu duas vezes durante o desenvolvimento dela (comentários grudando no seletor e comparação de ordem por linha em vez de posição).
-
1.25.2 [] 24/08/2026### Corrigido - **O botão "Escrever" não aparecia no celular.** O FAB já existia no CSS, mas o botão mora dentro da `.sidebar`, que no celular é `position: fixed` com `transform: translateX(-100%)`. Um `position: fixed` dentro de um ancestral com `transform` passa a se posicionar em relação a ESSE ancestral, não à tela — o FAB era arrastado para fora junto com o menu fechado. Agora existe um FAB próprio, fora da barra lateral (é um `<a href>`, então funciona sem JS). ### Alterado - **Chat sai do rodapé no celular.** O balão flutuante cobria a lista de mensagens, e com o FAB de escrever de volta seriam dois botões flutuando numa tela pequena. O chat passou a ser um item do menu lateral, com o mesmo contador de não lidas; ao abrir por ali, a gaveta fecha antes (senão o painel abriria atrás do escurecido). No desktop nada muda. ### Notas - Duas das correções desta rodada e a da 1.25.1 têm a mesma origem: regras de desktop escritas DEPOIS dos blocos `@media (max-width: 820px)` no `style.css`. Media query não soma especificidade, então elas vencem no cascata e desfazem o ajuste do celular. As novas foram envolvidas em `@media (min-width: 821px)`.
-
1.25.1 [] 24/08/2026### Corrigido - **Faixa do compositor presa no rodapé, no celular.** Uma tarja "Nova mensagem" ocupava ~18% da tela em TODAS as páginas do webmail, mesmo sem ninguém abrir o compositor (o painel vive no `base.html`). A regra do celular acrescentava `top: 0` ao painel, mas ele se esconde com `transform: translateY(105%)` — que só tira da tela quem está ancorado embaixo. Com `top`, `bottom` e `height` definidos ao mesmo tempo, o CSS descarta o `bottom`: o painel passou a nascer no topo e o deslocamento (105% de 78vh = 81,9vh) deixava 18,1vh visíveis. Agora o painel volta a ser ancorado embaixo e é **tela cheia** no celular — compositor encaixotado em 620px é coisa de desktop. Em `dvh` (com `vh` de reserva), porque a barra do navegador do celular entra e sai e `100vh` estoura. O botão "maximizar" some no celular: não faz sentido quando já é tela inteira. - **`.compose-panel { width: 640px }` estava fora de qualquer media query**, e depois do bloco do celular. Media query não soma especificidade, então essa regra vencia no cascata e devolvia 640px de largura ao painel no celular (mascarado pelo `max-width: 100%`). Movida para `@media (min-width: 821px)`.
-
1.25.0 [] 24/08/2026### Corrigido - **O botão "reparar o aplicativo" da tela offline não fazia nada.** Ele usava `data-act="repair"`, mas quem traduz isso em ação é o `webmail.js` — que a tela offline não carrega. Ficava morto exatamente na tela em que é a única saída do usuário. O clique passou a ser ligado no script da própria página. - **O cache de estáticos nunca era encontrado offline.** As páginas pedem `/static/style.css?v=NNN` e o shell guardava `/static/style.css`: chaves diferentes, então offline não se achava nem o que o próprio service worker havia pré-baixado. E cada alteração de CSS/JS deixava mais uma cópia no cache (`?v=100`, `?v=200`…), sem nada apagar as antigas. Agora a chave é a rota, sem a query — uma entrada por arquivo. Consequência deliberada: offline, se a versão mudou desde a última visita, serve-se a **anterior** em vez de nada (online continua sempre a mais nova). - **`webmail.js` estava fora do shell** do service worker — é todo o JS do webmail. E `admin.js` estava dentro, sendo que o painel é explicitamente excluído do service worker. Trocados. - **O badge do Web Push usava o ícone colorido.** O Android descarta a cor e desenha só a silhueta (canal alfa), então um ícone detalhado virava um borrão. Novo `badge-96.png` monocromático. ### Alterado - **`apple-touch-icon` de 180x180** (`icon-180.png`). O iOS ignora o manifest e usa essa tag; com 192 o sistema reamostrava e o ícone saía menos nítido. - **`theme-color` por tema (claro/escuro).** Era fixo, e no modo escuro a barra do app instalado ficava azul clara sobre uma interface escura. - **Atalhos do manifest com ícones próprios** (Escrever, Entrada, Agenda, Contatos). Os quatro usavam o ícone do app, então o menu de toque longo mostrava quatro entradas idênticas. - `scripts/gen_icons.py` gera também o 180, o badge e os ícones de atalho. - `application-name` adicionado. ### Notas - Os ícones do PWA foram conferidos e estão corretos: nos *maskable*, que o Android recorta em círculo, o conteúdo ocupa 85% da zona segura, com folga. - Falta `screenshots` no manifest (renderiza o diálogo de instalação rico no Chrome). Depende de capturas reais do app.
-
1.24.0 [] 24/08/2026### Alterado - **Interface sem emoji colorido.** As 151 ocorrências em títulos, botões, menus, assuntos de e-mail e notificações saíram. O que é tipografia monocromática (`→`, `←`, `✓`, `●`, `…`, `—`) **ficou**: tirar pioraria o texto sem ganho. - Onde o emoji **era** o botão (ações de contato, barra da assinatura, Drive, campos do calendário), virou ícone SVG — remover deixaria o botão vazio. - O filtro `fileicon` (chip de anexo) devolvia emoji; agora devolve o **nome** do ícone e o template renderiza o SVG do conjunto do projeto. - Saíram também dos assuntos de e-mail (`Novo acesso à sua conta`, `Sua caixa está quase cheia`) e das notificações push. - Removidos os seletores de variação `U+FE0F`, que são o que força um símbolo como `⚠` a renderizar colorido. - Emoji que aparece em **assunto de e-mail recebido** continua aparecendo: é conteúdo do remetente, não a interface. - **Menu do painel com ícones.** O webmail já usava o conjunto SVG e só o painel usava emoji. Foram adicionados 21 ícones e cada item do menu ganhou o seu, o que também vale para os chips de anexo e os campos do calendário. - **Seções do menu colapsáveis** (Correio, Segurança, Sistema). O cabeçalho é um `<button>` com `aria-expanded` (alcançável por teclado e anunciado por leitor de tela) e o estado fica no navegador. A seção da página aberta fica sempre expandida — esconder onde o usuário está seria desorientador.
-
1.23.0 [] 24/08/2026### Adicionado - **Capacidade medida** (Configurações → Desempenho → "Capacidade medida", ou `/painel/capacidade`). O guia de dimensionamento que já existia estima pelo número de contas; esta página usa o que o servidor **de fato atendeu**. - Coleta de 5 em 5 minutos (`cron/minimail-capacity`), mais uma varredura de disco por dia — a varredura do maildir é cara em caixa grande e não cabe em toda amostra. - Mede **capacidade** (pico de conexões IMAP, contas ativas, fila do Postfix), **recursos** (RAM por serviço em PSS — somar RSS contaria a memória compartilhada várias vezes e inflaria a conta), **volume** (mensagens/h, tamanho do maildir, índice FTS, contagem de arquivos) e **latência** das páginas do webmail. - A latência é acumulada em memória por worker e gravada a cada 5 minutos: medir não pode custar um `INSERT` por página. - **Recomenda sem aplicar** — cada item traz o comando pronto para copiar. Limite de processo e tamanho de disco erram feio quando erram; quem decide é o administrador. - Campo "projetar para N contas": escala os picos medidos na mesma proporção, para dimensionar uma migração antes de ela acontecer. - Cala a boca quando não sabe: com menos de 12 amostras (ou menos de 1 h de janela), ou com o servidor ocioso, os itens aparecem como "sem dados suficientes" em vez de recomendar um número tirado do nada. Os picos são rotulados como "maior valor amostrado", não pico absoluto — a coleta observa cerca de um minuto a cada cinco, e uma rajada curta passa batido. - A recomendação de workers do painel prefere **thread a worker** de propósito: o pool de conexões IMAP é por processo, então cada worker a mais multiplica as conexões seguradas no Dovecot. A tela mostra essa conta. ### Docs - DEPLOY.md: seção 5c ganhou a linha da capacidade medida.
-
1.22.0 [] 24/08/2026### Corrigido - **Ligar o 2FA numa conta derrubava o webmail dela por inteiro.** A caixa parava de abrir com `[AUTHORIZATIONFAILED]`, e migrações IMAP para essa conta falhavam — mas a interface continuava carregando (a sessão do Flask segue válida), o que fazia o problema parecer instabilidade do servidor. A passdb do master tem `result_success = continue`, então o webmail — que abre a caixa como `conta*webmaster` — também passava pela query SQL do usuário, e ela negava tudo quando `totp_enabled` estava ligado. A query agora abre exceção para o master user. Isso **não** enfraquece o 2FA: o master usa uma senha do servidor (`IMAP_MASTER_PASSWORD`, no `.env`), nunca a senha da conta — e o login IMAP externo com a senha principal continua recusado, só senha de aplicativo entra. Verificado numa matriz de 8 cenários (master com/sem 2FA, conta suspensa, senha errada, senha principal, senha de aplicativo). ⚠️ Servidor já instalado precisa aplicar à mão — ver DEPLOY.md, seção 5c. - **O processo `imap` do Dovecot morria em pastas grandes.** Com o `vsz_limit` padrão (256 MB), o `maildir_uidlist_sync` estoura em pastas com dezenas de milhares de mensagens e o processo cai no meio do FETCH (`Fatal: block_alloc(...): Out of memory`). Para o usuário aparecia como "Serviço de e-mail indisponível" ou a pasta que não abre — e reiniciar o servidor não resolvia. Novo `dovecot/conf.d/99-zz-minimail-perf.conf` sobe o teto para 1G (é espaço de endereçamento virtual, não reserva RAM). - **"Tentar novamente" numa migração IMAP só preenchia o formulário.** A senha de origem já ficava guardada (arquivo `0600`, usado pelo cron de retomada), mas o botão não a usava. Agora ele reexecuta de onde parou. Sem senha guardada, o rótulo passa a ser "Repetir (pede a senha)" — que é o que ele de fato faz. ### Alterado - **Navegação de pastas muito mais rápida em caixas grandes** (contas de 4–20 GB). Medido: pasta de 19,5 mil mensagens 196 ms → 18 ms; de 78 mil, 832 ms → 17 ms; listagem por conversa de 78 mil, 1632 ms → 1 ms. O que mudou: - o `SELECT` deixou de ser refeito a cada página (era o item mais caro: em pasta grande obriga o Dovecot a ressincronizar o maildir, e o FETCH seguinte paga a conta) — a conexão lembra onde parou e um `NOOP` avisa o que mudou; - pool de conexões IMAP por conta (abrir uma custava ~45 ms, e cada página abria uma ou duas), com descarte das ociosas para não segurar processos do Dovecot à toa; - `UID SEARCH ALL` trocado por faixa de números de sequência — a paginação deixou de baixar a lista inteira de UIDs a cada troca de página; - `THREAD` com cache por pasta e teto de 25 mil mensagens (acima disso, o agrupamento por assunto da página, que é O(página)); - parsing dos cabeçalhos da lista sem montar o objeto completo do e-mail (~50x mais barato), preservando exatamente o texto que ia para a tela; - abrir um e-mail não baixa mais todos os UIDs da pasta só para achar o vizinho do "avançar automático". Conferido campo a campo contra a implementação anterior em 1302 mensagens reais de 11 contas. - **Página da lista 45% mais leve** (799 KB → 437 KB numa página de 100 mensagens): o menu "mais opções" de cada linha passou a ser montado ao abrir (eram ~1000 formulários escondidos no HTML) e os ícones viraram um sprite SVG único em vez de repetir o desenho a cada uso. ### Docs - DEPLOY.md: seção 5c ganhou o `vsz_limit` para caixas grandes e o comando de aplicar os `conf.d/99-zz-minimail-*.conf` em servidor já instalado.
-
1.21.2 [] 23/08/2026### Corrigido - **"Serviço de e-mail indisponível" no webmail durante uma migração IMAP.** O `mail_max_userip_connections` do Dovecot conta **por usuário e por IP** — e o webmail e o imapsync conectam como a **mesma conta** a partir do **mesmo 127.0.0.1**. Com o limite baixo (10 numa instalação nova), a migração consumia todas as vagas e o webmail não conseguia abrir a caixa. Novo `dovecot/conf.d/99-zz-minimail-localhost.conf` isenta **apenas o loopback** do limite: a proteção continua valendo para a internet, mas os processos do próprio servidor não disputam vaga entre si. (O nome começa com `99-zz` porque precisa carregar depois do arquivo que o painel gera — senão o Dovecot avisa que a regra global não altera um filtro anterior.)
-
1.21.1 [] 23/08/2026### Adicionado - **`scripts/diag_imap.py`**: diagnóstico da tela *"Serviço de e-mail indisponível"*. Testa, na mesma ordem que o webmail — serviço → porta → login do master user → abrir a caixa — e, quando falha, diz a causa provável (senha do master fora de sincronia com `/etc/dovecot/master-users`, conta inexistente no Dovecot, porta errada no `.env`) e mostra os limites de conexão, úteis quando um imapsync pesado está saturando o Dovecot.
-
1.21.0 [] 23/08/2026### Adicionado - **Migração IMAP resistente ao limite do provedor (Gmail).** A cópia ia sem nenhuma proteção e o Google bloqueava a conta por ~24h ao estourar a cota diária de download. Agora, quando a origem é Gmail: - **exclui `[Gmail]/Todos os e-mails`** (e Importante/Spam/Lixeira) — no Gmail cada mensagem aparece na pasta do rótulo **e** em "Todos os e-mails", então sem isso tudo era copiado **duas vezes**, dobrando o tempo e a cota gasta; - para **antes** do limite diário (`--exitwhenover`) e limita a taxa (`--maxbytespersecond`), evitando o corte por rajada. - **Retomada automática até concluir**: opção *"Continuar automaticamente até concluir"* na tela de migração. A cópia que parou por cota fica **⏸ aguardando cota** e um cron (de hora em hora) a retoma de onde parou, até não faltar mensagem — mostrando quantas voltas e o total já transferido. - Distinguir "acabou" de "parou pela cota" é o ponto central: o desfecho é lido do resumo do imapsync (bytes transferidos + mensagens restantes). - A senha da origem fica num arquivo `0600` (só root) enquanto a migração estiver pendente e é **apagada ao concluir**.
-
1.20.0 [] 23/08/2026### Adicionado - **Relays de saída** (Painel → Relays de saída, super-admin): enviar por um provedor SMTP externo (Amazon SES, SendGrid…) em vez da entrega direta — por **domínio inteiro** ou por **conta específica** (a conta tem precedência). Cadastra host/porta/usuário/senha/TLS e o sistema gera `sender_dependent_relayhost_maps` + mapa de credenciais (modo 0600), validando com `postfix check` **antes** de recarregar (reverte se recusar). - Liga `smtp_sender_dependent_authentication` quando há credenciais: sem isso o Postfix busca a senha pelo servidor de destino e **duas contas com credenciais diferentes no mesmo provedor** usariam a mesma (a 2ª falharia). - Remover a rota/relay devolve o remetente ao envio direto e **limpa** a configuração, sem deixar sobra apontando para mapa vazio. - `libsasl2-modules` no instalador — sem ele o Postfix não autentica em relay externo ("no mechanism available").
-
1.19.0 [] 23/08/2026### Corrigido - **Instalador abortava numa máquina limpa.** O log do treino do Bayes fazia `chown vmail:vmail` **antes** de o usuário `vmail` existir; com `set -e`, isso derrubava a instalação no passo 5/11. Movido para depois da criação do usuário. - **`cairosvg` saiu do `requirements.txt`**: só é usado pelo gerador de ícones (`scripts/gen_icons.py`), mas como dependência de instalação puxava biblioteca C do sistema e podia quebrar o `pip install` num servidor novo. Os PNGs já vão versionados; o gerador documenta o que instalar quando for regenerar. - **Nomes das jails do fail2ban divergiam**: o template trazia `[minimail]` / `[postfix-sasl]`, mas o painel regenera o arquivo como `[minimail-web]` / `[postfix]` — instalação nova ficava com nomes diferentes dos que a UI mostra. Alinhados. O botão "Bloquear IP" também deixou de depender de nomes fixos. - Removidos resquícios do ambiente de teste nos modelos (IPs de exemplo agora na faixa de documentação RFC 5737; referência a hostname interno no BACKEND.md). ### Docs - `DEPLOY.md`: nova seção **5c** com as proteções/integrações que o instalador aplica (varreduras, força bruta lenta, log do Bayes, agenda CalDAV) e o procedimento para **servidores já instalados** — incluindo o alerta de que o nginx deve esvaziar `x-webobjects-remote-user` **antes** de habilitar o `SOGoTrustProxyAuthentication`.
-
1.18.5 [] 23/08/2026### Corrigido - **Cartão do contato voltava a ficar preso na tela.** Os fechamentos dependiam de eventos específicos (`mouseout`, rolagem, clique); quando a linha do remetente é redesenhada e sai do DOM **sob o cursor**, o `mouseout` nunca chega e o cartão ficava órfão. Agora qualquer **movimento do mouse** fora do cartão/remetente fecha (não depende do elemento de origem existir), e perder o foco da janela ou trocar de aba também fecha. - **Mudanças de JavaScript não chegavam ao navegador**: o parâmetro de cache (`asset_v`) usava só o `mtime` do `style.css`, então editar o `webmail.js` — onde vive todo o JS do webmail — não trocava a URL e o navegador/service worker seguiam com a versão antiga. Agora considera também o `webmail.js`.
-
1.18.4 [] 23/08/2026### Corrigido - **Auditoria registrava só o início da atualização.** A atualização roda em segundo plano e nada gravava o desfecho: a tela ficava com uma fila de `update.run — iniciado` sem dizer se deu certo. Agora o fim é registrado como `update.done` ou `update.failed` (com o motivo). - **`update.sync_db` gravava só "falha", sem explicação**: o código lia apenas o `stderr`, mas o `init-db` aborta imprimindo no `stdout`. Agora a auditoria guarda o motivo real.
-
1.18.3 [] 23/08/2026### Corrigido - **Histórico: coluna "Conta/IP" mostrava "unknown"** nas mensagens recebidas. O rspamd grava a *string* `"unknown"` no campo do usuário quando a mensagem não veio autenticada; como a coluna faz "usuário **ou** IP", esse texto vencia o fallback e **o IP de origem nunca aparecia**. Agora "unknown"/"none" contam como sem autenticação: mensagem enviada por uma conta mostra a conta, e a recebida de fora mostra o **IP de quem entregou**.
-
1.18.2 [] 23/08/2026### Corrigido - **"IPs bloqueados" do painel não batia com a tela de Bloqueio de IP** (card mostrava 18, a tabela só 5). A tabela percorria uma **lista fixa** de 4 jails, então as criadas depois (`minimail-scan`, `minimail-slowbrute-smtp`, `minimail-slowbrute-imap`) não apareciam — enquanto o card já enumerava todas. Agora a tela pergunta ao fail2ban quais jails existem (miniMAIL primeiro), e os números conferem. ### Alterado - A coluna de IPs mostra os **12 primeiros** e esconde o restante em "e mais N IP(s)": com a jail de força bruta lenta banindo dezenas, a linha virava um paredão de botões.
-
1.18.1 [] 19/08/2026### Corrigido - **Ícone do app no celular era um placeholder** — um quadrado azul chapado com um "M" minúsculo (736 bytes), que aparecia ao instalar o PWA. Os PNGs agora são gerados a partir do próprio `brand/minimail-icon.svg`, então app instalado e site usam a mesma marca (envelope + nuvem sobre o azul da marca). - Novas variantes **maskable** (`icon-192-maskable.png`, `icon-512-maskable.png`) com margem de segurança: o Android recorta o ícone em círculo/squircle e antes comeria as bordas da arte (o manifesto declarava o mesmo arquivo para "any" e "maskable"). - Cache do service worker para **v6** — sem isso o celular continuaria servindo o ícone antigo, já que os nomes dos arquivos não mudaram. - Novo `scripts/gen_icons.py` regenera tudo a partir do SVG (reproduzível). ### Notas - Quem já **instalou** o app no celular precisa **remover e instalar de novo**: o sistema guarda o ícone do momento da instalação.
-
1.18.0 [] 19/08/2026### Adicionado - **Agenda única: o miniMAIL agora fala CalDAV com o SOGo.** Antes eram duas agendas isoladas (a do miniMAIL vivia só no Postgres, sem CalDAV nem `.ics`). Agora: - evento criado no miniMAIL **aparece no SOGo, no celular e no Thunderbird** (espelhado no CalDAV, com o lembrete como `VALARM` — o próprio aparelho avisa); - evento criado no SOGo/celular **aparece na agenda do miniMAIL** (somente leitura, com tela própria e opção de excluir); - excluir no miniMAIL **remove também** no CalDAV; sem duplicidade (o evento espelhado guarda o `caldav_uid`). - Falha do CalDAV não derruba a agenda (melhor esforço). ### Segurança - SSO interno para o CalDAV: o SOGo passa a aceitar `SOGoTrustProxyAuthentication`, e o **nginx sempre esvazia** o header `x-webobjects-remote-user` das requisições externas. Sem essa limpeza, qualquer cliente entraria como qualquer usuário — validado: pelo loopback o app autentica (207) e pela internet o header forjado é recusado (401). O SOGo só escuta em 127.0.0.1. ### Notas - **Google Calendar continua fora**: o Google não sincroniza com CalDAV de terceiros (só assina `.ics`, somente leitura). A sincronização de duas vias funciona com iPhone, Android (DAVx5), Thunderbird e Outlook. - Eventos criados **antes** desta versão não têm `caldav_uid` e não aparecem no celular — recrie-os para sincronizar.
-
1.17.2 [] 19/08/2026### Corrigido - **Lixeira não mostra mais contador de não lidas.** Excluir mensagens sem abrir deixava a pasta com badge de "não vistas" para sempre, dando a impressão de que havia e-mails a ler no lixo. Agora o contador não aparece na Lixeira — na lateral e também no poll de status (que reintroduzia o badge). Entrada e Lixo Eletrônico seguem contando normalmente (mesmo comportamento do Gmail).
-
1.17.1 [] 19/08/2026### Corrigido - **Expurgo da quarentena falhava no cron** com `No such file or directory: 'postsuper'` (visível em `/var/log/minimail-retention.log`). Causa raiz: os arquivos em `/etc/cron.d` **não herdam** o `PATH` do `/etc/crontab` — o cron entrega um PATH mínimo (ou nenhum) e os binários de `/usr/sbin` (`postsuper`, `postqueue`, `postconf`) não são encontrados. Por isso o descarte por prazo nunca acontecia, embora a config estivesse correta. - `create_app()` agora garante os diretórios `sbin` no `PATH` do processo — cobre **todo** subprocess do app (cron e gunicorn), não só um comando. - Os 12 templates de cron passaram a declarar `PATH=` explicitamente. - O expurgo virou resiliente: falha numa mensagem é registrada e o restante continua, em vez de abortar a tarefa toda com traceback.
-
1.17.0 [] 19/08/2026### Adicionado - **Quarentena: botão "Aplicar agora"** — o descarte por prazo só acontecia no expurgo das **02:00**, então quem configurava os dias não via efeito nenhum e parecia que a limpeza não funcionava. Agora dá para aplicar na hora (super-admin). ### Alterado - **O cron de retenção sempre registra o resultado da quarentena** no `/var/log/minimail-retention.log`: prazo configurado e quantas foram descartadas, ou "descarte automático DESLIGADO (0 dias)". Antes só escrevia quando apagava algo — não havia como saber se a tarefa havia rodado. ### Corrigido - Painel da quarentena com HTML remontado (o formulário do prazo e o de aplicar agora eram aninhados indevidamente).
-
1.16.2 [] 19/08/2026### Corrigido - **Ajuda não abria no painel** (clicava e continuava na mesma tela). Causa: a sessão do painel vive no cookie `mmadmin` (paths `/painel`, `/superadmin`, `/login`); em `/ajuda` o Flask lia o cookie do webmail (`mmsession`), não reconhecia o login e redirecionava para `/login`, que devolvia ao painel. A central passou a ser servida também em `/painel/central-ajuda`, e o item do menu aponta para a rota do contexto (painel x webmail).
-
1.16.1 [] 18/08/2026### Adicionado - **Ajuda também no SOGo**: novo item *Ajuda* no hub de autoatendimento das Preferências (abre `/ajuda?embed=1`). A central de ajuda ganhou versão **embarcada** — mesmo conteúdo, sem a moldura do webmail miniMAIL — para caber no painel do SOGo, que é onde a maioria dos usuários está.
-
1.16.0 [] 18/08/2026### Adicionado - **Central de ajuda** (`/ajuda`, no menu da conta) com a documentação do sistema, visível conforme o perfil: - **Todos**: acessar o e-mail, configurar celular/Outlook (IMAP/SMTP/ActiveSync), senha e e-mail de recuperação, 2FA (incluindo o aviso de que o login do SOGo e o IMAP/SMTP não pedem o código), senhas de aplicativo, spam e quarentena, como treinar o filtro, filtros/férias/encaminhamento, Agenda/Contatos/Drive/Chat e segurança da conta. - **Administradores**: o que faz cada área do painel, bloqueio automático de conta comprometida e o detalhe de logins falhos (força bruta lenta), com link para o manual completo do domínio. - **Super-admin**: referência técnica — serviços, tarefas automáticas (com os horários reais) e onde olhar quando algo falha. - Ícone `help` no conjunto de ícones.
-
1.15.0 [] 17/08/2026### Segurança - **Força bruta LENTA agora é banida** (`deploy/fail2ban/jail.d/minimail-slowbrute.conf`). As jails padrão contam por janela curta (5 falhas em 10 min); um atacante que ritma 1 tentativa a cada ~10 min soma centenas de falhas por dia e **nunca era banido** — foi o caso real: 151 falhas em 24h de um único IP com a jail postfix intacta. As novas jails (SMTP e IMAP) olham **24h**: 20 falhas → ban de 7 dias. Aplicadas no teste, banaram na hora 36 IPs que passavam despercebidos. > Ficam em arquivo separado de propósito: a UI regenera o `minimail.conf` e > apagaria a configuração. **Falso positivo:** cliente com senha velha repete a > falha e pode ser banido — ponha os IPs fixos do escritório em `ignoreip` > (Segurança → Bloqueio de IP) antes de aplicar. ### Adicionado - **Botão "Bloquear" no detalhe de logins falhos**: bane o IP no firewall (7 dias) direto do modal, sem ir à tela de Bloqueio de IP. Só super-admin.
-
1.14.1 [] 17/08/2026### Corrigido - **Detalhe dos logins falhos vinha vazio em servidor movimentado**: a consulta varria o journal inteiro do Postfix e do Dovecot (24h) a cada abertura — em produção isso leva dezenas de segundos e o modal estourava o tempo do nginx. Agora o filtro é feito pelo próprio `journalctl --grep` (com fallback se o systemd não suportar): a consulta caiu de varrer ~25 mil linhas para ~0,3 s.
-
1.14.0 [] 17/08/2026### Adicionado - **Contas em risco: "logins falhos" agora é clicável** e abre o detalhe do ataque — tentativas **agrupadas por IP** (quantidade, protocolo SMTP/IMAP/POP, horário da última) lidas do journal do Postfix e do Dovecot, marcando quais IPs já estão banidos no fail2ban. - O detalhe também agrupa **por faixa /24** e avisa quando o ataque é **distribuído** (muitos IPs com poucas tentativas cada): nesse caso o fail2ban, que conta por IP, não bane ninguém — o caminho é bloquear a faixa em Segurança → Bloqueio de IP.
-
1.13.2 [] 17/08/2026### Corrigido - **Jail `minimail-scan` agora é um arquivo próprio** (`deploy/fail2ban/jail.d/minimail-scan.conf`). Antes era um bloco para colar à mão no jail compartilhado — quem só rodava os `cp` ficava com o filtro instalado e **a jail inexistente** (`fail2ban-client status minimail-scan` → "does not exist"). Instalar agora é copiar o arquivo. `install.sh` copia todas as jails/filtros de `deploy/`. - **nginx: fim do aviso `duplicate MIME type "text/html"`** a cada `nginx -t`/reload: o `sub_filter_types text/html` do snippet do SOGo era redundante (text/html já é o padrão). Removido — a injeção do `apppw.js` continua igual.
-
1.13.1 [] 17/08/2026### Segurança - **Varreduras de bots bloqueadas e banidas** (phpinfo.php, /wp-admin, /.env, /_profiler, /cgi-bin…). Elas geravam enxurrada de 404 no `minimail-web` e **nunca eram banidas**: a jail `minimail-web` lê só o log de falhas de LOGIN. - nginx `snippets/scanners.conf`: responde **444** (fecha a conexão) sem passar pelo gunicorn — o SOGo usa `location ^~` e não é afetado. - fail2ban: filtro `minimail-scan` + jail lendo o `access.log` do nginx (3 tentativas em 10 min → ban de 24 h). - `install.sh` já instala tudo em servidores novos. > **Servidor existente:** além do `git pull`, copie o snippet e o filtro, > inclua `snippets/scanners.conf` no site do nginx e adicione a jail > `[minimail-scan]` — ver `deploy/` (passos no DEPLOY.md).
-
1.13.0 [] 17/08/2026### Adicionado - **Tempo de permanência na quarentena configurável** — campo *"Descartar automaticamente após (dias)"* na tela de Quarentena (super-admin). Mensagens retidas há mais tempo que o prazo são apagadas no expurgo diário das 02:00 (cron de retenção), com registro na auditoria. **0 = desligado** (padrão, o comportamento de sempre): a fila HOLD do Postfix não expira sozinha, então sem isso a quarentena crescia indefinidamente.
-
1.12.2 [] 17/08/2026### Alterado - **Aviso de novo acesso (IP nunca visto) só para contas com e-mail de recuperação.** Sem ele o aviso ficava apenas na própria caixa — inútil no cenário que importa (conta invadida: o invasor lê/apaga) e ruído para o usuário. Quem tem o endereço externo continua recebendo nos dois lugares. Os demais avisos de segurança (senha, 2FA, senha de aplicativo, sessões) não mudaram.
-
1.12.1 [] 17/08/2026### Removido - **Webmail: aviso "cadastre um e-mail de recuperação"** (a faixa no topo, com os botões Cadastrar/Depois). O cadastro segue disponível em Configurações → Conta e no hub do SOGo, e o painel continua mostrando o card **"Sem e-mail de recuperação"** para o admin acompanhar.
-
1.12.0 [] 17/08/2026### Adicionado - **Bayes: "Material aprendido" no painel** (Segurança → Proteção avançada) — tabela com as mensagens usadas no treino: data, tipo (spam/legítima), conta, remetente e assunto (decodificado). Como o rspamd guarda só hashes de tokens (a mensagem não fica salva), o histórico passou a ser registrado **no momento do treino** pelos scripts do Dovecot (`learn-spam.sh`/`learn-ham.sh`) em `/var/log/minimail-bayes.log`, com logrotate mensal. O treino em si nunca falha por causa do log.
-
1.11.1 [] 17/08/2026### Removido - **SOGo: item "Verificação em 2 etapas"** saiu do hub de autoatendimento. O login do SOGo autentica direto no SQL (`userPasswordAlgorithm = crypt`) e **nunca pede o código TOTP** — habilitar o 2FA por lá dava falsa sensação de segurança e só quebrava os apps IMAP/SMTP. O 2FA continua disponível no webmail miniMAIL, onde o login realmente o exige.
-
1.11.0 [] 17/08/2026### Adicionado - **2FA: confirmação de que guardou os códigos** — caixa "Já guardei meus códigos em local seguro"; sair da tela sem marcar dispara o aviso do navegador (os códigos não podem ser exibidos de novo). Nas duas interfaces (webmail e SOGo). - **2FA: "confiar neste navegador por 30 dias"** no login do painel — marcando, o código não é pedido de novo nesse navegador (cookie assinado de 30 dias). Trocar a senha, desativar/reativar o 2FA ou desconectar dispositivos cancela a confiança. O webmail já tinha esse recurso; agora o painel também. ### Corrigido - **Login com 2FA no painel entrava em loop de volta ao /login**: o estado pendente era gravado no cookie `mmadmin` (em `/login`), mas `/login/2fa` não casava com a regra de path e lia o cookie do webmail. Agora `/login*` inteiro usa o cookie do painel.
-
1.10.0 [] 16/08/2026### Adicionado - **2FA: salvar os códigos de recuperação** — os códigos (mostrados uma única vez) agora têm **⬇️ Baixar (.txt)**, **📋 Copiar** e **🖨 Imprimir**, nas duas interfaces: webmail miniMAIL e a tela embarcada no **SOGo**. O .txt inclui a conta e a data. Antes só dava para anotar na mão — fechar a tela sem copiar significava perder os códigos. ### Alterado - Aviso dos códigos mais claro ("mostrados SÓ AGORA; guarde antes de sair") e com a consequência explícita de perder o app e os códigos.
-
1.9.2 [] 16/08/2026### Adicionado - **SOGo: item "E-mail de recuperação"** no hub de autoatendimento das Preferências (abre `/account/recovery?embed=1` no painel, como os demais). Assim quem usa o SOGo — a maioria — também cadastra/atualiza o endereço de recuperação; o POST preserva o modo embarcado.
-
1.9.1 [] 16/08/2026### Adicionado - **Painel: card "Sem e-mail de recuperação"** nos dashboards do super-admin e do domínio (só aparece se houver contas nessa situação) + etiqueta **"sem recuperação"** na lista de contas do domínio — essas contas não recebem avisos de segurança fora da caixa nem conseguem redefinir a senha sozinhas. - **Webmail: lembrete** para o usuário cadastrar o e-mail de recuperação quando está vazio, com botão *Cadastrar* (abre em modal) e *Depois* (some por 30 dias).
-
1.9.0 [] 16/08/2026### Adicionado - **Avisos de segurança no e-mail de recuperação** (endereço externo da conta). Novo helper `notify.security_notify()` envia para a **caixa E o e-mail de recuperação** — essencial quando a conta foi invadida (o invasor apagaria o aviso na caixa) ou quando a pessoa perdeu o acesso. Eventos cobertos: - **acesso de IP novo** (web e IMAP/POP) — antes ia só para a própria caixa; - **senha alterada** e **senha redefinida** pelo link de recuperação; - **2FA ativado/desativado**; - **nova senha de aplicativo** criada; - **sessões encerradas** em outros dispositivos; - **e-mail de recuperação alterado** — avisa também o **endereço antigo**, para o dono legítimo saber se um invasor trocou o contato de recuperação. Todos os avisos trazem conta, data/hora e IP, e nunca quebram o fluxo que os disparou (melhor esforço).
-
1.8.0 [] 16/08/2026### Adicionado - **Contas em risco**: coluna **Status** (Ativa / 🔒 Bloqueada até HH:MM / Suspensa) — fim da dúvida se a conta está ativa ou bloqueada. - **Card "Contas em risco"** no dashboard do super-admin e no de domínio (conta o total e destaca as de risco alto; leva para a lista). - **Bloqueio temporário automático**: contas com envio acima do limite agora são bloqueadas por **N horas** (configurável em Segurança → Avançado, padrão 4h) e **reativadas sozinhas** ao expirar (cron a cada 10 min). 0h = bloqueio permanente (comportamento antigo). Novo campo `users.blocked_until`. - **Histórico**: filtro por **remetente** (também destinatário/conta), na hora no navegador, com contador.
-
1.7.6 [] 16/08/2026### Adicionado - **Dashboard de template no Zabbix 7** (aparece pronta ao vincular o template): 6 KPIs de valor (enviados/recebidos/fila/quarentena/spam/IPs bloqueados), 3 gráficos em **barras de verdade** (widget svggraph, draw type Bar) e um widget de problemas do host.
-
1.7.5 [] 16/08/2026### Alterado - **Template Zabbix: gráficos sem buracos** — itens agora coletam a cada 1 min (removido o "descartar sem mudança", que criava espaços em branco) e os gráficos usam **região preenchida** (FILLED_REGION), visual cheio no lugar da linha com lacunas. (Gráfico clássico do Zabbix não tem barra de verdade; região preenchida é o equivalente.)
-
1.7.4 [] 16/08/2026### Corrigido - **Monitoramento: macro `{$MINIMAIL.URL}` deve ser só a URL base** — a UI mostrava a URL completa e, colada na macro, o template duplicava o caminho (`/api/monitoring/metrics/api/monitoring/metrics` → sem dados). A aba Monitoramento agora mostra a **URL base** (valor da macro) separada do endpoint de teste, com aviso. - **Versões/fila/quarentena "indisponível" ou zeradas no snapshot**: o cron `minimail-dashboard` rodava sem `/usr/sbin` no PATH (onde ficam postconf/postqueue/postsuper/dovecot/doveadm). `_run` agora amplia o PATH — corrige as versões do painel e da API e a contagem de fila/quarentena.
-
1.7.3 [] 16/08/2026### Corrigido - **Template do Zabbix 7 falhava ao importar** ("unexpected tag graphs"): os gráficos são seção de topo (`zabbix_export.graphs`), não vão dentro do `template`. Movidos para o lugar certo; removido `trends` do item mestre (TEXT).
-
1.7.2 [] 16/08/2026### Adicionado - **Monitoramento: descoberta (LLD) de serviços** — endpoint `/api/monitoring/discovery` e regra de descoberta no template do Zabbix criam automaticamente um item + trigger "serviço parado" por serviço (sem lista fixa). Template ganhou também **valuemap** (ativo/parado) e **3 gráficos** (correio hoje, fila/quarentena, spam hoje).
-
1.7.1 [] 16/08/2026### Adicionado - **Template do Zabbix 7.0** para a API de monitoramento (`deploy/zabbix/minimail_zabbix7.yaml`): item mestre HTTP agent + itens dependentes por JSONPath (fluxo de correio, spam, fila, quarentena, serviços up/down, saúde, versões) + triggers prontos (serviço parado, fila alta/crítica, saúde crítica, snapshot velho, sem dados) e macros `{$MINIMAIL.URL}`/`{$MINIMAIL.TOKEN}` e limiares. Baixável pela aba Configurações → Monitoramento.
-
1.7.0 [] 16/08/2026### Adicionado - **API de monitoramento (Zabbix)** — endpoint read-only `GET /api/monitoring/metrics` (JSON) autenticado por token dedicado, com as métricas do servidor: enviados/ recebidos (dia e mês), spam, fila, quarentena, IPs bloqueados, contagens (domínios/contas/aliases), armazenamento, serviços (up/down) e saúde. Também aceita item único em texto puro (`/api/monitoring/metrics/<chave>`). Token e URL gerenciáveis em **Configurações → Monitoramento** (super-admin). Reusa o snapshot do dashboard (sem subprocess por request).
-
1.6.7 [] 16/08/2026### Alterado - **Revertido** o card compacto de "Saúde do sistema" (1.6.6): voltou ao layout anterior em grid de caixas (bolinha + nome + detalhe visível).
-
1.6.6 [] 13/08/2026### Alterado - **Painel: card "Saúde do sistema" compacto** — as verificações viram pílulas (bolinha de status + nome) numa linha só ao lado do título; o detalhe aparece no tooltip. Antes era um grid de caixas ocupando várias linhas.
-
1.6.5 [] 13/08/2026### Adicionado - **Quarentena: campo de pesquisa** que filtra as linhas por remetente, destinatário, assunto ou motivo (na hora, no navegador), com contador "mostrando X de Y". ### Corrigido - **Coluna "Motivo" vinha em branco**: mensagens em HOLD não têm `delay_reason` na fila. Agora o motivo é lido do header `X-Spamd-Result` (score / limite do rspamd); se não der pra ler o score, mostra "Spam".
-
1.6.4 [] 13/08/2026### Alterado - **Quarentena**: coluna **Assunto** também não estica mais a linha (reticências + tooltip com o assunto completo).
-
1.6.3 [] 13/08/2026### Alterado - **Quarentena**: colunas **De** e **Para** não quebram mais linha (reticências + tooltip com o valor completo) e nova coluna **Recebido** com data/hora no fuso do usuário.
-
1.6.2 [] 13/08/2026### Adicionado - **Quarentena: "Liberar + permitir remetente"** — além de entregar a mensagem retida, adiciona o remetente à **lista de permitidos** (whitelist do rspamd) para ele não cair de novo na quarentena. Escopo: o domínio do admin (ou o domínio do destinatário, para super-admin). O "Liberar" simples continua só entregando aquela mensagem, sem memorizar o remetente.
-
1.6.1 [] 04/08/2026### Corrigido - **Botão "Voltar" ao ler um e-mail não fazia nada** sob a nova CSP: usava `href="javascript:history.back()"`, e URLs `javascript:` são bloqueadas por `script-src` sem `unsafe-inline`. Virou `data-act="back"` (delegação). Era o último vetor de execução inline — agora não há mais `on*=` nem `javascript:` em nenhum template.
-
1.6.0 [] 04/08/2026Revisão geral do projeto (Sprints 1–4): segurança, robustez de dados, fundação e frontend. Destaque: CSP sem `unsafe-inline` no `script-src` (nonce). ### Segurança - **Anexos inline com allowlist** (só imagem/PDF abrem inline; o resto baixa) + `X-Content-Type-Options: nosniff` — fecha XSS via anexo. - **CSP endurecida**: `script-src 'self' 'nonce-…'` (removido `unsafe-inline`). Todo `<script>` inline carrega nonce por request; os ~199 handlers `on*=` viraram delegação por `data-*`. `style-src` mantém `unsafe-inline` (escopo). - **Trava de SECRET_KEY**: em produção (sem DEBUG) recusa subir com o segredo padrão — cookies/tokens seriam forjáveis. - **SSRF no cancelar-inscrição** (One-Click/redirect) bloqueia IP privado/ loopback/link-local; **imagens remotas** bloqueadas até "Mostrar imagens" (inclui protocol-relative). - Rate-limit também na etapa **2FA**; token do hook do Dovecot comparado com `hmac.compare_digest`; **CR/LF/controle** removidos na busca IMAP e no Sieve. - XSS latente no nome de arquivo do **Drive** (montado por `innerHTML`) trocado por `createTextNode`. ### Adicionado - **Testes** (pytest): escaping IMAP, geração de Sieve, SSRF, allowlist de anexo, trava de SECRET_KEY e permissão de domínio. - **Toast de novo e-mail** (`wmToast`) reativado — aviso visual ao chegar mensagem com a aba aberta (clique leva à Entrada; some em 6s). - **Acessibilidade** nos modais: `role="dialog"`, `aria-modal`, foco preso (focus-trap) e devolução do foco ao fechar. - Página amigável (503) quando o **IMAP está fora do ar**. ### Alterado - **`app/admin/routes.py` (3032 linhas) dividido em pacote `app/admin/routes/`** por área (dashboard, domains, users, security, system, imports, admins) — mapa de rotas idêntico. - **JS do webmail extraído** de `base.html` para `app/static/webmail.js` (config dinâmica via `window.WM`); framework de delegação de eventos. - **Backup** com `pipefail` + validação (`gzip -t`, tamanho, remove parcial) e **alerta** "backup atrasado" (>26h). Crons com **flock** (sem sobreposição). - **gunicorn**: `gthread` + `--timeout 300`; **systemd** com hardening seguro (NoNewPrivileges, ProtectClock/KernelModules, RestrictRealtime, LimitNOFILE, OOMScoreAdjust). - **Migrações falham alto**: erro no `flask init-db` aborta o deploy (exit 1) em vez de subir com o banco meio migrado. - Polls (contadores, chat, autosave) **pausam com a aba oculta** e retomam ao voltar — economia de rede/bateria. ### Corrigido - Imports internos de função quebrados pela divisão do painel (resolviam para `app.admin.*` em vez de `app.*`) — causavam 500 ao abrir domínio, settings, etc. Coberto por check estático de todos os imports relativos do pacote. - Chamada WTForms `{{ campo(onchange=…) }}` ajustada para `**{...}` na conversão dos handlers.
-
1.5.6 [] 03/08/2026### Removido - **Webhooks e API REST desativados** (sem uso por enquanto): link/aba fora da interface, página de webhooks e endpoints /api retornam 404. Código e tabelas preservados — reativação é descomentar 3 linhas (API) e remover um abort (webhooks).
-
1.5.5 [] 03/08/2026### Adicionado - **Limpar mensagens antigas** por data, com backup antes: contar → baixar .zip (.eml por pasta) → apagar definitivamente. Disponível para o USUÁRIO (Configurações → Conta) e para ADMIN/SUPORTE no painel (botão 🧹 Limpeza em cada conta do domínio), com escopo por domínio e auditoria. ### Corrigido - **Login no painel com conta sem permissão não trava mais no 403**: a sessão é desfeita e o usuário volta ao /login com aviso (antes só resolvia limpando cookies).
-
1.5.4 [] 03/08/2026### Corrigido - **Não lidas "invisíveis" com conversas agrupadas**: a linha do tópico agora fica não lida (negrito) se QUALQUER mensagem do grupo estiver não lida — antes o contador da Entrada mostrava não lidas que não apareciam na lista (ficavam dentro de tópicos cuja mensagem mais recente já tinha sido lida). - **Hover card preso ao voltar de um e-mail** (bfcache): o card é fechado no pageshow/pagehide.
-
1.5.3 [] 03/08/2026### Corrigido - **Hover card do contato ficava preso na tela**: agora só abre se o mouse ainda está sobre o remetente, e fecha imediatamente ao rolar a lista, clicar, teclar, sair do remetente ou sair da janela.
-
1.5.2 [] 02/08/2026### Alterado - **Sessões persistentes (30 dias)** no webmail e no painel — fechar o navegador não desloga mais (estilo Gmail); renova a cada uso. Trocar a senha ou "Desconectar todos os dispositivos" continua derrubando tudo na hora. - **Painel fora do PWA**: o service worker ignora `/painel`, `/superadmin`, `/login` e `/logout` — o console de administração roda sem cache/offline do webmail (cache do SW renovado, v5).
-
1.5.1 [] 02/08/2026### Corrigido - **Usuário preso na tela "Você está offline"** mesmo com internet (ex.: após logout, ou com antivírus/proxy interferindo): o service worker agora tenta a navegação de novo pela URL antes de cair no offline, e a página offline **se recupera sozinha** (testa o servidor a cada 3s por fora do SW) e ganhou o botão **"reparar o aplicativo"** (desregistra o SW e limpa os caches). Cache do SW renovado (v4) — os navegadores se atualizam sozinhos na próxima visita.
-
1.5.0 [] 02/08/2026### Adicionado — webmail (paridade Gmail) - **Conversas completas**: agrupamento por conversa vira o PADRÃO; a linha da lista mostra os participantes ("Regis, Ana 3") e a leitura vem empilhada em ordem cronológica (atual expandida, demais recolhidas). - **Abas da caixa** (preferência): Principal | Promoções | Atualizações — regra Sieve roteia newsletters (List-Unsubscribe) e avisos automáticos (no-reply). - **Snackbar "Desfazer"** ao arquivar/mover/excluir (desfaz pela Message-ID); após arquivar você permanece na pasta atual. - **Cancelar inscrição** (List-Unsubscribe: One-Click/mailto/URL), **bloquear remetente** (filtro → Spam) e **silenciar conversa** (arquiva o tópico + filtra as próximas) no menu ⋮ da leitura. - **Painel de leitura lado a lado** (preferência em Exibição). - **Banner "por que está no Spam?"** (veredito do rspamd) e **banner de férias** com "Desativar agora". - **Busca**: `com-estrela:sim`, `maior:5M`, `arquivo:nome`. - **Hover card do contato** (Escrever / Ver mensagens) ao parar no remetente. - **PWA**: instalável (atalhos de app, botão "Instalar aplicativo", instruções iOS), badge de não-lidos no ícone (Badging API) e metas standalone. ### Corrigido — 2FA - **Etapa 2FA presa na sessão** fazia o login por senha de QUALQUER usuário cair na tela de código ("pede 2FA sem ter ativado"). A 2ª etapa agora só roda vindo da tela do código, com expiração de 5 min. - **"Confiar neste dispositivo por 30 dias"** no código (revogado ao desativar/reativar o 2FA) e **gerar novos códigos de recuperação**. ### Segurança — senhas de aplicativo - **Com 2FA ativo, IMAP/POP/SMTP só aceitam senha de aplicativo** (a senha principal deixa de valer nos clientes — antes o 2FA era contornável pelo IMAP). SOGo não é afetado (autentica por SQL próprio). - **"Último uso" agora funciona** (função SQL `minimail_app_pw_ok` valida e registra na mesma chamada); UI com botão copiar e avisos claros. ### Notas de atualização (produção) - Rodar `flask init-db` (colunas novas + função SQL) e ATUALIZAR o bloco `passdb sql` do `/etc/dovecot/local.conf` conforme `deploy/dovecot/local.conf.example` + `systemctl restart dovecot`. - Usuários com 2FA ativo precisam de senha de aplicativo nos clientes IMAP/SMTP. - Conversas ligadas por padrão para usuários novos; para os existentes: `UPDATE users SET mail_threads=true;` (opcional).
-
1.4.0 [] 02/08/2026### Adicionado — webmail - **Busca full-text (FTS)**: Dovecot flatcurve (Xapian, pt+en) — busca por corpo instantânea; teto de resultados 50→200 por pasta. Indexação automática. - **Busca avançada**: painel na barra (De/Para/Assunto/Contém/período/pasta/ com anexo/não lidas) e operadores `para:`, `antes:`/`depois:` (AAAA-MM-DD), `anexo:sim`, `naolidas:sim`, `pasta:`, `marcador:`. - **Marcadores (labels)** estilo Gmail via KEYWORDS IMAP: nome+cor, chips na lista, menu 🏷 na leitura, páginas por marcador na lateral, CRUD em Configurações → Marcadores (ficam na própria mensagem, em qualquer pasta). - **Agendar envio**: setinha no Enviar (presets/data-hora), fila com página "Agendados" (cancelar → rascunho), cron a cada minuto no fuso do usuário. - **Soneca (snooze)**: presets na leitura/lista; a mensagem vai para "Soneca" e volta à Entrada como não lida na hora marcada. - **Modelos (respostas prontas)**: CRUD em Configurações → Modelos + botão 📄 no compositor. - **Notificações do navegador (Web Push)**: aviso de e-mail/chat novo com a aba fechada — Dovecot (push_notification/Lua) → VAPID/pywebpush; opt-in em Configurações → Correio. - **Anti-phishing**: banner vermelho quando SPF/DKIM/DMARC falham (Authentication-Results) + tag "externo" p/ domínio não hospedado. - **Sub-endereçamento** `usuario+tag@dominio` (recipient_delimiter=+). - **Exportar/importar caixa** pelo usuário (.zip de .eml / .eml / .mbox). - **Assinatura por endereço**: cada alias pode ter assinatura própria (o envio usa a do "De" escolhido). - **Atividade da conta**: sessões ativas + últimos acessos (7 dias) + botão "desconectar todos os outros dispositivos". - **Grupos de contatos como destinatários**: digitar o grupo no Para expande para os membros. - **Filtros turbinados**: ações novas "aplicar marcador" e "encaminhar para" (redirect :copy) + botão "aplicar às mensagens existentes" (retroativo). - **Lightbox** para imagens anexadas (anterior/próxima, Esc) e **PDF inline** no navegador. - **Gestos no celular**: deslizar a linha → direita arquiva, esquerda exclui. - **Tecla `?`** abre a lista de atalhos; **auto-avançar** (preferência) abre a próxima mensagem após arquivar/excluir; **desfazer envio configurável** (0/5/10/20/30s); **100 mensagens por página** (padrão) e lista sem avatar (mais Gmail). ### Adicionado — operação/segurança - **Aviso de cota ao usuário** (80%/95%, sem spam) + banner no webmail ≥80%. - **Alerta de login novo**: e-mail ao dono quando a conta loga de IP nunca visto (web na hora; IMAP/POP via varredura do journal no cron). - **Relatório semanal por e-mail** aos admins de cada domínio (volume, spam, cota, nota de entregabilidade) — segunda 07:10. ### Corrigido - Recompilação do Sieve: o binário compilado (`minimail.svbin`) era removido com nome errado ao atualizar filtros. ### Infra/deploy - Novos: pacote `dovecot-flatcurve`, pip `pywebpush`, crons `minimail-sendlater` (minuto) e `minimail-weeklyreport` (semanal), config FTS em `deploy/dovecot/conf.d/`, push do Dovecot gerado pelo `init-db`. Tabelas novas via `init-db` (scheduled_mails, snoozed_mails, mail_templates, push_subscriptions, user_labels, user_signatures, known_ips) + colunas em users (quota_warned_level, mail_undo_secs, mail_autoadvance, mail_per_page=100 padrão).
-
1.3.0 [] 21/07/2026### Adicionado - **Admin de "suporte"** (nível intermediário): admin dedicado que gerencia domínios como um admin de domínio, mas também pode **criar domínios** (auto-atribuídos a ele), **gerar DKIM**, ver DNS e definir **plano/cota** — tudo só nos próprios domínios, sem os poderes de sistema do super-admin. Flag `can_manage_domains` marcável em Admin → Administradores; console ganha o menu **Domínios** (lista só os que gerencia + criar). ### Corrigido - **Cards do painel não atualizavam** ao trocar o domínio ativo no seletor: as páginas de `/painel` e `/superadmin` passaram a enviar `Cache-Control: no-store` (eram dinâmicas por-usuário e o navegador servia a versão em cache).
-
1.2.0 [] 21/07/2026### Adicionado - **Renomear o endereço de uma conta** (Admin → Editar conta): muda a parte antes do @, **move o Maildir no disco** (`/var/vmail/<domínio>/<antigo>` → `<novo>`), atualiza aliases que entregam na caixa e `forward_to`, com opção de **manter o endereço antigo como alias** (não perder e-mails) e encerro das sessões (novo login com o endereço novo). Mesmo domínio; registra em auditoria (`user.rename`). ### Corrigido - **Atalhos de teclado disparando ao escrever**: digitar no corpo do e-mail (editor rich-text, um `contenteditable`) acionava os atalhos (c/j/k/e/r/#). O handler global agora ignora campos editáveis e o compositor/modal aberto.
-
1.1.6 [] 16/07/2026### Corrigido - **"CSRF token has expired"** em ações após deixar o webmail aberto por horas (ex.: excluir em massa). Removido o limite de 1h do token (`WTF_CSRF_TIME_LIMIT = None`); ele continua assinado e vinculado à sessão, então a proteção anti-CSRF permanece.
-
1.1.5 [] 16/07/2026### Adicionado - **Seleção por critério na lista** (estilo Gmail): setinha ao lado da checkbox "selecionar" abre um menu com **Todas / Nenhuma / Lidas / Não lidas / Com estrela / Sem estrela** (age sobre a página atual).
-
1.1.4 [] 16/07/2026### Corrigido - **E-mail lido não voltava como lido na lista**: ao abrir um e-mail e clicar em "voltar", a lista vinha do bfcache do navegador (versão antiga, "não lido"). Agora, ao voltar, a linha é refletida como lida (estilo, botão e contador) sem recarregar a página, preservando a rolagem.
-
1.1.3 [] 16/07/2026### Corrigido - **Contadores das pastas** agora atualizam ao voltar para a aba (`visibilitychange`/`focus`), não só pelo intervalo. Corrige o caso de deixar o webmail aberto por horas: o navegador suspende a aba e o `setInterval` para, e antes só recarregando a página os e-mails novos apareciam na lateral.
-
1.1.2 [] 15/07/2026### Alterado - **Compositor repaginado ao estilo Gmail**: barra inferior com **Enviar** em pílula + ícones (formatação, anexar, Drive), status de autosave, salvar rascunho e **descartar** (lixeira). Cabeçalho com **minimizar / maximizar / fechar**. Anexos viram **chips** com remoção individual. Painel mais largo (640px). - **Rich-text agora é o padrão** (novos usuários já compõem em HTML). A barra de formatação ficou completa e com ícones Material (Gmail): **faixa principal sempre visível** (Fonte, Tamanho, N/I/S, cor, listas, link, remover formatação) e uma **faixa extra** atrás do "A" (desfazer/refazer, realce, alinhamento, recuo, citação, tachado, imagem, linha). ### Corrigido - **Autocomplete de destinatários**: a 1ª sugestão já vem destacada e **Enter/Tab preenchem** o endereço (antes o Enter enviava a mensagem sem querer).
-
1.1.1 [] 14/07/2026### Alterado - **Leitura de e-mail**: ao rolar, agora rola só a mensagem — a barra lateral e o topo ficam fixos, e a barra de ações do e-mail fica grudada no topo (estilo Gmail). A página de leitura passou a usar rolagem interna (`body.mail-page` + `msg-content`), como já era na lista de pastas.
-
1.1.0 [] 14/07/2026### Adicionado - **Ícones clássicos (Material, estilo Gmail)** em toda a webmail própria — conjunto SVG embutido (`webmail/_icons.html`, offline/sem CDN), coerente com o tema claro/escuro: barra lateral, topo (menu/busca/apps), abas de Configurações, lista e leitura de mensagens, menus de contexto, compositor, Drive e widget de chat. - **Visão de conversa** na leitura: mensagens anteriores do mesmo tópico aparecem recolhidas acima da atual e expandem sob demanda (quando "agrupar por conversa" está ligado). - **Texto citado recolhível** (botão "•••") em respostas/encaminhamentos. - **Atalhos de teclado** na caixa: `j/k` navega, `Enter/o` abre, `e` arquiva, `#` exclui, `s` estrela, `x` seleciona, `r`/`f` respondem/encaminham. - **Autosave de rascunho** (~45s) enquanto se escreve, sem duplicar (rastreio via UIDPLUS); mostra "Rascunho salvo HH:MM". - **Desfazer envio**: janela de ~6s ("Enviando… Desfazer") antes do envio real. - **Operadores de busca**: `de:`, `para:`, `assunto:`, `cc:` (ex.: `de:joao assunto:fatura`). ### Alterado - **Chat repaginado**: aviso de mensagens novas com o widget fechado (badge no botão + pulso + título da aba), lista de conversas com prévia/horário/não-lidas atualizando sozinha, envio instantâneo (eco otimista) e avatares coloridos. Novos endpoints `/chat/unread.json` e `/chat/peers.json` enriquecido. - **Desempenho**: cache curto (~30s) da lista de pastas elimina um `STATUS` por pasta a cada navegação; invalidação automática após ações. - **Refatoração**: `app/webmail/routes.py` (~1.180 linhas) dividido no pacote `app/webmail/routes/` (`_common`, `messages`, `compose`, `contacts`, `folders`, `settings`, `misc`); imports movidos para o topo. Sem mudança de comportamento. ### Corrigido - **Busca**: aspas na consulta deixam de quebrar o comando IMAP (escape RFC 3501). - **IMAP fora do ar**: em vez de erro 500, exibe página amigável (HTTP 503) ou JSON de erro em chamadas assíncronas. ### Segurança - **Limite de anexos no envio** (25 MB no total, upload + Drive) com aviso claro.
-
1.0.115 [] 14/07/2026### Corrigido - **Imagens embutidas (`cid:`)** — logos de assinatura, etc. — agora renderizam no corpo do e-mail (o `cid:` é reescrito para o anexo servido inline). Essas imagens deixam de ser listadas como anexo separado (nem como chip na lista).
-
1.0.114 [] 14/07/2026### Corrigido - Chips de anexo na lista deixam de **quebrar a linha** — ficam inline (mesma linha, truncando se faltar espaço). - Snippet da lista mostrava MIME cru ("Content-Type: multipart/alternative…", boundaries) em mensagens com anexo — agora é limpo.
-
1.0.113 [] 14/07/2026### Alterado - **Abrir e-mail em página cheia** (estilo Gmail) em vez de modal; menus **Abrir em nova guia** e **Imprimir** (à direita da barra de ações). ### Adicionado - **Anexos na listagem** como "chips" (nome + ícone por tipo), estilo Gmail — extraídos do BODYSTRUCTURE, sem baixar a mensagem.
-
1.0.112 [] 13/07/2026### Alterado - As notificações em "torradinha" (toast) foram **desativadas** — não aparecem mais na tela (o contador de não lidas e o fechamento do compositor seguem como feedback).
-
1.0.111 [] 13/07/2026### Adicionado - **Launcher de Aplicativos** no topo (ao lado do usuário): Contatos, Calendário, Chat e Drive — saíram da barra lateral. - **Autocomplete de destinatários** (Para/Cc/Cco) por nome **ou** e-mail, por destinatário, com navegação por teclado. - **Menu de contexto (botão direito)** nas mensagens (marcar lida/não lida, sinalizar, arquivar, spam, excluir) e nas pastas (marcar todas como lidas, renomear, excluir, esvaziar). - **Arrastar mensagem para uma pasta** (mover por drag-and-drop na lateral). - **Coexistência de filtros com o SOGo**: Filtros/Férias/Encaminhamento do webmail passam a rodar via Sieve `sieve_before` (`~/sieve/minimail.sieve`), sem sobrescrever o script do SOGo — os dois valem ao mesmo tempo. ### Alterado - **Lista de e-mails em largura total** (sem painel de leitura); abrir um e-mail abre em **modal** (fecha com **Esc**). - Contador de não lidas da pasta **decrementa ao abrir** a mensagem. - **Modo escuro** do webmail bem melhorado (lista, leitura, compositor, calendário, chat, contatos, menus). - Configurações abrem as opções no menu lateral; compositor com botão maximizar. ### Corrigido - Menu de contexto de pastas abria esticado — agora compacto e vertical. > **Deploy:** esta versão exige um passo manual no Dovecot em produção — ver > `deploy/dovecot/local.conf.example` (bloco `sieve_script minimail`) + `reload`.
-
1.0.110 [] 13/07/2026### Corrigido - Menu de contexto das pastas exibia as opções na horizontal; agora empilha vertical (itens em bloco), como um menu normal.
-
1.0.109 [] 13/07/2026### Corrigido - E-mails com bloco `<style>` (ex.: relatórios do Proxmox) mostravam o CSS como texto: agora `<style>/<script>/<head>` são removidos **com o conteúdo** antes da sanitização, e a mensagem renderiza corretamente. ### Adicionado - **Menu de contexto (botão direito) nas pastas** da barra lateral: marcar todas como lidas, abrir, renomear/excluir (não-sistema) e esvaziar (Lixeira/Spam).
-
1.0.108 [] 12/07/2026### Alterado - Opção de verificação de novos e-mails **a cada 15 segundos** (near-real-time seguro por polling; IMAP IDLE real exigiria workers assíncronos).
-
1.0.107 [] 12/07/2026### Adicionado - **Identidades no "De"**: ao escrever, é possível enviar como um **alias** ativo que entrega para você (aparece um seletor "De" quando há aliases). O endereço é validado no servidor (só aliases seus).
-
1.0.106 [] 12/07/2026### Adicionado - **Contatos** mais completos: **editar**, **grupos** (com filtro) e **importar/exportar CSV**.
-
1.0.105 [] 12/07/2026### Adicionado - **Baixar mensagem (.eml)** na leitura. - **Sempre exibir imagens deste remetente**: botão no aviso de imagens bloqueadas adiciona o remetente a uma lista de confiança.
-
1.0.104 [] 12/07/2026### Alterado - **Configurações**: as opções agora aparecem no **menu lateral** (a lista horizontal de abas saiu). - **Compositor**: botão para **maximizar/restaurar** o painel.
-
1.0.103 [] 12/07/2026### Adicionado - **Editor de texto do compositor mais completo**: fonte, tamanho, cor do texto e realce, negrito/itálico/sublinhado/tachado, alinhamento, listas, recuo, citação, linha, link/remover link, imagem e desfazer/refazer. ### Alterado - **Pastas** agora ficam em **Configurações → Pastas** (criar/renomear/excluir/ esvaziar); a barra lateral passa a ter só um **"+ Nova pasta"** para criação rápida.
-
1.0.102 [] 12/07/2026### Alterado - **Agrupar por tópico** agora usa **threading real do IMAP** (`THREAD REFERENCES`), agrupando a conversa **cruzando páginas** e ordenando pela mensagem mais recente; se o servidor não suportar, cai no agrupamento por assunto (dentro da página).
-
1.0.101 [] 12/07/2026### Adicionado - **Férias com período**: datas de início/fim opcionais na resposta automática; o Sieve liga/desliga sozinho no intervalo (via `currentdate`). Sem datas, vale enquanto estiver ativada.
-
1.0.100 [] 12/07/2026### Adicionado - **Gerenciar pastas**: **renomear** pastas (preservando a hierarquia) e **esvaziar** Lixeira/Spam (na tela de Pastas e um atalho na própria pasta).
-
1.0.99 [] 12/07/2026### Adicionado - **Compor em HTML mais completo**: inserir **imagem no corpo** (embutida em base64), **tamanho de fonte** e citação em **HTML** (blockquote) ao responder ou encaminhar. Sanitização preserva formatação/imagens e neutraliza o resto.
-
1.0.98 [] 12/07/2026### Adicionado - **Reabrir/continuar rascunho**: clicar num item da pasta Rascunhos abre o compositor preenchido; ao enviar/salvar, o rascunho antigo é substituído. - **Anexar do Drive** no compositor: escolher arquivos do Drive e enviá-los como anexo (além do upload local).
-
1.0.97 [] 12/07/2026### Segurança - **Proteção contra brute-force no login do `/webmail20`**: bloqueio por IP+e-mail após tentativas seguidas (mesma política do painel), aplicado tanto na senha quanto na 2ª etapa (2FA).
-
1.0.96 [] 12/07/2026### Adicionado - **Senhas de aplicativo** na aba Conta (abre em modal): gerar/revogar senhas para clientes IMAP/SMTP (Outlook, Thunderbird, celular) sem afetar a senha principal.
-
1.0.95 [] 12/07/2026### Adicionado - **Meus dispositivos e conexões** na aba Conta (abre em modal): conexões ativas e histórico de acessos IMAP/SMTP dos últimos 14 dias, com IP e TLS.
-
1.0.94 [] 12/07/2026### Alterado - **Ativar/gerenciar 2FA** agora abre em **modal** na aba Conta (sem sair da tela); ativar/desativar acontece no próprio modal. - Configurações → Conta: desativado o preenchimento automático do navegador nos campos (nome e senha).
-
1.0.93 [] 12/07/2026### Adicionado - **Verificação em duas etapas (2FA) no webmail próprio**: o login em `/webmail20` passa a pedir o código do autenticador quando a conta tem 2FA (aceita código TOTP ou de recuperação); ativar/gerenciar pela aba Conta. ### Segurança - Contas com 2FA deixam de ser desviadas para o SOGo — agora autenticam no webmail miniMAIL com a segunda etapa obrigatória.
-
1.0.92 [] 12/07/2026### Adicionado - **Férias (resposta automática)** e **Encaminhamento** de mensagens em Configurações → Correio, aplicados via Sieve (`vacation` e `redirect`; o encaminhamento pode manter uma cópia local). - **Ordenar por tópicos**: agrupa a lista por conversa (assunto normalizado), com contador e expansão da conversa.
-
1.0.91 [] 12/07/2026### Adicionado - **Configurações → Correio**: **marcar como lida** (imediatamente / após N segundos / nunca), **bloquear imagens remotas** na leitura (com "Exibir imagens" por mensagem), **compor em HTML** (editor com formatação) e controle de **assinatura por contexto** (nova / resposta / encaminhamento). ### Segurança - Imagens externas na leitura são bloqueadas por padrão (privacidade); o src real fica em `data-mmsrc` até o usuário optar por exibir.
-
1.0.90 [] 12/07/2026### Adicionado - **Calendário — visão de Semana e Dia** (estilo Google Agenda): grade de horas com faixa configurável ("o dia começa/termina às"), eventos posicionados por horário e **lado a lado** quando se sobrepõem; alternador Mês/Semana/Dia. - **Ocultar fins de semana** na grade (Configurações → Calendário). - **Lembretes de evento**: escolha por evento (5 min a 1 dia antes) e **lembrete padrão** nas preferências; um cron (`minimail-reminders`, a cada minuto) envia o aviso por e-mail ao organizador e convidados. ### Alterado - Clicar numa hora/dia nas visões de semana/dia abre o novo evento no painel.
-
1.0.89 [] 12/07/2026### Adicionado - **Webmail miniMAIL (/webmail20) repaginado estilo Gmail**: avatares coloridos por remetente na lista, toolbar com paginação, painel de leitura com ações em ícones e respostas em destaque. - **Compor/Responder/Encaminhar em painel deslizante** (sem trocar de tela), envio por AJAX com aviso; compositor com Para/Cc/Cco. - **Configurações do webmail** ampliadas: aba **Exibição** (verificar novos e-mails, densidade, mensagens por página, **fuso horário**, **formato de hora 24h/12h**) e aba **Calendário** (início da semana). - **Assinatura com HTML e imagem** (editor rico; imagem embutida em base64); os e-mails com assinatura saem em multipart/alternative (texto + HTML). - **Calendário** estilo Google Agenda: clicar no dia abre o **novo evento em painel** (estilo compose) e é possível **convidar qualquer e-mail** (internos e externos), com convite enviado por e-mail. - Contatos e Chat harmonizados (avatares, listas e bolhas na identidade visual). ### Corrigido - Nomes de pastas do IMAP: fim do `." Junk`/`.Trash` (parser robusto) e das subpastas aparecendo como `INBOX.Nome` (mostra só a folha, indentada). - As pastas de e-mail somem da lateral ao abrir Calendário/Chat/Drive — agora ficam visíveis em todas as ferramentas. - A rolagem da caixa fica **dentro da lista** (sidebar/toolbar fixos); a barra de seleção múltipla fica **fixa** no topo da lista. - A conta de **auditoria** (oculta) não aparece mais no Chat nem nas sugestões de convidados do calendário.
-
1.0.88 [] 12/07/2026### Adicionado - **Webmail próprio do miniMAIL** (estilo Gmail, cores da marca): acessível em **`/webmail20`** com login por e-mail e senha. O SOGo **segue como padrão** — a interface própria só abre por essa entrada (`session['wm20']`), sem alterar a preferência de webmail da conta. Saída dedicada em `/mail/logout` (limpa só a sessão do webmail). Contas com 2FA usam o webmail padrão por ora.
-
1.0.87 [] 12/07/2026### Alterado - **Settings → Atualização:** a ação que roda `flask init-db` ganhou nome comercial — **"Aplicar atualização do sistema"** — e agora reflete o que faz: migrações do banco (aditivas) + tarefas agendadas (crons) + cache do painel. Timeout ampliado (300s) para bases com muitas contas; o "Atualizar agora" também usa essa etapa.
-
1.0.86 [] 12/07/2026### Alterado - **Desempenho:** o dashboard do super-admin passa a **ler um snapshot** pré-calculado pelo cron `minimail-dashboard` (a cada minuto) — o painel não roda mais subprocess (logs/rspamc/fail2ban/systemctl/versões). Carrega em ~10-90ms; o custo (~1s) fica no cron, fora da requisição. `flask init-db` gera o snapshot na hora; `get()` recomputa sozinho se o snapshot ficar velho (>180s).
-
1.0.85 [] 12/07/2026### Alterado - **Desempenho:** dashboard do super-admin muito mais rápido — as chamadas de subprocess (logs, rspamc, fail2ban, systemctl, versões) rodam em **paralelo** e com **cache de 60s**; o armazenamento vem de `used_bytes` (sem `du` em `/var/vmail`). ~3-4s → ~1s na 1ª carga e instantâneo nas recargas.
-
1.0.84 [] 12/07/2026### Corrigido - Cards **Armazenamento** e **Cota do domínio** mostravam 0 B após atualizar por `git pull` — o cron `minimail-usage` (que popula `used_bytes` via quota do Dovecot) não era instalado no update. Agora `flask init-db` **sincroniza os crons** em `/etc/cron.d` e **popula o uso na hora**.
-
1.0.81 [] 12/07/2026### Segurança - Separação **física** de sessão painel × webmail: cookies distintos (`mmadmin` para `/painel`,`/superadmin`,`/login`,`/logout`; `mmsession` para o resto). Uma sessão nunca autentica na área da outra. ### Alterado - Alternar entre painel e webmail não pede mais re-login. - Impersonação refeita via **token assinado** (`/mail/imp/<token>`): o admin segue logado no painel enquanto vê a caixa do usuário. ### Removido - Esquema antigo de sessão por `scope` (substituído pelos cookies separados). > Obs.: ao atualizar, todos são deslogados uma vez (o cookie mudou de nome).
-
1.0.80 [] 12/07/2026### Corrigido - Colisão de sessão painel × SOGo: sessão de e-mail no `/painel` dava 403 (agora vai ao login) e admin dedicado abrindo o Drive dava 500 (Drive ignora admin sem caixa).
-
1.0.79 [] 10/07/2026### Corrigido - Admin de domínio **sem domínio** atribuído quebrava o login (500) — agora mostra aviso; criar/editar exige ao menos um domínio. ### Alterado - Tela de login do painel passa a dizer "Usuário e senha".
-
1.0.78 [] 10/07/2026### Corrigido - "Abrir caixa" (impersonação) travava: `ImapStore` usava IMAP texto puro na porta 993 (TLS) → agora `IMAP4_SSL` + timeout; e o admin não fica mais preso em 403 no painel durante a impersonação.
-
1.0.77 [] 09/07/2026### Adicionado - Warm-up (aquecimento) de IP de saída: rampa de cap diário aplicada no rate-limit do rspamd, com cron diário e status na tela de IPs de saída.
-
1.0.76 [] 09/07/2026### Adicionado - Checagem **diária** de blocklist dos IPs de saída cadastrados (alerta por e-mail na mudança de estado) + seção na tela de Reputação.
-
1.0.75 [] 09/07/2026### Alterado - Rótulo do campo de usuário na tela de login do SOGo vira "E-mail".
-
1.0.74 [] 08/07/2026### Adicionado - Verificação de **AAAA** e **PTR IPv6** do host de e-mail (Testar DNS/placar) quando o servidor tem IPv6; nota de infra em "Ver DNS".
-
1.0.73 [] 08/07/2026### Corrigido - IPs de saída IPv6 passam a usar `smtp_bind_address6` no Postfix.
-
1.0.72 [] 08/07/2026### Docs - Estudo de alta disponibilidade e divisão de carga (`docs/`).
-
1.0.69 [–1.0.71] 08/07/2026### Adicionado - IPs de saída (super-admin): **dedicar por domínio** ou **balancear**; mostra PTR/rDNS e se o IP existe na interface; exibe os **registros DNS a criar** por IP e a instrução de como adicionar o IP à placa.
-
1.0.68 [] 08/07/2026### Alterado - O admin de domínio vê **"suporte"** no lugar de "super-admin" nos textos voltados a ele.
-
1.0.67 [] 08/07/2026### Docs - Estudo de object storage (MinIO/S3) e escala horizontal (`docs/`).
-
1.0.66 [] 07/07/2026### Removido - Listagem "Contas" do painel (contas são geridas dentro de cada domínio).
-
1.0.64 [–1.0.65] 07/07/2026### Adicionado - Contas de **administrador dedicadas** (usuário+senha, sem caixa), convivendo com o login por e-mail legado; gestão em `/painel/admins`; um admin de domínio pode administrar **vários domínios** (seletor de domínio ativo).
-
1.0.63 [] 07/07/2026### Segurança - Primeira separação de sessão e-mail × painel (via `scope`) — substituída pela separação física na 1.0.81.
-
1.0.62 [] 07/07/2026### Adicionado - Planos de domínio: limites reutilizáveis de nº de contas e cota total.
-
1.0.61 [] 07/07/2026### Alterado - Cota do domínio vira só-leitura para o admin de domínio.
-
1.0.59 [–1.0.60] 07/07/2026### Alterado - Manual do admin de domínio (Ajuda) mais didático, com exemplos (DNS, CSV, cliente de e-mail); removidas as seções de API/super-admin do manual.
-
1.0.58 [] 07/07/2026### Adicionado - Placar de entregabilidade checa reputação e alinhamento do hostname SNI.
-
1.0.57 [] 07/07/2026### Adicionado - Botão "Limpar fila" (purge total) em Fila — super-admin.
-
1.0.56 [] 07/07/2026### Corrigido - Fim do N+1 de `du` (usa `used_bytes` mantido por cron via quota do Dovecot; conserta a barra de cota e a cota do Drive); guarda de None na quarentena. ### Segurança - Proteção contra auto-lockout do admin de domínio; sanitização anti-fórmula no CSV exportado.
-
1.0.53 [–1.0.55] 07/07/2026### Segurança - Gate de acesso **fail-closed** (allowlist); rastrear-msg/webhooks/reputação restritos ao super-admin. ### Corrigido - UX e **modo dark** (tokens `--g-*`, bug de confirmação, fundos hardcoded). ### Removido - Opção "E-mail de recuperação" do hub do SOGo.
-
1.0.50 [–1.0.52] 07/07/2026### Adicionado - **Lixeira** do Drive (soft-delete/restaurar/expurgo); **vacation agendada** (cron `update-autoreply`); **anexar do Drive** no compositor do SOGo. ### Segurança - Endurecimentos do SSO e do branding do SOGo (2ª revisão).
-
1.0.43 [–1.0.48] — /08 06/07/2026### Adicionado - **Drive**: armazenamento por usuário (Fase 1), integração no SOGo (Fase 2), compartilhamento por link e com membros (Fase 3); progresso de upload, respeito à cota da conta e toggle por domínio.
-
1.0.27 [–1.0.42] 06/07/2026### Adicionado - **SSO SOGo→Flask**: elimina o duplo login no hub de autoatendimento. - SNI: cert do Dovecot (Thunderbird), webmail por host, CNAME no "Ver DNS". ### Alterado - Branding do SOGo com a paleta da marca (chumbo #3a3f48, logo azul); DKIM em modal com confirmação; backup do banco do SOGo, alarmes por e-mail e headers de segurança. ### Corrigido - Legibilidade no login do SOGo (autofill, ícones, onda de boas-vindas); modo dark do menu lateral; alinhamento de cards; "clicar 2 vezes". --- _Histórico anterior à 1.0.27 (rastreamento de mensagem, placar de entregabilidade, onboarding, webhooks, arquivo pesquisável, hardening geral, migração IMAP/Zimbra/cPanel, etc.): ver `git log`._