Skip to content

feat(selfhosted): unified email — Stalwart, email-oauth2-proxy, imapsync#2441

Open
dapperdivers wants to merge 1 commit intomainfrom
feat/unified-email
Open

feat(selfhosted): unified email — Stalwart, email-oauth2-proxy, imapsync#2441
dapperdivers wants to merge 1 commit intomainfrom
feat/unified-email

Conversation

@dapperdivers
Copy link
Owner

Summary

  • Deploy Stalwart Mail Server as the single IMAP/SMTP endpoint consolidating Gmail, Outlook.com, and Migadu accounts
  • Add email-oauth2-proxy to bridge Outlook.com personal OAuth2 → plain IMAP for imapsync
  • Add imapsync CronJobs (3 controllers: gmail, outlook, migadu) for continuous mail pull into Stalwart

Architecture

┌─────────────┐    ┌──────────────┐    ┌────────────────────┐
│  Gmail IMAP  │───▶│              │    │                    │
└─────────────┘    │   imapsync   │───▶│  Stalwart Mail     │
┌─────────────┐    │  (3 CronJobs)│    │  Server            │
│ Outlook.com │───▶│              │    │                    │
│ (via proxy) │    │              │    │  IMAP/SMTP/JMAP    │
└─────────────┘    └──────────────┘    │  RocksDB storage   │
┌─────────────┐           │            │  Spam filtering    │
│ Migadu IMAP │───────────┘            │  Per-sender relay  │
└─────────────┘                        └────────────────────┘
       ▲                                        │
       │                                        ▼
┌──────────────────┐                   ┌────────────────────┐
│ email-oauth2-    │                   │  Thunderbird /     │
│ proxy (OAuth2    │                   │  mobile clients    │
│ → IMAP bridge)   │                   └────────────────────┘
└──────────────────┘

Components

App Chart Details
stalwart wrenix/stalwart-mail 0.4.9 (app v0.15.5) RocksDB, LB on MAIL_VIP_GATEWAY (10.100.0.30), per-sender SMTP relay, Authentik-protected web UI
email-oauth2-proxy app-template Outlook.com XOAUTH2→IMAP bridge on port 1993, PVC for token credstore
imapsync app-template (3 CronJob controllers) Staggered every 10 min, all suspended until secrets + initial import complete

Files Changed (16 files, +805 lines)

  • cluster-settings.yaml — new MAIL_VIP_GATEWAY: 10.100.0.30
  • selfhosted/kustomization.yaml — register 3 new apps
  • stalwart/ — ks.yaml, ocirepository, helmrelease, externalsecret, kustomization
  • email-oauth2-proxy/ — ks.yaml, helmrelease, externalsecret, configmap, kustomization
  • imapsync/ — ks.yaml, helmrelease, externalsecret, kustomization

Pre-deploy Checklist

  • Create Infisical secrets (STALWART_* prefix) for all ExternalSecret references
  • Register Azure AD app for Outlook OAuth2 (client ID + secret)
  • Create Gmail app password
  • Merge PR and let Flux reconcile
  • One-time interactive OAuth token acquisition for email-oauth2-proxy
  • Run manual import Jobs (sequential, with VolumeSnapshots between)
  • Verify import, then unsuspend CronJobs
  • Configure Thunderbird / mobile clients

Test Plan

  • kustomize build passes for all 3 apps (verified locally)
  • Helm-validator passed clean for both app-template HelmReleases
  • Stalwart HelmRelease validated (fixed duplicate spam-filter block, dotted-key notation, null port issue)
  • Flux reconciliation succeeds after merge
  • Stalwart web UI accessible via ingress
  • imapsync test run (manual Job) pulls mail successfully

🤖 Generated with Claude Code

…mapsync

Deploy Stalwart Mail Server as the single IMAP/SMTP endpoint consolidating
Gmail, Outlook.com, and Migadu accounts. email-oauth2-proxy handles
Outlook XOAUTH2, and imapsync CronJobs pull mail from all three providers.

- stalwart: wrenix/stalwart-mail chart v0.4.9, app v0.15.5
  RocksDB storage, per-sender SMTP relay, spam threshold 8,
  LoadBalancer on MAIL_VIP_GATEWAY (10.100.0.30), Authentik ingress
- email-oauth2-proxy: app-template, Outlook.com OAuth2→IMAP bridge
- imapsync: app-template, 3 CronJob controllers (gmail/outlook/migadu)
  staggered every 10 min, suspended until secrets + import complete
- All secrets via ExternalSecret (Infisical, STALWART_* prefix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dunce-bot
Copy link
Contributor

dunce-bot bot commented Feb 22, 2026

--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: actions-runner-system/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: actions-runner-system/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: ai/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: ai/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: cert-manager/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: cert-manager/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: database/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: database/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: default/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: default/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: external-secrets/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: external-secrets/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: flux-system/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: flux-system/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: home-automation/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: home-automation/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: kube-system/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: kube-system/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: kyverno/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: kyverno/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: media/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: media/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: network/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: network/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: observability/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: observability/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: rook-ceph/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: rook-ceph/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: roundtable/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: roundtable/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: security/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: security/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: selfhosted/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: selfhosted/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: storage/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: storage/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: system-upgrade/cluster-settings

+++ kubernetes/apps Kustomization: flux-system/cluster-apps ConfigMap: system-upgrade/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: selfhosted/stalwart

+++ kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: selfhosted/stalwart

@@ -0,0 +1,40 @@

+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: cluster-apps
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: stalwart
+  namespace: selfhosted
+spec:
+  commonMetadata:
+    labels:
+      app.kubernetes.io/name: stalwart
+  decryption:
+    provider: sops
+    secretRef:
+      name: sops-age
+  dependsOn:
+  - name: external-secrets-stores
+    namespace: external-secrets
+  interval: 30m
+  path: ./kubernetes/apps/selfhosted/stalwart/app
+  postBuild:
+    substitute:
+      APP: stalwart
+    substituteFrom:
+    - kind: ConfigMap
+      name: cluster-settings
+    - kind: Secret
+      name: cluster-secrets
+  prune: true
+  retryInterval: 1m
+  sourceRef:
+    kind: GitRepository
+    name: flux-system
+    namespace: flux-system
+  targetNamespace: selfhosted
+  timeout: 10m
+  wait: true
+
--- kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: selfhosted/email-oauth2-proxy

+++ kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: selfhosted/email-oauth2-proxy

@@ -0,0 +1,42 @@

+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: cluster-apps
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: email-oauth2-proxy
+  namespace: selfhosted
+spec:
+  commonMetadata:
+    labels:
+      app.kubernetes.io/name: email-oauth2-proxy
+  decryption:
+    provider: sops
+    secretRef:
+      name: sops-age
+  dependsOn:
+  - name: stalwart
+    namespace: selfhosted
+  - name: external-secrets-stores
+    namespace: external-secrets
+  interval: 30m
+  path: ./kubernetes/apps/selfhosted/email-oauth2-proxy/app
+  postBuild:
+    substitute:
+      APP: email-oauth2-proxy
+    substituteFrom:
+    - kind: ConfigMap
+      name: cluster-settings
+    - kind: Secret
+      name: cluster-secrets
+  prune: true
+  retryInterval: 1m
+  sourceRef:
+    kind: GitRepository
+    name: flux-system
+    namespace: flux-system
+  targetNamespace: selfhosted
+  timeout: 5m
+  wait: true
+
--- kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: selfhosted/imapsync

+++ kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: selfhosted/imapsync

@@ -0,0 +1,44 @@

+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: cluster-apps
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: imapsync
+  namespace: selfhosted
+spec:
+  commonMetadata:
+    labels:
+      app.kubernetes.io/name: imapsync
+  decryption:
+    provider: sops
+    secretRef:
+      name: sops-age
+  dependsOn:
+  - name: stalwart
+    namespace: selfhosted
+  - name: email-oauth2-proxy
+    namespace: selfhosted
+  - name: external-secrets-stores
+    namespace: external-secrets
+  interval: 30m
+  path: ./kubernetes/apps/selfhosted/imapsync/app
+  postBuild:
+    substitute:
+      APP: imapsync
+    substituteFrom:
+    - kind: ConfigMap
+      name: cluster-settings
+    - kind: Secret
+      name: cluster-secrets
+  prune: true
+  retryInterval: 1m
+  sourceRef:
+    kind: GitRepository
+    name: flux-system
+    namespace: flux-system
+  targetNamespace: selfhosted
+  timeout: 5m
+  wait: false
+
--- kubernetes/apps/selfhosted/gotenberg/app Kustomization: selfhosted/gotenberg ConfigMap: selfhosted/cluster-settings

+++ kubernetes/apps/selfhosted/gotenberg/app Kustomization: selfhosted/gotenberg ConfigMap: selfhosted/cluster-settings

@@ -4,12 +4,13 @@

   CLUSTER_NAME: kubernetes
   EMQX_VIP_GATEWAY: 10.100.0.26
   ESP_HOME_VIP_GATEWAY: 10.100.0.19
   EXTERNAL_VIP_GATEWAY: 10.100.0.22
   INTERNAL_VIP_GATEWAY: 10.100.0.20
   KUBE_VIP_GATEWAY: 10.100.0.21
+  MAIL_VIP_GATEWAY: 10.100.0.30
   NUT_VIP_GATEWAY: 10.100.0.28
   PLEX_VIP_GATEWAY: 10.100.0.24
   POSTGRESS_VIP_GATEWAY: 10.100.0.25
   SMTP_VIP_GATEWAY: 10.100.0.27
   TIME_ZONE: America/Chicago
   UNIFI_VIP_GATEWAY: 10.100.0.23
--- kubernetes/apps/selfhosted/stalwart/app Kustomization: selfhosted/stalwart OCIRepository: selfhosted/stalwart-mail

+++ kubernetes/apps/selfhosted/stalwart/app Kustomization: selfhosted/stalwart OCIRepository: selfhosted/stalwart-mail

@@ -0,0 +1,19 @@

+---
+apiVersion: source.toolkit.fluxcd.io/v1
+kind: OCIRepository
+metadata:
+  labels:
+    app.kubernetes.io/name: stalwart
+    kustomize.toolkit.fluxcd.io/name: stalwart
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: stalwart-mail
+  namespace: selfhosted
+spec:
+  interval: 5m
+  layerSelector:
+    mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
+    operation: copy
+  ref:
+    tag: 0.4.9
+  url: oci://codeberg.org/wrenix/helm-charts/stalwart-mail
+
--- kubernetes/apps/selfhosted/stalwart/app Kustomization: selfhosted/stalwart HelmRelease: selfhosted/stalwart

+++ kubernetes/apps/selfhosted/stalwart/app Kustomization: selfhosted/stalwart HelmRelease: selfhosted/stalwart

@@ -0,0 +1,220 @@

+---
+apiVersion: helm.toolkit.fluxcd.io/v2
+kind: HelmRelease
+metadata:
+  labels:
+    app.kubernetes.io/name: stalwart
+    kustomize.toolkit.fluxcd.io/name: stalwart
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: stalwart
+  namespace: selfhosted
+spec:
+  chartRef:
+    kind: OCIRepository
+    name: stalwart-mail
+  install:
+    remediation:
+      retries: 3
+  interval: 30m
+  upgrade:
+    cleanupOnFail: true
+    remediation:
+      retries: 3
+      strategy: rollback
+  values:
+    backup:
+      enabled: false
+    certificate:
+      certmanager:
+        enabled: false
+      secretName: -.PLACEHOLDER_SECRET_DOMAIN..-tls
+    config:
+      authentication:
+        fallback-admin:
+          secret: '%{env:FALLBACK_ADMIN_SECRET}%'
+          user: admin
+      certificate:
+        default:
+          cert: '%{file:/opt/stalwart/etc/certs/tls.crt}%'
+          default: true
+          private-key: '%{file:/opt/stalwart/etc/certs/tls.key}%'
+      directory:
+        internal:
+          store: rocksdb
+          type: internal
+      http:
+        use-x-forwarded: true
+      metrics:
+        prometheus:
+          auth:
+            secret: '%{env:METRICS_SECRET}%'
+            username: '%{env:METRICS_USERNAME}%'
+          enable: true
+      queue:
+        outbound:
+          hostname: mail...PLACEHOLDER_SECRET_DOMAIN..
+      remote:
+        gmail-relay:
+          address: smtp.gmail.com
+          auth:
+            secret: '%{env:GMAIL_SMTP_PASSWORD}%'
+            username: '%{env:GMAIL_ADDRESS}%'
+          port: 587
+          protocol: smtp
+          tls:
+            implicit: false
+            start-tls: true
+        migadu-relay:
+          address: smtp.migadu.com
+          auth:
+            secret: '%{env:MIGADU_SMTP_PASSWORD}%'
+            username: '%{env:MIGADU_ADDRESS}%'
+          port: 587
+          protocol: smtp
+          tls:
+            implicit: false
+            start-tls: true
+        outlook-relay:
+          address: smtp.office365.com
+          auth:
+            secret: '%{env:OUTLOOK_SMTP_PASSWORD}%'
+            username: '%{env:OUTLOOK_ADDRESS}%'
+          port: 587
+          protocol: smtp
+          tls:
+            implicit: false
+            start-tls: true
+      server:
+        allowed-ip:
+          10.0.0.0/8: ''
+          172.16.0.0/12: ''
+          192.168.0.0/16: ''
+        listener:
+          http:
+            bind:
+            - '[::]:80'
+            protocol: http
+          imap:
+            bind:
+            - '[::]:143'
+            protocol: imap
+          imaptls:
+            bind:
+            - '[::]:993'
+            protocol: imap
+            tls:
+              implicit: true
+          sieve:
+            bind:
+            - '[::]:4190'
+            protocol: managesieve
+          smtp:
+            bind:
+            - '[::]:25'
+            protocol: smtp
+          submission:
+            bind:
+            - '[::]:587'
+            protocol: smtp
+          submissions:
+            bind:
+            - '[::]:465'
+            protocol: smtp
+            tls:
+              implicit: true
+      spam-filter:
+        auto-update: true
+        dnsbl:
+          card-is-ham: true
+        header:
+          is-spam: X-Spam-Status
+        score:
+          discard: 0
+          reject: 0
+          spam: 8
+      storage:
+        blob: rocksdb
+        data: rocksdb
+        directory: internal
+        fts: rocksdb
+        lookup: rocksdb
+      store:
+        rocksdb:
+          compression: lz4
+          path: /data
+          type: rocksdb
+          write-buffer-size: 268435456
+      tracer:
+        otel:
+          enable: false
+        stdout:
+          ansi: false
+          enable: true
+          level: info
+          type: stdout
+    envFrom:
+    - secretRef:
+        name: stalwart-secret
+    image:
+      tag: v0.15.5
+    ingress:
+      annotations:
+        authentik.home.arpa/internal: 'true'
+        external-dns.alpha.kubernetes.io/target: internal...PLACEHOLDER_SECRET_DOMAIN..
+      className: internal
+      enabled: true
+      hosts:
+      - host: mail...PLACEHOLDER_SECRET_DOMAIN..
+        paths:
+        - path: /
+          pathType: ImplementationSpecific
+      tls:
+      - hosts:
+        - mail...PLACEHOLDER_SECRET_DOMAIN..
+        secretName: -.PLACEHOLDER_SECRET_DOMAIN..-tls
+    livenessProbe:
+      httpGet:
+        httpHeaders:
+        - name: X-Forwarded-For
+          value: 127.0.0.1
+        path: /healthz/live
+        port: http
+      initialDelaySeconds: 5
+      periodSeconds: 10
+    persistence:
+      accessMode: ReadWriteOnce
+      enabled: true
+      size: 50Gi
+      storageClass: ceph-rbd
+    readinessProbe:
+      httpGet:
+        httpHeaders:
+        - name: X-Forwarded-For
+          value: 127.0.0.1
+        path: /healthz/ready
+        port: http
+      initialDelaySeconds: 30
+      periodSeconds: 10
+    resources:
+      limits:
+        cpu: 2000m
+        memory: 4Gi
+      requests:
+        cpu: 500m
+        memory: 1Gi
+    secrets:
+      create: false
+    service:
+      annotations:
+        external-dns.alpha.kubernetes.io/hostname: mail...PLACEHOLDER_SECRET_DOMAIN..
+        lbipam.cilium.io/ips: 10.100.0.30
+      ports:
+        http: 80
+        imap: 143
+        imaptls: 993
+        sieve: 4190
+        smtp: 25
+        submission: 587
+        submissions: 465
+      type: LoadBalancer
+
--- kubernetes/apps/selfhosted/stalwart/app Kustomization: selfhosted/stalwart ExternalSecret: selfhosted/stalwart

+++ kubernetes/apps/selfhosted/stalwart/app Kustomization: selfhosted/stalwart ExternalSecret: selfhosted/stalwart

@@ -0,0 +1,40 @@

+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  labels:
+    app.kubernetes.io/name: stalwart
+    kustomize.toolkit.fluxcd.io/name: stalwart
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: stalwart
+  namespace: selfhosted
+spec:
+  dataFrom:
+  - find:
+      name:
+        regexp: ^STALWART.*
+  secretStoreRef:
+    kind: ClusterSecretStore
+    name: infisical
+  target:
+    name: stalwart-secret
+    template:
+      data:
+        FALLBACK_ADMIN_SECRET: '{{ .STALWART_ADMIN_PASSWORD }}'
+        GMAIL_ADDRESS: '{{ .STALWART_GMAIL_ADDRESS }}'
+        GMAIL_IMAP_PASSWORD: '{{ .STALWART_GMAIL_IMAP_PASSWORD }}'
+        GMAIL_SMTP_PASSWORD: '{{ .STALWART_GMAIL_SMTP_PASSWORD }}'
+        METRICS_SECRET: '{{ .STALWART_METRICS_PASSWORD }}'
+        METRICS_USERNAME: prometheus
+        MIGADU_ADDRESS: '{{ .STALWART_MIGADU_ADDRESS }}'
+        MIGADU_IMAP_PASSWORD: '{{ .STALWART_MIGADU_IMAP_PASSWORD }}'
+        MIGADU_SMTP_PASSWORD: '{{ .STALWART_MIGADU_SMTP_PASSWORD }}'
+        OUTLOOK_ADDRESS: '{{ .STALWART_OUTLOOK_ADDRESS }}'
+        OUTLOOK_OAUTH_CLIENT_ID: '{{ .STALWART_OUTLOOK_OAUTH_CLIENT_ID }}'
+        OUTLOOK_OAUTH_CLIENT_SECRET: '{{ .STALWART_OUTLOOK_OAUTH_CLIENT_SECRET }}'
+        OUTLOOK_SMTP_PASSWORD: '{{ .STALWART_OUTLOOK_SMTP_PASSWORD }}'
+        STALWART_PASSWORD: '{{ .STALWART_PRIMARY_PASSWORD }}'
+        STALWART_USER: '{{ .STALWART_PRIMARY_USER }}'
+      engineVersion: v2
+      mergePolicy: Replace
+
--- kubernetes/apps/selfhosted/email-oauth2-proxy/app Kustomization: selfhosted/email-oauth2-proxy HelmRelease: selfhosted/email-oauth2-proxy

+++ kubernetes/apps/selfhosted/email-oauth2-proxy/app Kustomization: selfhosted/email-oauth2-proxy HelmRelease: selfhosted/email-oauth2-proxy

@@ -0,0 +1,90 @@

+---
+apiVersion: helm.toolkit.fluxcd.io/v2
+kind: HelmRelease
+metadata:
+  labels:
+    app.kubernetes.io/name: email-oauth2-proxy
+    kustomize.toolkit.fluxcd.io/name: email-oauth2-proxy
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: email-oauth2-proxy
+  namespace: selfhosted
+spec:
+  chartRef:
+    kind: OCIRepository
+    name: app-template
+  install:
+    remediation:
+      retries: 3
+  interval: 30m
+  upgrade:
+    cleanupOnFail: true
+    remediation:
+      retries: 3
+      strategy: rollback
+  values:
+    controllers:
+      main:
+        annotations:
+          reloader.stakater.com/auto: 'true'
+        containers:
+          app:
+            env:
+              CACHE_STORE: /config/credstore.config
+              LOGFILE: 'true'
+            envFrom:
+            - secretRef:
+                name: email-oauth2-proxy-secret
+            image:
+              repository: blacktirion/email-oauth2-proxy-docker
+              tag: 2.4.0
+            probes:
+              liveness:
+                custom: true
+                enabled: true
+                spec:
+                  initialDelaySeconds: 10
+                  periodSeconds: 30
+                  tcpSocket:
+                    port: 1993
+              readiness:
+                custom: true
+                enabled: true
+                spec:
+                  initialDelaySeconds: 5
+                  periodSeconds: 10
+                  tcpSocket:
+                    port: 1993
+            resources:
+              limits:
+                cpu: 200m
+                memory: 256Mi
+              requests:
+                cpu: 100m
+                memory: 128Mi
+        strategy: Recreate
+    persistence:
+      config:
+        advancedMounts:
+          main:
+            app:
+            - path: /app/emailproxy.config
+              readOnly: true
+              subPath: emailproxy.config
+        name: email-oauth2-proxy-config
+        type: configMap
+      data:
+        accessMode: ReadWriteOnce
+        advancedMounts:
+          main:
+            app:
+            - path: /config
+        size: 100Mi
+        storageClass: ceph-rbd
+        type: persistentVolumeClaim
+    service:
+      app:
+        controller: main
+        ports:
+          imap:
+            port: 1993
+
--- kubernetes/apps/selfhosted/email-oauth2-proxy/app Kustomization: selfhosted/email-oauth2-proxy ExternalSecret: selfhosted/email-oauth2-proxy

+++ kubernetes/apps/selfhosted/email-oauth2-proxy/app Kustomization: selfhosted/email-oauth2-proxy ExternalSecret: selfhosted/email-oauth2-proxy

@@ -0,0 +1,28 @@

+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  labels:
+    app.kubernetes.io/name: email-oauth2-proxy
+    kustomize.toolkit.fluxcd.io/name: email-oauth2-proxy
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: email-oauth2-proxy
+  namespace: selfhosted
+spec:
+  dataFrom:
+  - find:
+      name:
+        regexp: ^STALWART_OUTLOOK.*
+  secretStoreRef:
+    kind: ClusterSecretStore
+    name: infisical
+  target:
+    name: email-oauth2-proxy-secret
+    template:
+      data:
+        OUTLOOK_ADDRESS: '{{ .STALWART_OUTLOOK_ADDRESS }}'
+        OUTLOOK_OAUTH_CLIENT_ID: '{{ .STALWART_OUTLOOK_OAUTH_CLIENT_ID }}'
+        OUTLOOK_OAUTH_CLIENT_SECRET: '{{ .STALWART_OUTLOOK_OAUTH_CLIENT_SECRET }}'
+      engineVersion: v2
+      mergePolicy: Replace
+
--- kubernetes/apps/selfhosted/email-oauth2-proxy/app Kustomization: selfhosted/email-oauth2-proxy ConfigMap: selfhosted/email-oauth2-proxy-config

+++ kubernetes/apps/selfhosted/email-oauth2-proxy/app Kustomization: selfhosted/email-oauth2-proxy ConfigMap: selfhosted/email-oauth2-proxy-config

@@ -0,0 +1,29 @@

+---
+apiVersion: v1
+data:
+  emailproxy.config: |
+    [Email OAuth 2.0 Proxy configuration]
+    # Log to stdout for k8s log collection
+    delete_account_token_on_password_error = False
+    encrypt_client_secret_on_first_use = False
+
+    [outlook]
+    permission_url = https://login.microsoftonline.com/common/oauth2/v2.0/authorize
+    token_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
+    oauth2_scope = https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+    redirect_uri = https://login.microsoftonline.com/common/oauth2/nativeclient
+
+    server_address = outlook.office.com
+    server_port = 993
+
+    local_address = 0.0.0.0
+    local_port = 1993
+kind: ConfigMap
+metadata:
+  labels:
+    app.kubernetes.io/name: email-oauth2-proxy
+    kustomize.toolkit.fluxcd.io/name: email-oauth2-proxy
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: email-oauth2-proxy-config
+  namespace: selfhosted
+
--- kubernetes/apps/selfhosted/imapsync/app Kustomization: selfhosted/imapsync HelmRelease: selfhosted/imapsync

+++ kubernetes/apps/selfhosted/imapsync/app Kustomization: selfhosted/imapsync HelmRelease: selfhosted/imapsync

@@ -0,0 +1,230 @@

+---
+apiVersion: helm.toolkit.fluxcd.io/v2
+kind: HelmRelease
+metadata:
+  labels:
+    app.kubernetes.io/name: imapsync
+    kustomize.toolkit.fluxcd.io/name: imapsync
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: imapsync
+  namespace: selfhosted
+spec:
+  chartRef:
+    kind: OCIRepository
+    name: app-template
+  install:
+    remediation:
+      retries: 3
+  interval: 30m
+  upgrade:
+    cleanupOnFail: true
+    remediation:
+      retries: 3
+      strategy: rollback
+  values:
+    controllers:
+      gmail:
+        containers:
+          app:
+            args:
+            - --gmail1
+            - --host2=stalwart.selfhosted.svc.cluster.local
+            - --port2=143
+            - --user1=$(GMAIL_ADDRESS)
+            - --passfile1=/secrets/gmail-password
+            - --user2=$(STALWART_USER)
+            - --passfile2=/secrets/stalwart-password
+            - --skipcrossduplicates
+            - --exclude=\[Gmail\]/(All Mail|Important|Starred|Spam|Chats|Trash)
+            - --usecache
+            - --tmpdir=/cache
+            - --logdir=/cache/logs
+            - --maxmessagespersecond=3
+            - --errorsmax=200
+            - --pidfilelocking
+            - --pidfile=/cache/gmail.pid
+            env:
+              GMAIL_ADDRESS:
+                valueFrom:
+                  secretKeyRef:
+                    key: GMAIL_ADDRESS
+                    name: imapsync-credentials
+              STALWART_USER:
+                valueFrom:
+                  secretKeyRef:
+                    key: STALWART_USER
+                    name: imapsync-credentials
+            image:
+              repository: gilleslamiral/imapsync
+              tag: '2.314'
+            resources:
+              limits:
+                cpu: 500m
+                memory: 512Mi
+              requests:
+                cpu: 100m
+                memory: 256Mi
+        cronjob:
+          activeDeadlineSeconds: 540
+          backoffLimit: 2
+          concurrencyPolicy: Forbid
+          failedJobsHistory: 5
+          schedule: '*/10 * * * *'
+          startingDeadlineSeconds: 300
+          successfulJobsHistory: 3
+          suspend: true
+          timeZone: Etc/UTC
+        pod:
+          restartPolicy: Never
+        type: cronjob
+      migadu:
+        containers:
+          app:
+            args:
+            - --host1=imap.migadu.com
+            - --port1=993
+            - --ssl1
+            - --host2=stalwart.selfhosted.svc.cluster.local
+            - --port2=143
+            - --user1=$(MIGADU_ADDRESS)
+            - --passfile1=/secrets/migadu-password
+            - --user2=$(STALWART_USER)
+            - --passfile2=/secrets/stalwart-password
+            - --usecache
+            - --tmpdir=/cache
+            - --logdir=/cache/logs
+            - --errorsmax=200
+            - --pidfilelocking
+            - --pidfile=/cache/migadu.pid
+            env:
+              MIGADU_ADDRESS:
+                valueFrom:
+                  secretKeyRef:
+                    key: MIGADU_ADDRESS
+                    name: imapsync-credentials
+              STALWART_USER:
+                valueFrom:
+                  secretKeyRef:
+                    key: STALWART_USER
+                    name: imapsync-credentials
+            image:
+              repository: gilleslamiral/imapsync
+              tag: '2.314'
+            resources:
+              limits:
+                cpu: 500m
+                memory: 512Mi
+              requests:
+                cpu: 100m
+                memory: 256Mi
+        cronjob:
+          activeDeadlineSeconds: 540
+          backoffLimit: 2
+          concurrencyPolicy: Forbid
+          failedJobsHistory: 5
+          schedule: 4-54/10 * * * *
+          startingDeadlineSeconds: 300
+          successfulJobsHistory: 3
+          suspend: true
+          timeZone: Etc/UTC
+        pod:
+          restartPolicy: Never
+        type: cronjob
+      outlook:
+        containers:
+          app:
+            args:
+            - --host1=email-oauth2-proxy.selfhosted.svc.cluster.local
+            - --port1=1993
+            - --host2=stalwart.selfhosted.svc.cluster.local
+            - --port2=143
+            - --user1=$(OUTLOOK_ADDRESS)
+            - --passfile1=/secrets/outlook-password
+            - --user2=$(STALWART_USER)
+            - --passfile2=/secrets/stalwart-password
+            - --usecache
+            - --tmpdir=/cache
+            - --logdir=/cache/logs
+            - --errorsmax=200
+            - --pidfilelocking
+            - --pidfile=/cache/outlook.pid
+            env:
+              OUTLOOK_ADDRESS:
+                valueFrom:
+                  secretKeyRef:
+                    key: OUTLOOK_ADDRESS
+                    name: imapsync-credentials
+              STALWART_USER:
+                valueFrom:
+                  secretKeyRef:
+                    key: STALWART_USER
+                    name: imapsync-credentials
+            image:
+              repository: gilleslamiral/imapsync
+              tag: '2.314'
+            resources:
+              limits:
+                cpu: 500m
+                memory: 512Mi
+              requests:
+                cpu: 100m
+                memory: 256Mi
+        cronjob:
+          activeDeadlineSeconds: 540
+          backoffLimit: 2
+          concurrencyPolicy: Forbid
+          failedJobsHistory: 5
+          schedule: 2-52/10 * * * *
+          startingDeadlineSeconds: 300
+          successfulJobsHistory: 3
+          suspend: true
+          timeZone: Etc/UTC
+        pod:
+          restartPolicy: Never
+        type: cronjob
+    persistence:
+      cache-gmail:
+        accessMode: ReadWriteOnce
+        advancedMounts:
+          gmail:
+            app:
+            - path: /cache
+        size: 1Gi
+        storageClass: ceph-rbd
+        type: persistentVolumeClaim
+      cache-migadu:
+        accessMode: ReadWriteOnce
+        advancedMounts:
+          migadu:
+            app:
+            - path: /cache
+        size: 1Gi
+        storageClass: ceph-rbd
+        type: persistentVolumeClaim
+      cache-outlook:
+        accessMode: ReadWriteOnce
+        advancedMounts:
+          outlook:
+            app:
+            - path: /cache
+        size: 1Gi
+        storageClass: ceph-rbd
+        type: persistentVolumeClaim
+      secrets:
+        advancedMounts:
+          gmail:
+            app:
+            - path: /secrets
+              readOnly: true
+          migadu:
+            app:
+            - path: /secrets
+              readOnly: true
+          outlook:
+            app:
+            - path: /secrets
+              readOnly: true
+        defaultMode: 256
+        name: imapsync-credentials
+        type: secret
+
--- kubernetes/apps/selfhosted/imapsync/app Kustomization: selfhosted/imapsync ExternalSecret: selfhosted/imapsync

+++ kubernetes/apps/selfhosted/imapsync/app Kustomization: selfhosted/imapsync ExternalSecret: selfhosted/imapsync

@@ -0,0 +1,33 @@

+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  labels:
+    app.kubernetes.io/name: imapsync
+    kustomize.toolkit.fluxcd.io/name: imapsync
+    kustomize.toolkit.fluxcd.io/namespace: selfhosted
+  name: imapsync
+  namespace: selfhosted
+spec:
+  dataFrom:
+  - find:
+      name:
+        regexp: ^STALWART.*
+  secretStoreRef:
+    kind: ClusterSecretStore
+    name: infisical
+  target:
+    name: imapsync-credentials
+    template:
+      data:
+        GMAIL_ADDRESS: '{{ .STALWART_GMAIL_ADDRESS }}'
+        MIGADU_ADDRESS: '{{ .STALWART_MIGADU_ADDRESS }}'
+        OUTLOOK_ADDRESS: '{{ .STALWART_OUTLOOK_ADDRESS }}'
+        STALWART_USER: '{{ .STALWART_PRIMARY_USER }}'
+        gmail-password: '{{ .STALWART_GMAIL_IMAP_PASSWORD }}'
+        migadu-password: '{{ .STALWART_MIGADU_IMAP_PASSWORD }}'
+        outlook-password: '{{ .STALWART_OUTLOOK_IMAP_PASSWORD }}'
+        stalwart-password: '{{ .STALWART_PRIMARY_PASSWORD }}'
+      engineVersion: v2
+      mergePolicy: Replace
+

@dunce-bot
Copy link
Contributor

dunce-bot bot commented Feb 22, 2026

--- HelmRelease: selfhosted/email-oauth2-proxy PersistentVolumeClaim: selfhosted/email-oauth2-proxy

+++ HelmRelease: selfhosted/email-oauth2-proxy PersistentVolumeClaim: selfhosted/email-oauth2-proxy

@@ -0,0 +1,18 @@

+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: email-oauth2-proxy
+  labels:
+    app.kubernetes.io/instance: email-oauth2-proxy
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: email-oauth2-proxy
+  namespace: selfhosted
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 100Mi
+  storageClassName: ceph-rbd
+
--- HelmRelease: selfhosted/email-oauth2-proxy Service: selfhosted/email-oauth2-proxy

+++ HelmRelease: selfhosted/email-oauth2-proxy Service: selfhosted/email-oauth2-proxy

@@ -0,0 +1,23 @@

+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: email-oauth2-proxy
+  labels:
+    app.kubernetes.io/instance: email-oauth2-proxy
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: email-oauth2-proxy
+    app.kubernetes.io/service: email-oauth2-proxy
+  namespace: selfhosted
+spec:
+  type: ClusterIP
+  ports:
+  - port: 1993
+    targetPort: 1993
+    protocol: TCP
+    name: imap
+  selector:
+    app.kubernetes.io/controller: main
+    app.kubernetes.io/instance: email-oauth2-proxy
+    app.kubernetes.io/name: email-oauth2-proxy
+
--- HelmRelease: selfhosted/email-oauth2-proxy Deployment: selfhosted/email-oauth2-proxy

+++ HelmRelease: selfhosted/email-oauth2-proxy Deployment: selfhosted/email-oauth2-proxy

@@ -0,0 +1,80 @@

+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: email-oauth2-proxy
+  labels:
+    app.kubernetes.io/controller: main
+    app.kubernetes.io/instance: email-oauth2-proxy
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: email-oauth2-proxy
+  annotations:
+    reloader.stakater.com/auto: 'true'
+  namespace: selfhosted
+spec:
+  revisionHistoryLimit: 3
+  replicas: 1
+  strategy:
+    type: Recreate
+  selector:
+    matchLabels:
+      app.kubernetes.io/controller: main
+      app.kubernetes.io/name: email-oauth2-proxy
+      app.kubernetes.io/instance: email-oauth2-proxy
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/controller: main
+        app.kubernetes.io/instance: email-oauth2-proxy
+        app.kubernetes.io/name: email-oauth2-proxy
+    spec:
+      enableServiceLinks: false
+      serviceAccountName: default
+      automountServiceAccountToken: true
+      hostIPC: false
+      hostNetwork: false
+      hostPID: false
+      dnsPolicy: ClusterFirst
+      containers:
+      - env:
+        - name: CACHE_STORE
+          value: /config/credstore.config
+        - name: LOGFILE
+          value: 'true'
+        envFrom:
+        - secretRef:
+            name: email-oauth2-proxy-secret
+        image: blacktirion/email-oauth2-proxy-docker:2.4.0
+        livenessProbe:
+          initialDelaySeconds: 10
+          periodSeconds: 30
+          tcpSocket:
+            port: 1993
+        name: app
+        readinessProbe:
+          initialDelaySeconds: 5
+          periodSeconds: 10
+          tcpSocket:
+            port: 1993
+        resources:
+          limits:
+            cpu: 200m
+            memory: 256Mi
+          requests:
+            cpu: 100m
+            memory: 128Mi
+        volumeMounts:
+        - mountPath: /app/emailproxy.config
+          name: config
+          readOnly: true
+          subPath: emailproxy.config
+        - mountPath: /config
+          name: data
+      volumes:
+      - configMap:
+          name: email-oauth2-proxy-config
+        name: config
+      - name: data
+        persistentVolumeClaim:
+          claimName: email-oauth2-proxy
+
--- HelmRelease: selfhosted/imapsync PersistentVolumeClaim: selfhosted/imapsync-cache-gmail

+++ HelmRelease: selfhosted/imapsync PersistentVolumeClaim: selfhosted/imapsync-cache-gmail

@@ -0,0 +1,18 @@

+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: imapsync-cache-gmail
+  labels:
+    app.kubernetes.io/instance: imapsync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: imapsync
+  namespace: selfhosted
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 1Gi
+  storageClassName: ceph-rbd
+
--- HelmRelease: selfhosted/imapsync PersistentVolumeClaim: selfhosted/imapsync-cache-migadu

+++ HelmRelease: selfhosted/imapsync PersistentVolumeClaim: selfhosted/imapsync-cache-migadu

@@ -0,0 +1,18 @@

+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: imapsync-cache-migadu
+  labels:
+    app.kubernetes.io/instance: imapsync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: imapsync
+  namespace: selfhosted
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 1Gi
+  storageClassName: ceph-rbd
+
--- HelmRelease: selfhosted/imapsync PersistentVolumeClaim: selfhosted/imapsync-cache-outlook

+++ HelmRelease: selfhosted/imapsync PersistentVolumeClaim: selfhosted/imapsync-cache-outlook

@@ -0,0 +1,18 @@

+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: imapsync-cache-outlook
+  labels:
+    app.kubernetes.io/instance: imapsync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: imapsync
+  namespace: selfhosted
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 1Gi
+  storageClassName: ceph-rbd
+
--- HelmRelease: selfhosted/imapsync CronJob: selfhosted/imapsync-outlook

+++ HelmRelease: selfhosted/imapsync CronJob: selfhosted/imapsync-outlook

@@ -0,0 +1,89 @@

+---
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+  name: imapsync-outlook
+  labels:
+    app.kubernetes.io/controller: outlook
+    app.kubernetes.io/instance: imapsync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: imapsync
+  namespace: selfhosted
+spec:
+  suspend: true
+  concurrencyPolicy: Forbid
+  startingDeadlineSeconds: 300
+  timeZone: Etc/UTC
+  schedule: 2-52/10 * * * *
+  successfulJobsHistoryLimit: 3
+  failedJobsHistoryLimit: 5
+  jobTemplate:
+    spec:
+      activeDeadlineSeconds: 540
+      backoffLimit: 2
+      template:
+        metadata:
+          labels:
+            app.kubernetes.io/controller: outlook
+            app.kubernetes.io/instance: imapsync
+            app.kubernetes.io/name: imapsync
+        spec:
+          enableServiceLinks: false
+          serviceAccountName: default
+          automountServiceAccountToken: true
+          hostIPC: false
+          hostNetwork: false
+          hostPID: false
+          dnsPolicy: ClusterFirst
+          restartPolicy: Never
+          containers:
+          - args:
+            - --host1=email-oauth2-proxy.selfhosted.svc.cluster.local
+            - --port1=1993
+            - --host2=stalwart.selfhosted.svc.cluster.local
+            - --port2=143
+            - --user1=$(OUTLOOK_ADDRESS)
+            - --passfile1=/secrets/outlook-password
+            - --user2=$(STALWART_USER)
+            - --passfile2=/secrets/stalwart-password
+            - --usecache
+            - --tmpdir=/cache
+            - --logdir=/cache/logs
+            - --errorsmax=200
+            - --pidfilelocking
+            - --pidfile=/cache/outlook.pid
+            env:
+            - name: OUTLOOK_ADDRESS
+              valueFrom:
+                secretKeyRef:
+                  key: OUTLOOK_ADDRESS
+                  name: imapsync-credentials
+            - name: STALWART_USER
+              valueFrom:
+                secretKeyRef:
+                  key: STALWART_USER
+                  name: imapsync-credentials
+            image: gilleslamiral/imapsync:2.314
+            name: app
+            resources:
+              limits:
+                cpu: 500m
+                memory: 512Mi
+              requests:
+                cpu: 100m
+                memory: 256Mi
+            volumeMounts:
+            - mountPath: /cache
+              name: cache-outlook
+            - mountPath: /secrets
+              name: secrets
+              readOnly: true
+          volumes:
+          - name: cache-outlook
+            persistentVolumeClaim:
+              claimName: imapsync-cache-outlook
+          - name: secrets
+            secret:
+              defaultMode: 256
+              secretName: imapsync-credentials
+
--- HelmRelease: selfhosted/imapsync CronJob: selfhosted/imapsync-gmail

+++ HelmRelease: selfhosted/imapsync CronJob: selfhosted/imapsync-gmail

@@ -0,0 +1,91 @@

+---
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+  name: imapsync-gmail
+  labels:
+    app.kubernetes.io/controller: gmail
+    app.kubernetes.io/instance: imapsync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: imapsync
+  namespace: selfhosted
+spec:
+  suspend: true
+  concurrencyPolicy: Forbid
+  startingDeadlineSeconds: 300
+  timeZone: Etc/UTC
+  schedule: '*/10 * * * *'
+  successfulJobsHistoryLimit: 3
+  failedJobsHistoryLimit: 5
+  jobTemplate:
+    spec:
+      activeDeadlineSeconds: 540
+      backoffLimit: 2
+      template:
+        metadata:
+          labels:
+            app.kubernetes.io/controller: gmail
+            app.kubernetes.io/instance: imapsync
+            app.kubernetes.io/name: imapsync
+        spec:
+          enableServiceLinks: false
+          serviceAccountName: default
+          automountServiceAccountToken: true
+          hostIPC: false
+          hostNetwork: false
+          hostPID: false
+          dnsPolicy: ClusterFirst
+          restartPolicy: Never
+          containers:
+          - args:
+            - --gmail1
+            - --host2=stalwart.selfhosted.svc.cluster.local
+            - --port2=143
+            - --user1=$(GMAIL_ADDRESS)
+            - --passfile1=/secrets/gmail-password
+            - --user2=$(STALWART_USER)
+            - --passfile2=/secrets/stalwart-password
+            - --skipcrossduplicates
+            - --exclude=\[Gmail\]/(All Mail|Important|Starred|Spam|Chats|Trash)
+            - --usecache
+            - --tmpdir=/cache
+            - --logdir=/cache/logs
+            - --maxmessagespersecond=3
+            - --errorsmax=200
+            - --pidfilelocking
+            - --pidfile=/cache/gmail.pid
+            env:
+            - name: GMAIL_ADDRESS
+              valueFrom:
+                secretKeyRef:
+                  key: GMAIL_ADDRESS
+                  name: imapsync-credentials
+            - name: STALWART_USER
+              valueFrom:
+                secretKeyRef:
+                  key: STALWART_USER
+                  name: imapsync-credentials
+            image: gilleslamiral/imapsync:2.314
+            name: app
+            resources:
+              limits:
+                cpu: 500m
+                memory: 512Mi
+              requests:
+                cpu: 100m
+                memory: 256Mi
+            volumeMounts:
+            - mountPath: /cache
+              name: cache-gmail
+            - mountPath: /secrets
+              name: secrets
+              readOnly: true
+          volumes:
+          - name: cache-gmail
+            persistentVolumeClaim:
+              claimName: imapsync-cache-gmail
+          - name: secrets
+            secret:
+              defaultMode: 256
+              secretName: imapsync-credentials
+
--- HelmRelease: selfhosted/imapsync CronJob: selfhosted/imapsync-migadu

+++ HelmRelease: selfhosted/imapsync CronJob: selfhosted/imapsync-migadu

@@ -0,0 +1,90 @@

+---
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+  name: imapsync-migadu
+  labels:
+    app.kubernetes.io/controller: migadu
+    app.kubernetes.io/instance: imapsync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: imapsync
+  namespace: selfhosted
+spec:
+  suspend: true
+  concurrencyPolicy: Forbid
+  startingDeadlineSeconds: 300
+  timeZone: Etc/UTC
+  schedule: 4-54/10 * * * *
+  successfulJobsHistoryLimit: 3
+  failedJobsHistoryLimit: 5
+  jobTemplate:
+    spec:
+      activeDeadlineSeconds: 540
+      backoffLimit: 2
+      template:
+        metadata:
+          labels:
+            app.kubernetes.io/controller: migadu
+            app.kubernetes.io/instance: imapsync
+            app.kubernetes.io/name: imapsync
+        spec:
+          enableServiceLinks: false
+          serviceAccountName: default
+          automountServiceAccountToken: true
+          hostIPC: false
+          hostNetwork: false
+          hostPID: false
+          dnsPolicy: ClusterFirst
+          restartPolicy: Never
+          containers:
+          - args:
+            - --host1=imap.migadu.com
+            - --port1=993
+            - --ssl1
+            - --host2=stalwart.selfhosted.svc.cluster.local
+            - --port2=143
+            - --user1=$(MIGADU_ADDRESS)
+            - --passfile1=/secrets/migadu-password
+            - --user2=$(STALWART_USER)
+            - --passfile2=/secrets/stalwart-password
+            - --usecache
+            - --tmpdir=/cache
+            - --logdir=/cache/logs
+            - --errorsmax=200
+            - --pidfilelocking
+            - --pidfile=/cache/migadu.pid
+            env:
+            - name: MIGADU_ADDRESS
+              valueFrom:
+                secretKeyRef:
+                  key: MIGADU_ADDRESS
+                  name: imapsync-credentials
+            - name: STALWART_USER
+              valueFrom:
+                secretKeyRef:
+                  key: STALWART_USER
+                  name: imapsync-credentials
+            image: gilleslamiral/imapsync:2.314
+            name: app
+            resources:
+              limits:
+                cpu: 500m
+                memory: 512Mi
+              requests:
+                cpu: 100m
+                memory: 256Mi
+            volumeMounts:
+            - mountPath: /cache
+              name: cache-migadu
+            - mountPath: /secrets
+              name: secrets
+              readOnly: true
+          volumes:
+          - name: cache-migadu
+            persistentVolumeClaim:
+              claimName: imapsync-cache-migadu
+          - name: secrets
+            secret:
+              defaultMode: 256
+              secretName: imapsync-credentials
+
--- HelmRelease: selfhosted/stalwart ConfigMap: selfhosted/stalwart-stalwart-mail

+++ HelmRelease: selfhosted/stalwart ConfigMap: selfhosted/stalwart-stalwart-mail

@@ -0,0 +1,176 @@

+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: stalwart-stalwart-mail
+  labels:
+    app.kubernetes.io/name: stalwart-mail
+    app.kubernetes.io/instance: stalwart
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/component: main
+data:
+  config.toml: |
+    [auth]
+      [auth.dkim]
+        verify = "relaxed"
+
+        [[auth.dkim.sign]]
+          if = "listener != 'smtp' && is_local_domain('', sender_domain)"
+          then = "['rsa-' + sender_domain, 'ed25519-' + sender_domain]"
+
+        [[auth.dkim.sign]]
+          else = false
+
+    [authentication]
+      [authentication.fallback-admin]
+        secret = "%{env:FALLBACK_ADMIN_SECRET}%"
+        user = "admin"
+
+    [certificate]
+      [certificate.default]
+        cert = "%{file:/opt/stalwart/etc/certs/tls.crt}%"
+        default = true
+        private-key = "%{file:/opt/stalwart/etc/certs/tls.key}%"
+
+    [cluster]
+      node-id = "%{env:POD_INDEX}%"
+
+    [config]
+      local-keys = ["store.*", "directory.*", "tracer.*", "!server.blocked-ip.*", "server.*", "authentication.fallback-admin.*", "cluster.*", "config.local-keys.*", "storage.data", "storage.blob", "storage.lookup", "storage.fts", "storage.directory", "certificate.*", "server.allowed-ip.*", "metrics.prometheus.*", "auth.dkim.sign.*", "auth.dkim.verify", "http.use-x-forwarded", "report.domain"]
+
+    [directory]
+      [directory.internal]
+        store = "rocksdb"
+        type = "internal"
+
+    [http]
+      use-x-forwarded = true
+
+    [metrics]
+      [metrics.prometheus]
+        enable = true
+        [metrics.prometheus.auth]
+          secret = "%{env:METRICS_SECRET}%"
+          username = "%{env:METRICS_USERNAME}%"
+
+    [queue]
+      [queue.outbound]
+        hostname = "mail...PLACEHOLDER_SECRET_DOMAIN.."
+
+    [remote]
+      [remote.gmail-relay]
+        address = "smtp.gmail.com"
+        port = 587
+        protocol = "smtp"
+        [remote.gmail-relay.auth]
+          secret = "%{env:GMAIL_SMTP_PASSWORD}%"
+          username = "%{env:GMAIL_ADDRESS}%"
+        [remote.gmail-relay.tls]
+          implicit = false
+          start-tls = true
+      [remote.migadu-relay]
+        address = "smtp.migadu.com"
+        port = 587
+        protocol = "smtp"
+        [remote.migadu-relay.auth]
+          secret = "%{env:MIGADU_SMTP_PASSWORD}%"
+          username = "%{env:MIGADU_ADDRESS}%"
+        [remote.migadu-relay.tls]
+          implicit = false
+          start-tls = true
+      [remote.outlook-relay]
+        address = "smtp.office365.com"
+        port = 587
+        protocol = "smtp"
+        [remote.outlook-relay.auth]
+          secret = "%{env:OUTLOOK_SMTP_PASSWORD}%"
+          username = "%{env:OUTLOOK_ADDRESS}%"
+        [remote.outlook-relay.tls]
+          implicit = false
+          start-tls = true
+
+    [server]
+      [server.allowed-ip]
+        "10.0.0.0/8" = ""
+        "10.42.0.1/16" = ""
+        "172.16.0.0/12" = ""
+        "192.168.0.0/16" = ""
+      [server.listener]
+        [server.listener.http]
+          bind = ["[::]:80"]
+          protocol = "http"
+        [server.listener.https]
+          bind = ["[::]:443"]
+          protocol = "http"
+          [server.listener.https.tls]
+            implicit = true
+        [server.listener.imap]
+          bind = ["[::]:143"]
+          protocol = "imap"
+        [server.listener.imaptls]
+          bind = ["[::]:993"]
+          protocol = "imap"
+          [server.listener.imaptls.tls]
+            implicit = true
+        [server.listener.pop3]
+          bind = ["[::]:110"]
+          protocol = "pop3"
+        [server.listener.pop3s]
+          bind = ["[::]:995"]
+          protocol = "pop3"
+          [server.listener.pop3s.tls]
+            implicit = true
+        [server.listener.sieve]
+          bind = ["[::]:4190"]
+          protocol = "managesieve"
+        [server.listener.smtp]
+          bind = ["[::]:25"]
+          protocol = "smtp"
+        [server.listener.submission]
+          bind = ["[::]:587"]
+          protocol = "smtp"
+        [server.listener.submissions]
+          bind = ["[::]:465"]
+          protocol = "smtp"
+          [server.listener.submissions.tls]
+            implicit = true
+
+    [spam-filter]
+      auto-update = true
+      [spam-filter.dnsbl]
+        card-is-ham = true
+      [spam-filter.header]
+        is-spam = "X-Spam-Status"
+      [spam-filter.score]
+        discard = 0
+        reject = 0
+        spam = 8
+
+    [storage]
+      blob = "rocksdb"
+      data = "rocksdb"
+      directory = "internal"
+      fts = "rocksdb"
+      lookup = "rocksdb"
+
+    [store]
+      [store.rocksdb]
+        compression = "lz4"
+        path = "/data"
+        type = "rocksdb"
+        write-buffer-size = 268435456
+
+    [tracer]
+      [tracer.otel]
+        enable = false
+        endpoint = "https://127.0.0.1/otel"
+        headers = []
+        level = "info"
+        transport = "grpc"
+        type = "open-telemetry"
+      [tracer.stdout]
+        ansi = false
+        enable = true
+        level = "info"
+        type = "stdout"
+
--- HelmRelease: selfhosted/stalwart PersistentVolumeClaim: selfhosted/stalwart-stalwart-mail

+++ HelmRelease: selfhosted/stalwart PersistentVolumeClaim: selfhosted/stalwart-stalwart-mail

@@ -0,0 +1,17 @@

+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: stalwart-stalwart-mail
+  labels:
+    app.kubernetes.io/name: stalwart-mail
+    app.kubernetes.io/instance: stalwart
+    app.kubernetes.io/managed-by: Helm
+spec:
+  accessModes:
+  - ReadWriteOnce
+  resources:
+    requests:
+      storage: 50Gi
+  storageClassName: ceph-rbd
+
--- HelmRelease: selfhosted/stalwart Service: selfhosted/stalwart-stalwart-mail

+++ HelmRelease: selfhosted/stalwart Service: selfhosted/stalwart-stalwart-mail

@@ -0,0 +1,64 @@

+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: stalwart-stalwart-mail
+  labels:
+    app.kubernetes.io/name: stalwart-mail
+    app.kubernetes.io/instance: stalwart
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/component: main
+  annotations:
+    external-dns.alpha.kubernetes.io/hostname: mail...PLACEHOLDER_SECRET_DOMAIN..
+    lbipam.cilium.io/ips: 10.100.0.30
+spec:
+  type: LoadBalancer
+  ipFamilyPolicy: SingleStack
+  ipFamilies:
+  - IPv4
+  ports:
+  - port: 80
+    targetPort: http
+    protocol: TCP
+    name: http
+  - port: 443
+    targetPort: https
+    protocol: TCP
+    name: https
+  - port: 143
+    targetPort: imap
+    protocol: TCP
+    name: imap
+  - port: 993
+    targetPort: imaptls
+    protocol: TCP
+    name: imaptls
+  - port: 110
+    targetPort: pop3
+    protocol: TCP
+    name: pop3
+  - port: 995
+    targetPort: pop3s
+    protocol: TCP
+    name: pop3s
+  - port: 4190
+    targetPort: sieve
+    protocol: TCP
+    name: sieve
+  - port: 25
+    targetPort: smtp
+    protocol: TCP
+    name: smtp
+  - port: 587
+    targetPort: submission
+    protocol: TCP
+    name: submission
+  - port: 465
+    targetPort: submissions
+    protocol: TCP
+    name: submissions
+  selector:
+    app.kubernetes.io/name: stalwart-mail
+    app.kubernetes.io/instance: stalwart
+    app.kubernetes.io/component: main
+
--- HelmRelease: selfhosted/stalwart StatefulSet: selfhosted/stalwart-stalwart-mail

+++ HelmRelease: selfhosted/stalwart StatefulSet: selfhosted/stalwart-stalwart-mail

@@ -0,0 +1,139 @@

+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: stalwart-stalwart-mail
+  labels:
+    app.kubernetes.io/name: stalwart-mail
+    app.kubernetes.io/instance: stalwart
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/component: main
+spec:
+  replicas: 1
+  podManagementPolicy: Parallel
+  ordinals:
+    start: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: stalwart-mail
+      app.kubernetes.io/instance: stalwart
+      app.kubernetes.io/component: main
+  template:
+    metadata:
+      annotations:
+        config-hash: 8df30b7f12f3ef38a9162bd3a888c4401e3f370b156420af20806865716fd351
+        secret-env-hash: f52d711103d50a437830c6fbcd04fb4bab49a0f82f6d26d1c791c6e8488dd090
+      labels:
+        app.kubernetes.io/name: stalwart-mail
+        app.kubernetes.io/instance: stalwart
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/component: main
+    spec:
+      serviceAccountName: default
+      securityContext: {}
+      containers:
+      - name: stalwart-mail
+        securityContext: {}
+        image: ghcr.io/stalwartlabs/stalwart:v0.15.5
+        imagePullPolicy: IfNotPresent
+        env:
+        - name: POD_INDEX
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.labels['apps.kubernetes.io/pod-index']
+        - name: POD_IP
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        - name: URL
+          value: http://localhost:80
+        - name: CREDENTIALS
+          value: admin:$(FALLBACK_ADMIN_SECRET)
+        envFrom:
+        - secretRef:
+            name: stalwart-secret
+        ports:
+        - name: http
+          containerPort: 80
+          protocol: TCP
+        - name: https
+          containerPort: 443
+          protocol: TCP
+        - name: imap
+          containerPort: 143
+          protocol: TCP
+        - name: imaptls
+          containerPort: 993
+          protocol: TCP
+        - name: pop3
+          containerPort: 110
+          protocol: TCP
+        - name: pop3s
+          containerPort: 995
+          protocol: TCP
+        - name: sieve
+          containerPort: 4190
+          protocol: TCP
+        - name: smtp
+          containerPort: 25
+          protocol: TCP
+        - name: submission
+          containerPort: 587
+          protocol: TCP
+        - name: submissions
+          containerPort: 465
+          protocol: TCP
+        livenessProbe:
+          httpGet:
+            httpHeaders:
+            - name: X-Forwarded-For
+              value: 127.0.0.1
+            path: /healthz/live
+            port: http
+          initialDelaySeconds: 5
+          periodSeconds: 10
+        readinessProbe:
+          httpGet:
+            httpHeaders:
+            - name: X-Forwarded-For
+              value: 127.0.0.1
+            path: /healthz/ready
+            port: http
+          initialDelaySeconds: 30
+          periodSeconds: 10
+        resources:
+          limits:
+            cpu: 2000m
+            memory: 4Gi
+          requests:
+            cpu: 500m
+            memory: 1Gi
+        volumeMounts:
+        - name: data
+          mountPath: /data
+        - name: data
+          mountPath: /data/blobs
+          subPath: blobs
+        - name: data
+          mountPath: /data/queue
+          subPath: queue
+        - name: data
+          mountPath: /data/reports
+          subPath: reports
+        - name: config
+          mountPath: /opt/stalwart/etc/config.toml
+          subPath: config.toml
+          readOnly: true
+        - name: certificate
+          mountPath: /opt/stalwart/etc/certs
+      volumes:
+      - name: config
+        configMap:
+          name: stalwart-stalwart-mail
+      - name: certificate
+        secret:
+          secretName: -.PLACEHOLDER_SECRET_DOMAIN..-tls
+      - name: data
+        persistentVolumeClaim:
+          claimName: stalwart-stalwart-mail
+
--- HelmRelease: selfhosted/stalwart Ingress: selfhosted/stalwart-stalwart-mail

+++ HelmRelease: selfhosted/stalwart Ingress: selfhosted/stalwart-stalwart-mail

@@ -0,0 +1,30 @@

+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: stalwart-stalwart-mail
+  labels:
+    app.kubernetes.io/name: stalwart-mail
+    app.kubernetes.io/instance: stalwart
+    app.kubernetes.io/managed-by: Helm
+  annotations:
+    authentik.home.arpa/internal: 'true'
+    external-dns.alpha.kubernetes.io/target: internal...PLACEHOLDER_SECRET_DOMAIN..
+spec:
+  ingressClassName: internal
+  tls:
+  - hosts:
+    - mail...PLACEHOLDER_SECRET_DOMAIN..
+    secretName: -.PLACEHOLDER_SECRET_DOMAIN..-tls
+  rules:
+  - host: mail...PLACEHOLDER_SECRET_DOMAIN..
+    http:
+      paths:
+      - path: /
+        pathType: ImplementationSpecific
+        backend:
+          service:
+            name: stalwart-stalwart-mail
+            port:
+              number: 80
+

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant