Installs Listmonk into a Kubernetes cluster as the
platform's self-hosted newsletter + marketing-campaign engine. Wraps the
redzumi/listmonk-chart chart
with a typed EmailMarketingStack XRD that handles:
- Database: embedded
PSQLCluster(CNPG) by default — Listmonk reads itsdb__user/db__passworddirectly from the CNPG-generated app Secret via the chart'sexistingSecretfield. - SMTP: an
ExternalSecretthat pulls SES SMTP credentials from AWS Secrets Manager. The remote path is observed from the referencedSMTPStack.status.smtp.awsSecretsManagerPath— composition gates on the observed status, so the ES renders only afterSMTPStackreports the path.
See the spec at specs/email-marketing-stack in the GitKB for the full
design (per-tenant model, OIDC, exposure, observability, analytics bridge,
provider workstream).
| Composed | What | Notes |
|---|---|---|
Namespace |
The Listmonk install namespace (default marketing) |
Owned by the stack. |
PSQLCluster |
CNPG-backed Postgres for Listmonk | targetNamespace = install ns so the app Secret is mounted natively. |
Helm Release |
redzumi/listmonk-chart 2.0.1 (Listmonk v6.0.0) | Embedded Postgres subchart off; chart wires to CNPG via database.existingSecret (passwordKey: password). |
ExternalSecret (Object MR) |
smtp-{host,port,username,password,from} projected into listmonk-smtp K8s Secret |
Matches the chart's smtp.existingSecret contract. Renders only when spec.smtp.smtpStackRef.name is set AND the observed SMTPStack reports an AWS SM path. |
Object (Observe) |
Reads upstream SMTPStack |
managementPolicies: [Observe] — composition reads status.smtp.awsSecretsManagerPath. |
Usage × 2 |
Delete-order safeguards | Helm release drains before namespace or PSQLCluster teardown. |
- OIDC + Gateway API exposure (
/admin,/apigated;/subscription/*,/campaign/*,/link/*,/uploads/*anonymous). meysam81/listmonk-exportersidecar + the two Grafana dashboards inxrs/stacks/aws/observe/dashboards/email-marketing/.- S3 media backend, SES bounce ingestion.
tenancy.mode=tenantintegration with TenantStack.provider-listmonkdeclarative content management (Template/List/Campaign/Sequence/Settings/User).
The pinned upstream chart (redzumi/listmonk-chart 2.0.1) accepts
smtp.existingSecret and synthesizes a placeholder Secret when none is
supplied, but the Deployment template does not project the SMTP
secret values into env vars. The listmonk-smtp K8s Secret we render
materializes correctly, but the running Pod won't consume it
automatically.
v1 operator path: configure SMTP via the Listmonk admin UI (Settings → SMTP) — paste the values from the K8s Secret:
kubectl -n marketing get secret listmonk-smtp -o json \
| jq '.data | map_values(@base64d)'v2 plan: fork the chart into hops-ops/helm-charts/listmonk and add
envFrom: secretRef on the SMTP Secret so the existing ExternalSecret
wires the Pod declaratively. The Secret key layout (smtp-host,
smtp-port, smtp-username, smtp-password, smtp-from) already
matches the chart's smtp.existingSecret contract, so the fork only
needs to add the envFrom block — no ExternalSecret changes required.
Tracked in the spec under OQ1 + OQ10.
We evaluated th0th/helm-charts/listmonk (5.0.3-3, Listmonk v5.0.3) but
its values.schema.json carries an external $ref to
raw.githubusercontent.com that the provider-helm-bundled helm CLI
rejects with "invalid file url", failing every install. The redzumi
chart ships no values.schema.json and uses Listmonk v6.0.0 — newer
app + works through provider-helm without modification.
The chart's initContainer runs listmonk --install --idempotent --yes
which creates the database schema; Listmonk's web UI prompts for the
admin username + password on first HTTP visit. There is no env-var
admin bootstrap — operator sets credentials interactively. v2 can switch
to OIDC (Zitadel) once the AuthStack listmonk Application is provisioned.
Pat-local's helm/kubernetes ProviderConfigs target the AWS EKS data
plane (see reference_pat_local_providerconfig_target). With Crossplane
running on colima:
# Install the configuration package
hops config install --path xrs/stacks/k8s/email-marketing
# Apply the local example
kubectl --context pat-local apply -f xrs/stacks/k8s/email-marketing/examples/emailmarketingstacks/local-colima.yaml
# Watch the XR
kubectl --context pat-local get emailmarketingstack -A -w
# Port-forward the admin UI once the Helm release is Ready
kubectl --context pat-local -n marketing port-forward svc/marketing-listmonk 9000:9000
# → open http://localhost:9000/admin (default credentials: admin / set at first run)The local-colima.yaml example references an SMTPStack named smtp in
namespace default. Until that XR lands on the cluster, the
marketing-observed-smtp-stack Object MR will be in a "not found" state
and the SMTP ExternalSecret will not render. Install + admin UI still
work; SMTP wiring catches up automatically once SMTPStack reports
status.smtp.awsSecretsManagerPath.
| Command | What it does |
|---|---|
make render:all |
Render every example, parallelized |
make validate:all |
Same as render + crossplane beta validate |
make test |
Run the KCL CompositionTest cases under tests/ |
make build |
up project build — emits the configuration package |
make clean |
Drop _output/ + .up/ + apis/.../configuration.yaml |
make publish tag=vX.Y.Z |
Push the configuration package to the registry |