# --------------------------------------------------------------------------- # Maddy — self-hosted SMTP server for AnyDrop magic-link emails. # # BEFORE FIRST DEPLOYMENT, the following DNS records must exist for # anydrop.arthurbarre.fr (OVH): # - A mail.anydrop.arthurbarre.fr → # - MX 10 anydrop.arthurbarre.fr → mail.anydrop.arthurbarre.fr # - TXT anydrop.arthurbarre.fr → "v=spf1 a mx ~all" # - TXT _dmarc.anydrop.arthurbarre.fr → "v=DMARC1; p=none; rua=mailto:arthurbarre.js@gmail.com" # # The DKIM public key is generated by maddy on first start and stored in the # PVC. Extract it once with: # kubectl -n anydrop exec -it deploy/maddy -- cat /data/dkim_keys/anydrop.arthurbarre.fr_default.dns # Then publish it as: # TXT default._domainkey.anydrop.arthurbarre.fr → # # Also make sure PTR (reverse DNS) for the public IP points to # mail.anydrop.arthurbarre.fr — configure at the Proxmox / ISP level. # --------------------------------------------------------------------------- apiVersion: v1 kind: ConfigMap metadata: name: maddy-config namespace: anydrop data: maddy.conf: | $(hostname) = mail.anydrop.arthurbarre.fr $(primary_domain) = anydrop.arthurbarre.fr $(local_domains) = $(primary_domain) tls off # ------------------------------------------------------------------- # Outbound pipeline — sign with DKIM, send directly to destination MX. # ------------------------------------------------------------------- (local_routing) { destination postmaster $(local_domains) { reject 550 5.1.1 "No local mailboxes — outbound only" } default_destination { modify { dkim $(primary_domain) default (1024) } deliver_to &remote_queue } } target.queue remote_queue { target &remote_delivery max_parallelism 16 max_tries 20 } target.remote remote_delivery { limits { destination rate 20 1s destination concurrency 10 } mx_auth { dane mtasts { cache fs fs_dir mtasts_cache/ } local_policy { min_tls_level none min_mx_level none } } } # ------------------------------------------------------------------- # SMTP submission endpoint (internal only — cluster-ip service). # No TLS required in-cluster; the server process talks to maddy over # the flat pod network. # ------------------------------------------------------------------- smtp tcp://0.0.0.0:587 { limits { all rate 100 1s all concurrency 50 } source $(local_domains) { reject 501 5.1.8 "Non-local sender refused" } default_source { destination postmaster $(local_domains) { reject 550 5.1.1 "Cannot send to local — outbound only" } default_destination { modify { dkim $(primary_domain) default (1024) } deliver_to &remote_queue } } } --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: maddy-data namespace: anydrop spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: maddy namespace: anydrop spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: maddy template: metadata: labels: app: maddy spec: containers: - name: maddy image: foxcpp/maddy:0.8 args: ["-config", "/etc/maddy/maddy.conf"] ports: - containerPort: 587 name: submission - containerPort: 25 name: smtp volumeMounts: - name: config mountPath: /etc/maddy - name: data mountPath: /data workingDir: /data readinessProbe: tcpSocket: port: 587 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: tcpSocket: port: 587 initialDelaySeconds: 30 periodSeconds: 30 resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "256Mi" cpu: "200m" volumes: - name: config configMap: name: maddy-config - name: data persistentVolumeClaim: claimName: maddy-data --- apiVersion: v1 kind: Service metadata: name: maddy namespace: anydrop spec: type: ClusterIP selector: app: maddy ports: - name: submission port: 587 targetPort: 587 --- # Optional: expose port 25 on a NodePort if you want maddy to also receive # inbound mail (bounces, replies). For Phase 1 outbound-only, this can stay # commented out — direct-to-MX delivery does not require inbound. # # apiVersion: v1 # kind: Service # metadata: # name: maddy-smtp # namespace: anydrop # spec: # type: NodePort # selector: # app: maddy # ports: # - name: smtp # port: 25 # targetPort: 25 # nodePort: 30025