app-email rollout: gitea + rallly mappings; calcom/nextcloud/immich = findings

gitea (STARTTLS + FORCE_TRUST_SERVER_CERT) and rallly (Nodemailer STARTTLS +
SMTP_REJECT_UNAUTHORIZED=false) wired. Fixed rallly's non-existent 3.12.1 pin → 3.11.2.
calcom/nextcloud/immich don't fit the mechanism (self-signed opportunistic-STARTTLS,
split From, no-SMTP-env) — see FINDING doc in felhom.eu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 10:32:55 +02:00
parent f0529bc9a0
commit 3b5d102d21
5 changed files with 65 additions and 1 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## 2026-06-29 — App-email rollout: gitea + rallly (calcom/nextcloud/immich = findings)
- **gitea 1.23.4** — added `smtp_mapping` (STARTTLS via `GITEA__mailer__PROTOCOL=smtp+starttls` +
`FORCE_TRUST_SERVER_CERT=true` to trust the shim's self-signed cert; single `GITEA__mailer__FROM`). Compose
references the injected `GITEA__mailer__*` keys; env applied every boot.
- **rallly** — added `smtp_mapping` (Nodemailer STARTTLS, `SMTP_SECURE=false` + `SMTP_REJECT_UNAUTHORIZED=false`
to accept the self-signed cert; single `NOREPLY_EMAIL`). **Also fixed a broken image pin:** `3.12.1` does not
exist (3.x stops at 3.11.x) → corrected to `3.11.2` so the template is deployable.
- **NOT wired — reported as findings** (`felhom.eu/documentation/audits/FINDING-app-email-rollout-2026-06-29.md`):
- **cal.com v4.8.7** — hard-codes TLS `rejectUnauthorized:true` with no override; opportunistic STARTTLS
against the self-signed shim fails. Needs a non-STARTTLS-advertising plaintext listener (mechanism change).
- **nextcloud 31** — no cert-skip env (same opportunistic-STARTTLS gap) **and** a split From
(`MAIL_FROM_ADDRESS`+`MAIL_DOMAIN`) the single-`from_var` mapping can't express.
- **immich v2.5.5** — no SMTP env vars at all; config is admin-UI/DB or an `IMMICH_CONFIG_FILE` JSON. Does not
fit env-injection; left for a future config-file-injection mechanism (or manual admin-UI setup).
## 2026-06-29 — App-email: smtp_mapping for Vaultwarden + Mealie
- Added the `smtp_mapping` block to `templates/vaultwarden/.felhom.yml` and `templates/mealie/.felhom.yml`,
enabling managed outbound email (app → in-controller shim → hub → Resend) for the two spike-proven apps
+15
View File
@@ -61,3 +61,18 @@ healthcheck:
path: "/api/healthz"
expect:
status: 200
# --- App-email mapping (apps → in-controller shim → hub → Resend) ---
# Gitea uses STARTTLS to the shim and trusts its self-signed cert via FORCE_TRUST_SERVER_CERT.
# Gitea applies GITEA__* env on every boot, so toggling app-email + redeploy takes effect. The From
# is Gitea's single GITEA__mailer__FROM; USER/PASSWD stay unset (the shim accepts no-auth).
smtp_mapping:
host_var: GITEA__mailer__SMTP_ADDR
port_var: GITEA__mailer__SMTP_PORT
security_var: GITEA__mailer__PROTOCOL
security_value: "smtp+starttls"
from_var: GITEA__mailer__FROM
from_local: gitea
extra:
GITEA__mailer__ENABLED: "true"
GITEA__mailer__FORCE_TRUST_SERVER_CERT: "true"
+9
View File
@@ -16,6 +16,15 @@ services:
- GITEA__server__ROOT_URL=https://${SUBDOMAIN}.${DOMAIN}
- GITEA__server__SSH_DOMAIN=${SUBDOMAIN}.${DOMAIN}
- GITEA__database__DB_TYPE=sqlite3
# App-email (managed relay). Injected by the controller only when app-email is on (global +
# per-app); empty ENABLED/ADDR keeps Gitea mail disabled. Gitea applies GITEA__* env on every
# boot (environment-to-ini), so the toggle takes effect on redeploy. See .felhom.yml smtp_mapping.
- GITEA__mailer__ENABLED=${GITEA__mailer__ENABLED:-false}
- GITEA__mailer__PROTOCOL=${GITEA__mailer__PROTOCOL:-smtp+starttls}
- GITEA__mailer__SMTP_ADDR=${GITEA__mailer__SMTP_ADDR:-}
- GITEA__mailer__SMTP_PORT=${GITEA__mailer__SMTP_PORT:-2525}
- GITEA__mailer__FROM=${GITEA__mailer__FROM:-}
- GITEA__mailer__FORCE_TRUST_SERVER_CERT=${GITEA__mailer__FORCE_TRUST_SERVER_CERT:-false}
volumes:
- gitea_data:/data
networks:
+15
View File
@@ -68,3 +68,18 @@ healthcheck:
checks:
- type: http
port: 3000
# --- App-email mapping (apps → in-controller shim → hub → Resend) ---
# Rallly (Nodemailer) uses STARTTLS to the shim (SMTP_SECURE=false) and accepts the shim's self-signed
# cert (SMTP_REJECT_UNAUTHORIZED=false on v4; v3.x accepts by default). From = Rallly's single
# NOREPLY_EMAIL; SMTP_USER/SMTP_PWD stay unset (the shim accepts no-auth).
smtp_mapping:
host_var: SMTP_HOST
port_var: SMTP_PORT
security_var: SMTP_SECURE
security_value: "false"
from_var: NOREPLY_EMAIL
from_name_var: NOREPLY_EMAIL_NAME
from_local: rallly
extra:
SMTP_REJECT_UNAUTHORIZED: "false"
+11 -1
View File
@@ -10,7 +10,7 @@
services:
rallly:
image: lukevella/rallly:3.12.1
image: lukevella/rallly:3.11.2
container_name: rallly
restart: unless-stopped
depends_on:
@@ -21,6 +21,16 @@ services:
- DATABASE_URL=postgresql://rallly:${DB_PASSWORD}@rallly-postgres:5432/rallly
- SECRET_PASSWORD=${SECRET_PASSWORD}
- NEXT_PUBLIC_BASE_URL=https://${SUBDOMAIN}.${DOMAIN}
# App-email (managed relay). Injected by the controller only when app-email is on (global +
# per-app); empty SMTP_HOST keeps Rallly mail disabled. Rallly (Nodemailer) reads these at send
# time. SMTP_SECURE=false → STARTTLS to the shim; SMTP_REJECT_UNAUTHORIZED=false accepts the
# shim's self-signed cert (v4 flag; v3.x accepts self-signed by default). See .felhom.yml smtp_mapping.
- SMTP_HOST=${SMTP_HOST:-}
- SMTP_PORT=${SMTP_PORT:-587}
- SMTP_SECURE=${SMTP_SECURE:-false}
- SMTP_REJECT_UNAUTHORIZED=${SMTP_REJECT_UNAUTHORIZED:-true}
- NOREPLY_EMAIL=${NOREPLY_EMAIL:-}
- NOREPLY_EMAIL_NAME=${NOREPLY_EMAIL_NAME:-Felhom}
networks:
- traefik-public
- rallly-internal