Skip to content

Commit 0df6d09

Browse files
committed
fix(mail): address review feedback on PR #3238
- env.js: relax SMTP_SECURE from enum(4-values) to z.string() + case-insensitive transform, so any casing from the V1 secret (True/TRUE/true) flows through without a Zod validation crash. - .kontinuous/values.yaml: expand the SMTP-mapping comment to spell out the env/envFrom precedence, which is what makes the dev/preprod MailDev fallback work when smtp-app is missing. - smtp-app.sealed-secret.yaml: annotate the file header with a note on the V1-legacy MAILER_SEND_EMAILS key — sealed, kept for a no-op migration, not consumed by the V2 app. - transporter.ts: document that the cached transporter is process- scoped on purpose (configmap/secret changes roll the pod anyway).
1 parent 3eaf308 commit 0df6d09

4 files changed

Lines changed: 25 additions & 7 deletions

File tree

.kontinuous/env/prod/templates/smtp-app.sealed-secret.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Inherited as-is from the V1 prod setup (SocialGouv/egapro@master).
2+
# The MAILER_* keys are remapped to the V2 SMTP_* env var names in
3+
# .kontinuous/values.yaml. `MAILER_SEND_EMAILS` is a V1 legacy flag —
4+
# unused by the V2 app (sending is gated by MAIL_ENABLED in the `mail`
5+
# configmap). Kept in the sealed-secret for a no-op migration; can be
6+
# dropped once the prod credentials are re-sealed without it.
17
apiVersion: bitnami.com/v1alpha1
28
kind: SealedSecret
39
metadata:

.kontinuous/values.yaml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,17 @@ app:
7777
secretKeyRef:
7878
name: "{{ .Values.global.pgSecretName }}"
7979
key: PGSSLMODE
80-
# SMTP (Tipimail in preprod/prod). The smtp-app sealed-secret is kept
81-
# as-is from the V1 prod setup, with MAILER_* keys. We remap to the
82-
# V2 SMTP_* names expected by src/env.js. `optional: true` lets dev
83-
# review apps (no smtp-app secret) fall back to the values injected by
84-
# the mail configmap (pointing at MailDev).
80+
# SMTP (Tipimail in prod, MailDev on dev/preprod). The smtp-app
81+
# sealed-secret is kept as-is from the V1 prod setup, with MAILER_*
82+
# keys. We remap to the V2 SMTP_* names expected by src/env.js.
83+
#
84+
# Kubernetes precedence: `env` runs AFTER `envFrom`, so an `env` entry
85+
# would normally override a same-named envFrom value. But with
86+
# `optional: true` on a missing secret/key, the env entry is silently
87+
# dropped and the envFrom value is preserved. That is exactly the
88+
# fallback we rely on for dev/preprod review apps (no smtp-app
89+
# secret) where SMTP_HOST, SMTP_PORT, etc. come from the `mail`
90+
# configmap pointing at the in-cluster MailDev service.
8591
- name: SMTP_HOST
8692
valueFrom:
8793
secretKeyRef:

packages/app/src/env.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ export const env = createEnv({
9191
/**
9292
* `true` → connect with implicit TLS (port 465). `false` (default) keeps
9393
* the plain-socket / STARTTLS upgrade path used by MailDev (1025) and
94-
* Tipimail on 587. Accepts both "True"/"False" so the value inherited
94+
* Tipimail on 587. Case-insensitive so any casing of the value inherited
9595
* from the V1 sealed-secret (MAILER_SMTP_SSL) flows through unchanged.
9696
*/
9797
SMTP_SECURE: z
98-
.enum(["true", "false", "True", "False"])
98+
.string()
9999
.default("false")
100100
.transform((v) => v.toLowerCase() === "true"),
101101
MAIL_FROM: z.string().default("no-reply@egapro.local"),

packages/app/src/modules/mail/transporter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import "server-only";
22
import nodemailer, { type Transporter } from "nodemailer";
33
import { env } from "~/env.js";
44

5+
/**
6+
* Cached for the lifetime of the Node.js process. Env vars are read once at
7+
* first call; changes to SMTP_HOST / SMTP_PORT / auth require a restart of
8+
* the pod. That's the expected behaviour on Kubernetes since the Deployment
9+
* is recreated when the configmap or secret changes.
10+
*/
511
let cachedTransporter: Transporter | null = null;
612

713
export function getTransporter(): Transporter {

0 commit comments

Comments
 (0)