release: 1.0.0, a changelog, one .env.example and the docs to match
CI / Lint de idioma (R9) (push) Successful in 5s
CI / Go (protocols) (push) Successful in 2m34s
CI / TypeScript (storage) (push) Successful in 15s
CI / Testes (webmail) (push) Successful in 51s
CI / TypeScript (auth) (push) Successful in 2m39s
CI / Trivy (dependências, segredos e Dockerfiles) (push) Successful in 9s
CI / Imagem (migrations) (push) Successful in 5s
CI / Stack completa (push) Successful in 1m11s
CI / Imagem (auth) (push) Successful in 17s
CI / Imagem (protocols) (push) Successful in 7s
CI / Imagem (rspamd) (push) Successful in 5s
CI / Imagem (storage) (push) Successful in 17s
CI / Imagem (webmail) (push) Successful in 1m52s

- CHANGELOG.md and CHANGELOG.pt-BR.md, from the project history.
- .env.example now serves development and production; scripts/init-env.sh
  (make env) creates .env with random secrets. .env.production.example is gone.
- docs/RELEASING.md: the pipeline, how a version is cut and deployed, the runners.
- DEPLOYMENT.md and both READMEs updated for Gitea and the new env flow.
- Service versions set to 1.0.0.
- CI: the proxy network step is idempotent and the stack is torn down always,
  since the runner's Docker daemon now outlives a job.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-30 11:36:22 -03:00
1 parent f500534ac6
commit 5bbbb85128
17 files changed
+523 -181

No files matched your search

+95 -44
View File
@@ -1,81 +1,132 @@
# --- Container images --- # LambdaMail - environment
# Published to the Gitea registry by .gitea/workflows/release.yml, multi-architecture #
# (linux/amd64 and linux/arm64). The deploy host pulls these instead of # One file for local development and for production.
# compiling: `docker compose pull && docker compose up -d`. Running #
# `docker compose build` locally still overrides them with a local build. # scripts/init-env.sh copy this to .env and generate the secrets
# Pin IMAGE_TAG to a released version (e.g. 1.2.3) or a sha- tag to roll back. # scripts/init-env.sh --production the same, plus a list of what you still
# have to set before this is a real server
#
# Values below are the local-development defaults. Lines marked PRODUCTION say
# what a real deployment needs. In Coolify, paste the finished .env into the
# environment editor instead of keeping a file on the host.
#
# The secrets are left empty on purpose. The script fills them with random
# values, and compose refuses to start while one is empty, because a default
# secret in a published repository is not a secret.
# --- Container images -------------------------------------------------------
# Published to the Gitea registry by .gitea/workflows/release.yml for
# linux/amd64 and linux/arm64. The deploy host pulls instead of compiling:
# `docker compose pull && docker compose up -d`. `make up` builds from source
# through the build overlay and ignores these.
# PRODUCTION: pin IMAGE_TAG to a release (1.0.0) or to a sha-<commit> tag. Never
# leave `latest` on a server: a redeploy then silently changes what runs.
IMAGE_NAMESPACE=git.lucasrguerra.dev.br/lucasrguerra/lambdamail IMAGE_NAMESPACE=git.lucasrguerra.dev.br/lucasrguerra/lambdamail
IMAGE_TAG=latest IMAGE_TAG=latest
# --- Postgres --- # --- Identity ---------------------------------------------------------------
POSTGRES_DB=lambdamail # MAIL_DOMAIN is the domain you send and receive as.
POSTGRES_USER=lambdamail # PRIMARY_MAIL_HOST is the host the MX record points at; its PTR must match it.
POSTGRES_PASSWORD=change-me-in-local-dev # PRODUCTION: MAIL_DOMAIN=example.com PRIMARY_MAIL_HOST=mail.example.com
# WEBMAIL_HOST=mail.example.com PUBLIC_IPV4=<the VPS public IPv4>
# --- Redis ---
REDIS_PASSWORD=change-me-in-local-dev
# --- Shared secrets (local dev values — never reuse in production) ---
JWT_SECRET=local-dev-jwt-secret-change-me-at-least-32-chars
# Encrypts DKIM private keys and provider tokens at rest. Losing it makes every
# stored secret unrecoverable — back it up separately from the database.
LAMBDAMAIL_MASTER_KEY=local-dev-master-key-32-bytes-minimum-value
# --- Host identity ---
PRIMARY_MAIL_HOST=mail.localhost
MAIL_DOMAIN=localhost MAIL_DOMAIN=localhost
PRIMARY_MAIL_HOST=mail.localhost
WEBMAIL_HOST=localhost:3000
# Optional separate host for /admin.
ADMIN_HOST=
PUBLIC_IPV4=127.0.0.1 PUBLIC_IPV4=127.0.0.1
# Leave empty unless IPv6 has a matching PTR and is covered by SPF. A half
# configured IPv6 is a common cause of rejection (PLAN.md premises).
PUBLIC_IPV6= PUBLIC_IPV6=
# --- DNS automation (Cloudflare) --- # --- Secrets ----------------------------------------------------------------
# Filled in by scripts/init-env.sh. To do it by hand:
#
# POSTGRES_PASSWORD, REDIS_PASSWORD openssl rand -hex 24
# JWT_SECRET, LAMBDAMAIL_MASTER_KEY openssl rand -hex 32
#
# The first two are substituted into connection URIs (postgres://user:PASS@db/...
# and redis://:PASS@redis:6379), so they must hold no character that means
# something in a URL: use hex, NOT `openssl rand -base64`, whose alphabet has "/"
# and "+". A "/" ends the authority component, the host is parsed as part of the
# password and every connection fails authentication - which shows up as the
# migrations container exiting 1 while Postgres itself reports healthy.
POSTGRES_DB=lambdamail
POSTGRES_USER=lambdamail
POSTGRES_PASSWORD=
REDIS_PASSWORD=
# Signs webmail and admin sessions. At least 32 characters; the auth service
# refuses to start below that.
JWT_SECRET=
# Encrypts DKIM private keys, TOTP secrets and provider tokens at rest.
# LOSING THIS MAKES EVERY STORED SECRET UNRECOVERABLE: back it up separately
# from the database (PLAN.md risk R9).
LAMBDAMAIL_MASTER_KEY=
# --- DNS automation (Cloudflare) --------------------------------------------
# The token needs Zone.DNS:Edit plus Zone.Zone:Read on the zone. Leave it empty # The token needs Zone.DNS:Edit plus Zone.Zone:Read on the zone. Leave it empty
# to disable reconciliation; the service then logs that DNS sync is off. # to manage the records by hand; the service then logs that DNS sync is off.
CLOUDFLARE_API_TOKEN= CLOUDFLARE_API_TOKEN=
DNS_SYNC_INTERVAL_SECONDS=21600 DNS_SYNC_INTERVAL_SECONDS=21600
# --- TLS --- # --- TLS --------------------------------------------------------------------
# traefik: read the certificate Traefik manages (default, DANE not possible). # traefik: read the certificate the proxy already manages (default; DANE is not
# acme: obtain the certificate ourselves via DNS-01. Required for DANE, # possible in this mode).
# because only this mode can reuse a pre-generated key. # acme: obtain the certificate here via DNS-01. Required for DANE, because
# only this mode can reuse a pre-generated key.
TLS_MODE=traefik TLS_MODE=traefik
# Used only when TLS_MODE=acme. Leave ACME_DIRECTORY_URL empty for Let's # Used only when TLS_MODE=acme. Leave ACME_DIRECTORY_URL empty for Let's Encrypt
# Encrypt production; point it at the staging directory while testing, since # production; point it at the staging directory while testing, since production
# production rate limits can lock a domain out for a week. # rate limits can lock a domain out for a week.
ACME_EMAIL= ACME_EMAIL=
ACME_DIRECTORY_URL= ACME_DIRECTORY_URL=
# Where the proxy keeps its certificates, and the Docker network it listens on.
# PRODUCTION (Coolify): COOLIFY_PROXY_DIR=/data/coolify/proxy
COOLIFY_PROXY_DIR=./local/acme COOLIFY_PROXY_DIR=./local/acme
COOLIFY_PROXY_NETWORK=coolify
TRAEFIK_ACME_FILENAME=acme.json TRAEFIK_ACME_FILENAME=acme.json
CERT_POLL_INTERVAL_SECONDS=60 CERT_POLL_INTERVAL_SECONDS=60
# Static certificate files, used when no ACME store is available (local dev). # Static certificate files, used when no ACME store is available (local dev;
# `make gen-dev-cert` writes them).
PROTOCOLS_CERT_PATH=/etc/lambdamail/certs/cert.pem PROTOCOLS_CERT_PATH=/etc/lambdamail/certs/cert.pem
PROTOCOLS_KEY_PATH=/etc/lambdamail/certs/key.pem PROTOCOLS_KEY_PATH=/etc/lambdamail/certs/key.pem
PROTOCOLS_SPOOL_DIR=/var/lambdamail/spool PROTOCOLS_SPOOL_DIR=/var/lambdamail/spool
# --- Transport security policy --- # --- Transport security -----------------------------------------------------
# Start at "testing": senders honour the policy but do not hard-fail on it. # Start at "testing": senders honour the policy but do not hard-fail on it.
# Promote to "enforce" only once TLS-RPT reports come back clean. # Promote to "enforce" only once TLS-RPT reports come back clean; enforce with a
# broken certificate silently blackholes inbound mail.
MTA_STS_MODE=testing MTA_STS_MODE=testing
OUTBOUND_MTA_STS_ENABLED=true OUTBOUND_MTA_STS_ENABLED=true
# DANE stays off unless TLS_MODE=acme. Under Traefik-managed certificates the # DANE stays off unless TLS_MODE=acme. Under proxy-managed certificates the key
# key changes at every renewal and the published TLSA record stops matching, # changes at every renewal and the published TLSA record stops matching, which
# which permanently rejects mail at every validating MTA. # permanently rejects mail at every validating MTA (PLAN.md section 5.1).
OUTBOUND_DANE_ENABLED=false OUTBOUND_DANE_ENABLED=false
DNSSEC_RESOLVER=1.1.1.1:53 DNSSEC_RESOLVER=1.1.1.1:53
MAX_MESSAGE_BYTES=52428800 MAX_MESSAGE_BYTES=52428800
# ARC seals accepted messages so a downstream receiver can still tell that the # ARC seals accepted messages so a downstream receiver can still tell that the
# mail authenticated at the first hop. Only worth enabling on a host that # mail authenticated at the first hop. Only worth enabling on a host that
# forwards mail onward. # forwards mail onward.
ARC_SEAL_ENABLED=false ARC_SEAL_ENABLED=false
# --- Smarthost (for hosts where outbound port 25 is blocked) --- # --- Smarthost (hosts where outbound port 25 is blocked) --------------------
# Setting RELAY_HOST routes all delivery through the relay and adds it to the # Most VPS providers block port 25. If `make preflight` reports it blocked, set a
# published SPF record, without which every relayed message fails SPF. # relay here: all delivery goes through it and it is added to the published SPF,
# without which every relayed message fails SPF.
RELAY_HOST= RELAY_HOST=
RELAY_PORT=587 RELAY_PORT=587
RELAY_USER= RELAY_USER=
RELAY_PASS= RELAY_PASS=
# The SPF term that authorises the relay, exactly as the provider documents it.
# It cannot be derived from RELAY_HOST, and getting it wrong is worse than
# leaving it out. Brevo's relay is smtp-relay.brevo.com, but brevo.com publishes
# the SPF of their corporate mail: the sending ranges are at spf.brevo.com, so
# the value there is "include:spf.brevo.com".
RELAY_SPF_INCLUDE=
# --- Webmail --- # --- Storage ----------------------------------------------------------------
WEBMAIL_HOST=localhost:3000 STORAGE_DRIVER=local
ADMIN_HOST= S3_ENDPOINT=
S3_BUCKET=
S3_ACCESS_KEY=
S3_SECRET_KEY=
-104
View File
@@ -1,104 +0,0 @@
# LambdaMail - production environment
#
# Copy this into Coolify's environment editor, or to .env on the host, and
# replace every value marked CHANGE. The stack refuses to start without the
# required ones rather than falling back to a default, because a default
# secret in a published repository is not a secret.
#
# Generate each secret with: openssl rand -base64 36
# except POSTGRES_PASSWORD and REDIS_PASSWORD, which go inside connection URIs
# and must be URL-safe - see the note above them.
# --- Container images -------------------------------------------------------
# Published by .gitea/workflows/release.yml for linux/amd64 and linux/arm64.
# Pin IMAGE_TAG to a released version or a sha- tag to roll back.
IMAGE_NAMESPACE=git.lucasrguerra.dev.br/lucasrguerra/lambdamail
IMAGE_TAG=latest
# --- Identity ---------------------------------------------------------------
# MAIL_DOMAIN is the domain you send and receive as.
# PRIMARY_MAIL_HOST is the host the MX record points at; its PTR must match.
MAIL_DOMAIN=example.com # CHANGE
PRIMARY_MAIL_HOST=mail.example.com # CHANGE
WEBMAIL_HOST=mail.example.com # CHANGE - where the web UI is served
ADMIN_HOST= # optional separate host for /admin
PUBLIC_IPV4=203.0.113.10 # CHANGE - the VPS public IPv4
# Leave empty unless IPv6 has a matching PTR and is covered by SPF. Half
# configured IPv6 is a common cause of rejection (PLAN.md premises).
PUBLIC_IPV6=
# --- Secrets ----------------------------------------------------------------
# These two are substituted into connection URIs - postgres://user:PASS@db/... and
# redis://:PASS@redis:6379 - so they must contain no character that means
# something in a URL. Use hex:
#
# openssl rand -hex 24
#
# NOT `openssl rand -base64`, whose alphabet includes "/" and "+". A "/" ends
# the authority component, so the host is parsed as part of the password and
# every connection fails authentication - which surfaces as the migrations
# container exiting 1 while Postgres itself reports healthy.
POSTGRES_DB=lambdamail
POSTGRES_USER=lambdamail
POSTGRES_PASSWORD=CHANGE-generate-with-openssl-rand-hex-24
REDIS_PASSWORD=CHANGE-generate-with-openssl-rand-hex-24
# Signs webmail and admin sessions. Minimum 32 characters; the auth service
# refuses to start below that.
JWT_SECRET=CHANGE-generate-with-openssl-rand-at-least-32-chars
# Encrypts DKIM private keys, TOTP secrets and provider tokens at rest.
# LOSING THIS MAKES EVERY STORED SECRET UNRECOVERABLE - back it up separately
# from the database (PLAN.md risk R9).
LAMBDAMAIL_MASTER_KEY=CHANGE-generate-with-openssl-rand-at-least-32-chars
# --- DNS automation ---------------------------------------------------------
# Token scope: Zone.DNS:Edit plus Zone.Zone:Read on the zone. Leave empty to
# manage the 13 records by hand; the service then logs that sync is off.
CLOUDFLARE_API_TOKEN=
DNS_SYNC_INTERVAL_SECONDS=21600
# --- TLS --------------------------------------------------------------------
# traefik: read the certificate Coolify's proxy already manages (default).
# acme: obtain it here via DNS-01. Required for DANE, and only then.
TLS_MODE=traefik
COOLIFY_PROXY_DIR=/data/coolify/proxy
COOLIFY_PROXY_NETWORK=coolify
TRAEFIK_ACME_FILENAME=acme.json
CERT_POLL_INTERVAL_SECONDS=60
PROTOCOLS_SPOOL_DIR=/var/lambdamail/spool
# Used only when TLS_MODE=acme.
ACME_EMAIL=
ACME_DIRECTORY_URL=
# --- Transport security -----------------------------------------------------
# Start at testing. Promote to enforce only once TLS-RPT reports come back
# clean: enforce with a broken certificate silently blackholes inbound mail.
MTA_STS_MODE=testing
OUTBOUND_MTA_STS_ENABLED=true
# DANE stays off under TLS_MODE=traefik. The key changes at every renewal and
# the published TLSA record stops matching, which permanently rejects mail at
# every validating MTA (PLAN.md section 5.1).
OUTBOUND_DANE_ENABLED=false
DNSSEC_RESOLVER=1.1.1.1:53
MAX_MESSAGE_BYTES=52428800
ARC_SEAL_ENABLED=false
# --- Smarthost --------------------------------------------------------------
# Most VPS providers block outbound port 25. If the preflight reports it
# blocked, set a relay here; it is added to the published SPF automatically.
RELAY_HOST=
RELAY_PORT=587
RELAY_USER=
RELAY_PASS=
# The SPF term that authorises the relay, exactly as your provider documents
# it. Set this: it cannot be derived from RELAY_HOST, and getting it wrong is
# worse than leaving it out. Brevo's relay is smtp-relay.brevo.com, but
# brevo.com publishes the SPF of their corporate mail - the sending ranges are
# at spf.brevo.com, so the value here is "include:spf.brevo.com".
RELAY_SPF_INCLUDE=
# --- Storage ----------------------------------------------------------------
STORAGE_DRIVER=local
S3_ENDPOINT=
S3_BUCKET=
S3_ACCESS_KEY=
S3_SECRET_KEY=
+12 -3
View File
@@ -269,14 +269,23 @@ jobs:
needs: [go-protocols, ts-auth, ts-storage, webmail-build] needs: [go-protocols, ts-auth, ts-storage, webmail-build]
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- run: cp .env.example .env - run: scripts/init-env.sh
# The proxy network is declared external because Coolify owns it on the # The proxy network is declared external because Coolify owns it on the
# deploy host. Nothing creates it here, and without it compose refuses to # deploy host. Nothing creates it here, and without it compose refuses to
# start the stack at all. # start the stack at all.
#
# Idempotent on purpose: the runner's Docker daemon outlives the job, so
# the network may already exist from an earlier run.
- name: Cria a rede do proxy - name: Cria a rede do proxy
run: docker network create "${COOLIFY_PROXY_NETWORK:-coolify}" run: |
net="${COOLIFY_PROXY_NETWORK:-coolify}"
docker network inspect "$net" >/dev/null 2>&1 || docker network create "$net"
# The build overlay is explicit here: CI has to exercise the code in the # The build overlay is explicit here: CI has to exercise the code in the
# branch, not the images already published from main. # branch, not the images already published from main.
- run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml --env-file .env up --build --wait - run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml --env-file .env up --build --wait
- run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml ps - run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml ps
- run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml down -v # Always, or a failed `up` leaves containers and volumes behind in the
# daemon the next run starts from.
- name: Derruba a stack
if: always()
run: docker compose -f docker-compose.yaml -f docker-compose.build.yaml down -v
+105
View File
@@ -0,0 +1,105 @@
# Changelog
All notable changes to LambdaMail are listed here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows
[Semantic Versioning](https://semver.org/).
[Português](CHANGELOG.pt-BR.md)
## [Unreleased]
## [1.0.0] - 2026-10-30
The first tagged release. Earlier work was never tagged, so this entry sums up
everything from the first commit on 2026-07-23 to the move to Gitea. Gaps that
remain are listed in the README under
[What is not finished](README.md#what-is-not-finished).
### Mail server
- SMTP MX and Submission (587 STARTTLS, 465 implicit TLS), IMAP with UID, flags,
IDLE, CONDSTORE, `SEARCH`, `EXPUNGE`, `COPY` and `MOVE`, POP3 and ManageSieve.
- SPF, DKIM (Ed25519 and RSA dual signature), DMARC, ARC sealing, MTA-STS and
DANE on delivery, and TLS-RPT report ingestion.
- Rspamd scoring with greylisting, RBLs and rate limiting, and ClamAV.
- A durable outbound queue in Postgres with the retry ladder and RFC 3464
bounces, a smarthost fallback for hosts with port 25 blocked, and a
configurable SPF term for the relay (`RELAY_SPF_INCLUDE`).
- Mail addressed to this server is delivered locally instead of going out to the
internet and back.
- Every submitted message is kept in the sender's Sent folder; app passwords
sign in over SMTP, IMAP and POP3.
- Sieve rules evaluated at delivery, custom folders, and an RFC 5230 vacation
responder that replies in thread and once per sender per week.
### Webmail and console
- A complete redesign of both surfaces (the "Nocturne" interface): dark and light
themes, a command menu on `Ctrl K` / `Cmd K`, `c` and `/` shortcuts, an
installable app with its own icon, and a drawn logo.
- Conversations in cascade, with who wrote, how many, and whether you have
answered; reply, reply all, important mail, undo send, and drafts that replace
the previous autosave.
- A reader that suits both themes: plain messages take the app's colours,
designed ones keep their white sheet, quoted history folds away, remote images
are blocked until allowed.
- Trusted senders, kept on the server per mailbox: their mail skips spam and
always shows images.
- Webmail sessions of 30 days that renew while used, several accounts signed in
at once and switchable from the sidebar; the console stays at 8 hours.
- Profile photos with a circular crop editor, and per-domain BIMI logo hosting
and validation, published to DNS and kept in step with the logo.
- Admin console with server-side paging and filtering, user editing (role,
quota, language, password, second factor), aliases, a diagnostics page, and
isolation between the user and admin surfaces. Domain admins are confined to
their own domain.
- English, Portuguese and Spanish, with a test that every string exists
in all of them.
### DNS and TLS
- Cloudflare reconciliation of the expected records for every active domain,
idempotent, with per-record verification against public resolvers; a DKIM
record is never published with an empty key.
- A TLS panel that states the digest the TLSA record must carry, and documented
steps for turning DANE on. `TLS_MODE=traefik` (default) or `acme`.
### Security
- Two-factor sign-in with TOTP and recovery codes; enrolment can no longer
replace a secret the authenticator already has, enable MFA without recovery
codes, or kill the auth service. `make reset-mfa` recovers a broken enrolment.
- Argon2 concurrency is bounded, so a login flood cannot take the auth service
down.
- All service containers run as non-root; images are scanned with Trivy and the
build fails on HIGH or CRITICAL findings.
- Dependencies updated for two published advisories: `next` 15.5.28
(unauthenticated remote code execution in image optimisation,
GHSA-2464-fhr4-w9xw) and `go-jose` 4.1.6 (denial of service).
### Deployment and CI
- The deploy host pulls images that CI built and tested; nothing is compiled on
the server. The compose file pins Traefik to the proxy network by name.
- **Moved to Gitea.** The source, the workflows (`.gitea/workflows`) and the
container images now live on `git.lucasrguerra.dev.br`; the images are
published for `linux/amd64` and `linux/arm64` under
`git.lucasrguerra.dev.br/lucasrguerra/lambdamail/<service>` and the `v*` tag
creates a release with notes. See [docs/RELEASING.md](docs/RELEASING.md).
- CI caches npm, Go modules, the embedded Postgres, Trivy's database and image
layers.
- One `.env.example` for development and production, and `scripts/init-env.sh`
to create `.env` with random secrets (`make env`). It replaces
`.env.production.example`.
### Fixed
- Webmail healthcheck: Next binds IPv4 only and `wget` resolved `localhost` to
`::1`.
- The admin lockout, a draft that did not discard, folder counters, a vacation
reply that was not a reply, DNS panels that reported published records as
missing, and the reconcile button that created nothing.
- Migrations: a default partition for `email_messages`.
[Unreleased]: https://git.lucasrguerra.dev.br/lucasrguerra/LambdaMail/compare/v1.0.0...HEAD
[1.0.0]: https://git.lucasrguerra.dev.br/lucasrguerra/LambdaMail/releases/tag/v1.0.0
+106
View File
@@ -0,0 +1,106 @@
# Registro de mudanças
Tudo o que muda de relevante no LambdaMail está aqui. O formato segue o
[Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/) e o projeto segue o
[Versionamento Semântico](https://semver.org/lang/pt-BR/).
[English](CHANGELOG.md)
## [Não lançado]
## [1.0.0] - 2026-10-30
A primeira versão com tag. O trabalho anterior nunca foi tagueado, então esta
entrada resume tudo desde o primeiro commit, em 2026-07-23, até a mudança para o
Gitea. O que ainda falta está no README, em
[O que não está pronto](README.pt-BR.md#o-que-não-está-pronto).
### Servidor de e-mail
- SMTP MX e Submission (587 STARTTLS, 465 TLS implícito), IMAP com UID, flags,
IDLE, CONDSTORE, `SEARCH`, `EXPUNGE`, `COPY` e `MOVE`, POP3 e ManageSieve.
- SPF, DKIM (assinatura dupla Ed25519 e RSA), DMARC, selo ARC, MTA-STS e DANE na
entrega, e ingestão de relatórios TLS-RPT.
- Pontuação do Rspamd com greylisting, RBLs e limite de taxa, e ClamAV.
- Fila de saída durável no Postgres, com a escada de tentativas e devoluções RFC
3464, retransmissão por smarthost para hosts com a porta 25 bloqueada, e termo
SPF do relay configurável (`RELAY_SPF_INCLUDE`).
- O e-mail endereçado a este servidor é entregue localmente, sem sair para a
internet e voltar.
- Toda mensagem enviada fica na pasta Enviados de quem enviou; as senhas de
aplicativo funcionam em SMTP, IMAP e POP3.
- Regras Sieve avaliadas na entrega, pastas próprias e resposta de ausência RFC
5230, que responde dentro da conversa e uma vez por remetente por semana.
### Webmail e console
- Redesenho completo das duas interfaces ("Nocturne"): temas escuro e claro,
menu de comandos em `Ctrl K` / `Cmd K`, atalhos `c` e `/`, app instalável com
ícone próprio e um logo desenhado.
- Conversas em cascata, com quem escreveu, quantas mensagens e se você já
respondeu; responder, responder a todos, importantes, desfazer o envio, e
rascunhos que substituem o salvamento automático anterior.
- Um leitor que combina com os dois temas: mensagens simples usam as cores do
app, as desenhadas mantêm a folha branca, o histórico citado se recolhe e as
imagens remotas ficam bloqueadas até serem liberadas.
- Remetentes confiáveis, guardados no servidor por caixa: o e-mail deles não vai
para o spam e sempre mostra as imagens.
- Sessões do webmail de 30 dias que se renovam com o uso, várias contas abertas
ao mesmo tempo e trocáveis pela barra lateral; o console continua em 8 horas.
- Foto de perfil com editor de recorte circular, e hospedagem e validação do
logotipo BIMI por domínio, publicado no DNS e mantido em dia com o logo.
- Console de administração com paginação e filtros no servidor, edição de
usuários (papel, cota, idioma, senha, segundo fator), apelidos, uma página de
diagnóstico e isolamento entre as superfícies de usuário e de administração.
Administradores de domínio ficam restritos ao próprio domínio.
- Inglês, português e espanhol, com um teste de que toda string existe nos três.
### DNS e TLS
- Reconciliação com a Cloudflare dos registros esperados de cada domínio ativo,
idempotente, com verificação de cada registro em resolvedores públicos; um
registro DKIM nunca é publicado com a chave vazia.
- Um painel de TLS que mostra o digest que o registro TLSA precisa carregar, e
passos documentados para ligar o DANE. `TLS_MODE=traefik` (padrão) ou `acme`.
### Segurança
- Login em duas etapas com TOTP e códigos de recuperação; o cadastro não
substitui mais um segredo que o autenticador já tem, não ativa o MFA sem
códigos de recuperação e não derruba o serviço de autenticação. O
`make reset-mfa` recupera um cadastro quebrado.
- A concorrência do Argon2 é limitada, então uma enxurrada de logins não derruba
o serviço de autenticação.
- Todos os containers de serviço rodam sem root; as imagens passam pelo Trivy e
o build falha em achados HIGH ou CRITICAL.
- Dependências atualizadas por dois avisos publicados: `next` 15.5.28 (execução
remota de código sem autenticação na otimização de imagens,
GHSA-2464-fhr4-w9xw) e `go-jose` 4.1.6 (negação de serviço).
### Implantação e CI
- O servidor de produção só puxa imagens que o CI construiu e testou; nada é
compilado nele. O compose fixa o Traefik na rede do proxy pelo nome.
- **Mudança para o Gitea.** O código, os workflows (`.gitea/workflows`) e as
imagens de container agora ficam em `git.lucasrguerra.dev.br`; as imagens são
publicadas para `linux/amd64` e `linux/arm64` em
`git.lucasrguerra.dev.br/lucasrguerra/lambdamail/<serviço>`, e a tag `v*` cria
uma release com notas. Veja [docs/RELEASING.md](docs/RELEASING.md).
- O CI guarda em cache o npm, os módulos Go, o Postgres embutido, o banco do
Trivy e as camadas das imagens.
- Um único `.env.example` para desenvolvimento e produção, e o
`scripts/init-env.sh` para criar o `.env` com segredos aleatórios
(`make env`). Ele substitui o `.env.production.example`.
### Corrigido
- Healthcheck do webmail: o Next escuta só em IPv4 e o `wget` resolvia
`localhost` para `::1`.
- O bloqueio do administrador, o rascunho que não descartava, os contadores de
pastas, uma resposta de ausência que não era resposta, painéis de DNS que
diziam faltar registros já publicados e o botão de reconciliar que não criava
nada.
- Migrações: partição padrão para `email_messages`.
[Não lançado]: https://git.lucasrguerra.dev.br/lucasrguerra/LambdaMail/compare/v1.0.0...HEAD
[1.0.0]: https://git.lucasrguerra.dev.br/lucasrguerra/LambdaMail/releases/tag/v1.0.0
+5 -1
View File
@@ -1,4 +1,8 @@
.PHONY: up down logs test test-db-up test-db-down lint migrate-up migrate-down gen-dev-cert seed preflight create-admin reset-password reset-mfa .PHONY: env up down logs test test-db-up test-db-down lint migrate-up migrate-down gen-dev-cert seed preflight create-admin reset-password reset-mfa
# .env from .env.example with fresh secrets; refuses to overwrite an existing one.
env:
scripts/init-env.sh
# Local development builds from source; production pulls what CI published. # Local development builds from source; production pulls what CI published.
up: up:
+8 -6
View File
@@ -2,7 +2,7 @@
<h1 align="center">LambdaMail</h1> <h1 align="center">LambdaMail</h1>
<p align="center">Your own mail server, with a webmail that is a pleasure to use.<br>SMTP, IMAP and POP3 · SPF, DKIM, DMARC and ARC · Webmail and admin console · One <code>docker-compose.yaml</code>, made for Coolify.</p> <p align="center">Your own mail server, with a webmail that is a pleasure to use.<br>SMTP, IMAP and POP3 · SPF, DKIM, DMARC and ARC · Webmail and admin console · One <code>docker-compose.yaml</code>, made for Coolify.</p>
<p align="center"><a href="docs/WEBMAIL.md">Webmail</a> · <a href="docs/DEPLOYMENT.md">Deploying</a> · <a href="docs/OPERATIONS.md">Operating</a> · <a href="docs/MAILBOX-CORE.md">Mailbox core</a> · <a href="LICENSE">License</a></p> <p align="center"><a href="docs/WEBMAIL.md">Webmail</a> · <a href="docs/DEPLOYMENT.md">Deploying</a> · <a href="docs/OPERATIONS.md">Operating</a> · <a href="docs/MAILBOX-CORE.md">Mailbox core</a> · <a href="docs/RELEASING.md">Releasing</a> · <a href="CHANGELOG.md">Changelog</a> · <a href="LICENSE">License</a></p>
<p align="center"><b>English</b> · <a href="README.pt-BR.md">Português</a></p> <p align="center"><b>English</b> · <a href="README.pt-BR.md">Português</a></p>
@@ -18,7 +18,7 @@ SMTP, IMAP, POP3 and ManageSieve, with SPF, DKIM, DMARC, ARC, MTA-STS and
TLS-RPT, DNS automation through Cloudflare, and spam and virus filtering. No TLS-RPT, DNS automation through Cloudflare, and spam and virus filtering. No
paid tiers and no artificial limits on domains or mailboxes. paid tiers and no artificial limits on domains or mailboxes.
> **Status: pre-release.** The mail path, the security posture and the web > **Version 1.0.0** ([changelog](CHANGELOG.md)). The mail path, the security posture and the web
> interfaces work end to end, and there is an automated test suite behind them. > interfaces work end to end, and there is an automated test suite behind them.
> See [What is not finished](#what-is-not-finished) before putting real mail > See [What is not finished](#what-is-not-finished) before putting real mail
> through it. > through it.
@@ -107,7 +107,7 @@ getting port 25 unblocked — and the order the rest has to happen in.
The short version: The short version:
```bash ```bash
cp .env.production.example .env # then replace every CHANGE scripts/init-env.sh --production # .env with random secrets; lists what to set
make preflight # checks port 25, PTR, RBLs, token scope make preflight # checks port 25, PTR, RBLs, token scope
docker compose up -d docker compose up -d
make create-admin EMAIL=you@example.com PASSWORD='...' make create-admin EMAIL=you@example.com PASSWORD='...'
@@ -166,7 +166,7 @@ more thing to keep patched.
## Running locally ## Running locally
```bash ```bash
cp .env.example .env make env # .env from .env.example, with random secrets
make gen-dev-cert # self-signed certificate for the listeners make gen-dev-cert # self-signed certificate for the listeners
make up # builds from source via the build overlay make up # builds from source via the build overlay
make seed # a postmaster@example.test fixture make seed # a postmaster@example.test fixture
@@ -204,8 +204,8 @@ checked in a real browser; see
## Configuration ## Configuration
Every variable is documented inline in Every variable is documented inline in
[`.env.production.example`](.env.production.example). The ones without a safe [`.env.example`](.env.example), the one file for development and production.
default: The ones without a safe default (`scripts/init-env.sh` generates the secrets):
| Variable | | | Variable | |
| :--- | :--- | | :--- | :--- |
@@ -383,6 +383,8 @@ Stated plainly, because a mail server that half works loses mail:
the first. the first.
- Tests come with the change that needs them; the suites above must stay green. - Tests come with the change that needs them; the suites above must stay green.
- Commits reference their issue: `#N: what changed and why`. - Commits reference their issue: `#N: what changed and why`.
- A change someone would notice goes under *Unreleased* in [CHANGELOG.md](CHANGELOG.md)
(and its Portuguese twin). How a version is cut: [docs/RELEASING.md](docs/RELEASING.md).
## License ## License
+8 -5
View File
@@ -2,7 +2,7 @@
<h1 align="center">LambdaMail</h1> <h1 align="center">LambdaMail</h1>
<p align="center">Seu próprio servidor de e-mail, com um webmail que dá gosto de usar.<br>SMTP, IMAP e POP3 · SPF, DKIM, DMARC e ARC · Webmail e console de administração · Um único <code>docker-compose.yaml</code>, feito para o Coolify.</p> <p align="center">Seu próprio servidor de e-mail, com um webmail que dá gosto de usar.<br>SMTP, IMAP e POP3 · SPF, DKIM, DMARC e ARC · Webmail e console de administração · Um único <code>docker-compose.yaml</code>, feito para o Coolify.</p>
<p align="center"><a href="docs/WEBMAIL.md">Webmail</a> · <a href="docs/DEPLOYMENT.md">Implantação</a> · <a href="docs/OPERATIONS.md">Operação</a> · <a href="docs/MAILBOX-CORE.md">Núcleo da caixa</a> · <a href="LICENSE">Licença</a></p> <p align="center"><a href="docs/WEBMAIL.md">Webmail</a> · <a href="docs/DEPLOYMENT.md">Implantação</a> · <a href="docs/OPERATIONS.md">Operação</a> · <a href="docs/MAILBOX-CORE.md">Núcleo da caixa</a> · <a href="docs/RELEASING.md">Lançamentos</a> · <a href="CHANGELOG.pt-BR.md">Mudanças</a> · <a href="LICENSE">Licença</a></p>
<p align="center"><a href="README.md">English</a> · <b>Português</b></p> <p align="center"><a href="README.md">English</a> · <b>Português</b></p>
@@ -18,7 +18,7 @@ SMTP, IMAP, POP3 e ManageSieve, com SPF, DKIM, DMARC, ARC, MTA-STS e TLS-RPT,
automação de DNS pela Cloudflare e filtro de spam e de vírus. Sem planos pagos e automação de DNS pela Cloudflare e filtro de spam e de vírus. Sem planos pagos e
sem limites artificiais de domínios ou caixas. sem limites artificiais de domínios ou caixas.
> **Estado: pré-lançamento.** O caminho do e-mail, a postura de segurança e as > **Versão 1.0.0** ([mudanças](CHANGELOG.pt-BR.md)). O caminho do e-mail, a postura de segurança e as
> interfaces web funcionam de ponta a ponta, e há uma suíte de testes > interfaces web funcionam de ponta a ponta, e há uma suíte de testes
> automatizados por trás. Leia [O que não está pronto](#o-que-não-está-pronto) > automatizados por trás. Leia [O que não está pronto](#o-que-não-está-pronto)
> antes de colocar e-mail de verdade nele. > antes de colocar e-mail de verdade nele.
@@ -109,7 +109,7 @@ precisa acontecer.
A versão curta: A versão curta:
```bash ```bash
cp .env.production.example .env # depois troque cada CHANGE scripts/init-env.sh --production # .env com segredos aleatórios; lista o que definir
make preflight # confere porta 25, PTR, RBLs, escopo do token make preflight # confere porta 25, PTR, RBLs, escopo do token
docker compose up -d docker compose up -d
make create-admin EMAIL=voce@exemplo.com PASSWORD='...' make create-admin EMAIL=voce@exemplo.com PASSWORD='...'
@@ -166,7 +166,7 @@ coisa para manter atualizada.
## Rodando localmente ## Rodando localmente
```bash ```bash
cp .env.example .env make env # .env a partir do .env.example, com segredos aleatórios
make gen-dev-cert # certificado autoassinado para os listeners make gen-dev-cert # certificado autoassinado para os listeners
make up # compila a partir do código pelo overlay de build make up # compila a partir do código pelo overlay de build
make seed # uma caixa postmaster@example.test de teste make seed # uma caixa postmaster@example.test de teste
@@ -204,7 +204,8 @@ navegador de verdade; veja [docs/WEBMAIL.md](docs/WEBMAIL.md#testing-it).
## Configuração ## Configuração
Cada variável está documentada em linha em Cada variável está documentada em linha em
[`.env.production.example`](.env.production.example). As que não têm padrão seguro: [`.env.example`](.env.example), o arquivo único para desenvolvimento e produção.
As que não têm padrão seguro (o `scripts/init-env.sh` gera os segredos):
| Variável | | | Variável | |
| :--- | :--- | | :--- | :--- |
@@ -385,6 +386,8 @@ e-mail:
- Os testes acompanham a mudança que precisa deles; as suítes acima precisam - Os testes acompanham a mudança que precisa deles; as suítes acima precisam
continuar verdes. continuar verdes.
- Os commits citam a issue: `#N: o que mudou e por quê`. - Os commits citam a issue: `#N: o que mudou e por quê`.
- Uma mudança que alguém vai notar entra em *Não lançado* no [CHANGELOG.pt-BR.md](CHANGELOG.pt-BR.md)
(e no equivalente em inglês). Como se lança uma versão: [docs/RELEASING.md](docs/RELEASING.md).
## Licença ## Licença
+22 -9
View File
@@ -70,18 +70,29 @@ Cloudflare token, or you can add them by hand later.
## Step 2 — Create the application in Coolify ## Step 2 — Create the application in Coolify
1. **New Resource → Docker Compose**, pointed at this repository. 1. **New Resource → Docker Compose**, pointed at this repository. It lives on
Gitea: choose **Private Repository (with Deploy Key)** and use
`git@git.lucasrguerra.dev.br:lucasrguerra/LambdaMail.git` with a key that is
registered in Gitea. An application that was created from a GitHub source
keeps cloning from GitHub until its source is changed in the Coolify UI; the
API cannot change it.
2. Coolify will read `docker-compose.yaml`. Leave the compose file alone; all 2. Coolify will read `docker-compose.yaml`. Leave the compose file alone; all
configuration is environment. configuration is environment.
3. Under **Environment Variables**, paste the contents of 3. Create the environment on your own machine, then paste it into
`.env.production.example` and replace every value marked `CHANGE`. **Environment Variables**:
Generate each secret separately:
```bash ```bash
openssl rand -base64 36 scripts/init-env.sh --production # writes .env with random secrets
``` ```
The script lists every value that is still at a development default
(`MAIL_DOMAIN`, `PRIMARY_MAIL_HOST`, `WEBMAIL_HOST`, `PUBLIC_IPV4`,
`IMAGE_TAG`); set those in `.env`, then paste. Every variable is explained
inline in [`.env.example`](../.env.example).
`POSTGRES_PASSWORD` and `REDIS_PASSWORD` are generated as hex on purpose: they
go inside connection URIs, and the `/` and `+` of base64 break them.
`LAMBDAMAIL_MASTER_KEY` deserves particular care. It encrypts every DKIM `LAMBDAMAIL_MASTER_KEY` deserves particular care. It encrypts every DKIM
private key, TOTP secret and provider token in the database. **Store a copy private key, TOTP secret and provider token in the database. **Store a copy
somewhere other than the database backup.** If you lose it, those secrets are somewhere other than the database backup.** If you lose it, those secrets are
@@ -176,8 +187,9 @@ have propagated and the IP has some sending history.
## Upgrading ## Upgrading
Images are published to the Gitea container registry for `linux/amd64` and `linux/arm64` on every merge Images are published to the Gitea container registry for `linux/amd64` and
to `main`. `linux/arm64` on every merge to `main`, and again under the version number when
a `v*` tag is pushed (see [RELEASING.md](RELEASING.md)).
```bash ```bash
docker compose pull docker compose pull
@@ -189,7 +201,8 @@ Deploys pull the images CI published rather than building on the server;
source instead. source instead.
Migrations run automatically on start, before the services that depend on them. Migrations run automatically on start, before the services that depend on them.
**Pin `IMAGE_TAG` to a `sha-` tag rather than leaving it on `latest`.** Not **Pin `IMAGE_TAG` to a release (`1.0.0`) or a `sha-` tag rather than leaving it
on `latest`.** Not
only for rollback: `docker compose up` does not re-pull a tag it already has only for rollback: `docker compose up` does not re-pull a tag it already has
locally, so a deployment left on `latest` will happily keep running the image locally, so a deployment left on `latest` will happily keep running the image
it pulled the first time while reporting success. An immutable tag changes on it pulled the first time while reporting success. An immutable tag changes on
+88
View File
@@ -0,0 +1,88 @@
# Releasing LambdaMail
How a version gets from a commit to a server, and what runs where. The history
of what changed is in the [CHANGELOG](../CHANGELOG.md).
## The pipeline
Everything runs on **Gitea Actions** (`.gitea/workflows/`), on runners hosted on
the Oracle server.
| Workflow | Runs on | Does |
| :--- | :--- | :--- |
| `ci.yml` | every push and pull request | language lint, Go and TypeScript suites against a real Postgres, webmail tests, a Trivy scan of the repository and of each image, and the whole stack brought up with compose |
| `release.yml` | a push to `main`, a `v*` tag, or by hand | runs `ci.yml` again, builds the six images for `linux/amd64` and `linux/arm64`, publishes them, and on a tag creates the release |
Nothing is published unless the full suite passes first.
Images go to the Gitea registry, one package per service:
```
git.lucasrguerra.dev.br/lucasrguerra/lambdamail/{protocols,auth,storage,webmail,migrations,rspamd}
```
Tags on every image: `latest`, `sha-<full commit>`, and on a version tag also
`1.2.3`, `1.2` and `1`. The packages are public, so a server pulls them without
logging in.
## Cutting a release
1. Update [CHANGELOG.md](../CHANGELOG.md) and [CHANGELOG.pt-BR.md](../CHANGELOG.pt-BR.md):
move what is under *Unreleased* to a new `## [X.Y.Z] - date` section.
2. Set `"version"` in `services/{auth,storage,webmail}/package.json` and their
lockfiles to the same number.
3. Merge to `main` and wait for the `Release` run of that merge to go green.
4. Tag the merge commit and push the tag:
```bash
git tag -a v1.2.3 -m "LambdaMail 1.2.3"
git push gitea v1.2.3
```
The tag triggers `release.yml` once more. It republishes the images under
`1.2.3`, `1.2` and `1` (layers are cached, so this is quick) and creates the
entry under **Releases** with the image list and the commit log since the
previous tag. A tag with a hyphen (`v1.3.0-rc.1`) is marked as a pre-release.
Versions follow [Semantic Versioning](https://semver.org/): a change to a
migration, a required environment variable, or a published port is at least a
minor release, and anything that needs manual steps on upgrade is called out in
the changelog.
## Deploying a version
Coolify pulls; it does not build. See the *Upgrading* section of
[DEPLOYMENT.md](DEPLOYMENT.md) for the two-step deploy (set `IMAGE_TAG`, then
deploy). In short:
1. `IMAGE_TAG` = the release (`1.2.3`) or `sha-<commit>`. Never `latest`.
2. Deploy. Migrations run before the services that need them.
3. Check `GET /health` on `protocols` and sign in to the webmail.
To roll back, set `IMAGE_TAG` back to the previous release and deploy again.
Migrations are not reverted: write them so the previous version still runs
against the new schema.
## Runners
Two `act_runner` instances run on the Oracle server (Ampere ARM, 4 cores), each
with its own Docker-in-Docker daemon, so two jobs never share a network or a
port. The jobs run unprivileged.
- `linux/arm64` images build natively; `linux/amd64` images build under QEMU
emulation, which is slower. Layers are cached in the registry.
- The Docker daemon of each runner keeps its layer cache between runs, and the
toolcache volume keeps downloaded toolchains.
- The workspace of a job is not visible to the Docker daemon, so workflows copy
files into containers with `docker cp` instead of mounting them with `-v`.
- Windows and macOS jobs cannot run here.
## Why some actions are not used
- `aquasecurity/trivy-action` clones Trivy from the workflow's own server,
which is Gitea; Trivy runs from its official image instead.
- `actions/upload-artifact` v4+ and the `gha` build cache are GitHub services;
per-architecture images are told apart by tag, and the cache lives in the
registry.
- Workflows read `GITHUB_OUTPUT` and `GITHUB_STEP_SUMMARY` under those names:
the `GITEA_*` equivalents are not set by the runner.
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Creates .env from .env.example and fills the empty secrets with random values.
#
# scripts/init-env.sh local development
# scripts/init-env.sh --production also lists what is still at a dev default
# scripts/init-env.sh --force overwrite an existing .env
#
# An existing .env is never touched without --force: regenerating
# LAMBDAMAIL_MASTER_KEY makes every stored secret unrecoverable.
set -euo pipefail
cd "$(dirname "$0")/.."
production=0
force=0
for arg in "$@"; do
case "$arg" in
--production) production=1 ;;
--force) force=1 ;;
-h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "unknown option: $arg" >&2; exit 2 ;;
esac
done
if [ -e .env ] && [ "$force" -ne 1 ]; then
echo ".env already exists; leaving it alone (use --force to overwrite)." >&2
exit 1
fi
command -v openssl >/dev/null || { echo "openssl is required to generate secrets" >&2; exit 1; }
cp .env.example .env
chmod 600 .env
# set_var KEY VALUE: replaces the line KEY=... in .env. The value is hex, so it
# needs no escaping for sed.
set_var() {
sed -i.bak "s|^$1=.*|$1=$2|" .env && rm -f .env.bak
}
set_var POSTGRES_PASSWORD "$(openssl rand -hex 24)"
set_var REDIS_PASSWORD "$(openssl rand -hex 24)"
set_var JWT_SECRET "$(openssl rand -hex 32)"
set_var LAMBDAMAIL_MASTER_KEY "$(openssl rand -hex 32)"
echo ".env written (mode 600) with fresh secrets."
if [ "$production" -eq 1 ]; then
set_var COOLIFY_PROXY_DIR /data/coolify/proxy
echo
echo "Production: still at their development defaults, set these in .env:"
pending=0
check() {
if grep -qE "^$1=$2\$" .env; then echo " - $1 (now $2)"; pending=1; fi
}
check MAIL_DOMAIN localhost
check PRIMARY_MAIL_HOST mail.localhost
check WEBMAIL_HOST 'localhost:3000'
check PUBLIC_IPV4 127.0.0.1
check IMAGE_TAG latest
[ "$pending" -eq 0 ] && echo " (nothing)"
echo
echo "Back up LAMBDAMAIL_MASTER_KEY separately from the database."
echo "Next: make preflight, then docker compose up -d (docs/DEPLOYMENT.md)."
fi
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@lambdamail/auth", "name": "@lambdamail/auth",
"version": "0.1.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lambdamail/auth", "name": "@lambdamail/auth",
"version": "0.1.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"hash-wasm": "^4.12.0", "hash-wasm": "^4.12.0",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lambdamail/auth", "name": "@lambdamail/auth",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@lambdamail/storage", "name": "@lambdamail/storage",
"version": "0.1.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lambdamail/storage", "name": "@lambdamail/storage",
"version": "0.1.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"pg": "^8.12.0" "pg": "^8.12.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lambdamail/storage", "name": "@lambdamail/storage",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@lambdamail/webmail", "name": "@lambdamail/webmail",
"version": "0.1.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lambdamail/webmail", "name": "@lambdamail/webmail",
"version": "0.1.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@fontsource-variable/geist": "^5.3.0", "@fontsource-variable/geist": "^5.3.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lambdamail/webmail", "name": "@lambdamail/webmail",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",