Files
Wabenhain/docs/EINRICHTUNG.md
T
Nick SzaunigandClaude Opus 5.5 5c6d99d0a3 docs(einrichtung): ApiCore sync uses an API key, not a service account
The setup guide still listed APICORE_SYNC_EMAIL/APICORE_SYNC_PASSWORD,
which the client no longer reads since 3bc0693.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 01:09:53 +02:00

23 KiB

Wabenhain einrichten — von null bis live

Diese Anleitung führt durch alles, was außerhalb des Codes eingerichtet werden muss, damit der Shop in Kubernetes vollständig läuft: Geheimnisse (Secrets), Microsoft-Anmeldung, Mailversand über Microsoft 365, Stripe und die optionalen Dienste. Sie setzt nichts voraus, was nicht hier steht.

Zeitbedarf: etwa 2 Stunden für Pflicht und Mailversand, plus Wartezeiten bei Microsoft (bis zu 30 Minuten) und Stripe.

Reihenfolge:

  1. Werkzeuge und Zugänge bereitlegen
  2. Grundwerte erzeugen (Datenbank, Sitzungen, Admin)
  3. Microsoft Entra ID: App-Registrierung (Anmeldung und Mailversand)
  4. Microsoft 365: Absender-Postfach freischalten
  5. Stripe
  6. Optionale Dienste
  7. Secret und ConfigMap ausfüllen
  8. Ausrollen
  9. Prüfen
  10. Wartung: was abläuft und was zu tun ist
  11. Fehlerbehebung

Sicherheitsregel für das ganze Dokument: Echte Werte gehören ausschließlich in k8s/02-secret.yaml (per .gitignore ausgeschlossen) bzw. direkt ins Cluster. Nie committen, nie in Chats, Tickets oder Screenshots kopieren. Die ConfigMap (k8s/01-configmap.yaml) ist unverschlüsselt und im Cluster für jeden mit Lesezugriff im Klartext sichtbar — dort steht nichts Geheimes.


1. Werkzeuge und Zugänge

Auf deinem Rechner:

Werkzeug Wofür Prüfen mit
kubectl mit Zugriff auf das Cluster Secrets und Manifeste anwenden kubectl get nodes
openssl Zufallswerte erzeugen openssl version
Node.js Datenbank-Passwort URL-kodieren node --version
PowerShell mit Modul ExchangeOnlineManagement Postfach für den Mailversand freischalten Get-Module -ListAvailable ExchangeOnlineManagement

Modul einmalig installieren (PowerShell):

Install-Module ExchangeOnlineManagement -Scope CurrentUser

Zugänge, die du brauchst:

Zugang Rolle Wofür
Microsoft Entra Admin Center (entra.microsoft.com) Anwendungsadministrator oder Globaler Administrator App-Registrierung, Administratorzustimmung
Microsoft 365 Admin Center (admin.microsoft.com) Exchange-Administrator Freigegebenes Postfach anlegen und freischalten
Stripe-Dashboard (dashboard.stripe.com) Administrator API-Schlüssel, Webhook
Gitea (git.encry.de) Schreibrecht am Repository Image-Build (siehe k8s/README.md)

2. Grundwerte erzeugen

Diese Werte denkst du dir nicht aus, sondern lässt sie erzeugen. Notiere sie vorübergehend in einem Passwort-Manager, nicht in einer Textdatei.

Datenbank-Passwort (DB_PASSWORD und DATABASE_URL)

openssl rand -base64 32 | tr -d '=+/' | head -c 32

Das Passwort steht an zwei Stellen im Secret:

  • DB_PASSWORD: roh, genau wie erzeugt.
  • DATABASE_URL: URL-kodiert. Sonderzeichen würden sonst als Trennzeichen der URL gelesen.
    node -e "console.log(encodeURIComponent('HIER-DAS-PASSWORT'))"
    
    Ergebnis einsetzen: postgresql://wabenhain:<KODIERTES-PASSWORT>@wabenhain-postgres:5432/wabenhain?schema=public

Bei MySQL statt Postgres: mysql://wabenhain:<KODIERTES-PASSWORT>@wabenhain-mysql:3306/wabenhain und in der ConfigMap DATABASE_PROVIDER: "mysql".

Sitzungsschlüssel (SESSION_SECRET)

openssl rand -base64 32

Signiert alle Login-Cookies. Wird er bekannt, lassen sich Sitzungen fälschen — auch die eines Admins. Ihn zu ändern meldet alle nur ab.

Erstes Admin-Konto (ADMIN_EMAIL, ADMIN_PASSWORD)

  • ADMIN_EMAIL: eine echte, nicht erratbare Adresse, z. B. [email protected] — nicht admin@… oder info@…. Sonst könnte jemand diese Adresse vorher als Kundenkonto registrieren. An diese Adresse gehen außerdem die Benachrichtigungen über eingegangene Widerrufe.
  • ADMIN_PASSWORD:
    openssl rand -base64 24 | tr -d '=+/' | head -c 28
    

Das Konto wird beim ersten Start angelegt. Beim ersten Login verlangt der Admin-Bereich die Einrichtung der Zwei-Faktor-Authentifizierung (Authenticator-App bereithalten).


3. Microsoft Entra ID: App-Registrierung

Eine App-Registrierung erledigt zwei Aufgaben:

  • „Mit Microsoft anmelden“ (Single Sign-on im Kundenkonto)
  • Mailversand über Microsoft 365 per OAuth 2.0 — ohne Passwort

Eine zweite Unternehmensanwendung ist nicht nötig.

3.1 App registrieren

  1. Entra Admin Center → Identität → Anwendungen → App-Registrierungen → Neue Registrierung.
  2. Name: z. B. Wabenhain Shop.
  3. Unterstützte Kontotypen: „Nur Konten in diesem Organisationsverzeichnis“ (Single Tenant).
  4. Umleitungs-URI: Plattform Web, Adresse https://wabenhain.com/api/auth/entra/callback — zeichengenau, mit https, ohne Schrägstrich am Ende.
  5. Registrieren.

Auf der Seite Übersicht notieren:

Angezeigt als Gehört in
Anwendungs-ID (Client-ID) ENTRA_CLIENT_ID
Verzeichnis-ID (Mandanten-ID) ENTRA_TENANT_ID

ENTRA_REDIRECT_URI ist die Adresse aus Schritt 4: https://wabenhain.com/api/auth/entra/callback.

3.2 Geheimen Clientschlüssel anlegen

  1. In der App → Zertifikate & Geheimnisse → Geheime Clientschlüssel → Neuer geheimer Clientschlüssel.
  2. Beschreibung z. B. Shop 2026, Ablauf 24 Monate (Maximum).
  3. Sofort die Spalte „Wert“ kopieren → ENTRA_CLIENT_SECRET.

⚠ Nicht die „Geheimnis-ID“ kopieren — das ist der häufigste Fehler (Microsoft meldet dann AADSTS7000215). Der Wert wird nur dieses eine Mal angezeigt; ist er weg, einen neuen Schlüssel anlegen.

Ablaufdatum sofort in den Kalender eintragen, mit Erinnerung vier Wochen vorher. Läuft der Schlüssel ab, fallen Microsoft-Anmeldung und Mailversand gleichzeitig aus (siehe Wartung).

3.3 API-Berechtigungen

In der App → API-Berechtigungen. Es braucht zwei Arten:

Berechtigung API Typ Wofür
openid, email, profile, User.Read Microsoft Graph Delegiert Anmeldung („Mit Microsoft anmelden“)
SMTP.SendAsApp Office 365 Exchange Online Anwendung Mailversand

User.Read ist meist schon vorhanden. Die übrigen hinzufügen:

Für die Anmeldung (falls noch nicht vorhanden):

  1. Berechtigung hinzufügen → Microsoft Graph → Delegierte Berechtigungen.
  2. openid, email, profile anhaken → Berechtigungen hinzufügen.

Für den Mailversand:

  1. Berechtigung hinzufügen → Reiter „Von meiner Organisation verwendete APIs“.
  2. Nach Office 365 Exchange Online suchen und auswählen. (Die Berechtigung liegt nicht unter Microsoft Graph.)
  3. Anwendungsberechtigungen (nicht „Delegierte“) → SMTP.SendAsApp anhaken → Berechtigungen hinzufügen.

Danach: „Administratorzustimmung für <Organisation> erteilen“ klicken und bestätigen. In der Liste muss bei jeder Berechtigung ein grünes „Erteilt für …“ stehen.

Was SMTP.SendAsApp erlaubt: Die App darf per SMTP senden — aber nur mit Postfächern, die ihr in Exchange ausdrücklich freigegeben werden (Abschnitt 4). Ohne diese Freigabe kann sie nichts senden.

3.4 Objekt-ID der Unternehmensanwendung notieren

  1. Entra → Identität → Anwendungen → Unternehmensanwendungen.
  2. Filter „Anwendungstyp“ auf Alle Anwendungen stellen, nach Wabenhain Shop suchen, öffnen.
  3. Auf der Übersicht die Objekt-ID kopieren.

⚠ Das ist eine andere Objekt-ID als die auf der Seite der App-Registrierung. Für Exchange zählt nur die der Unternehmensanwendung.


4. Microsoft 365: Absender-Postfach freischalten

Alle Shop-Mails (Bestellbestätigung, Versandmeldung, Passwort-Reset, Widerrufsbestätigung …) gehen von einem Postfach aus, z. B. [email protected].

Empfohlen: ein freigegebenes Postfach (Shared Mailbox). Es braucht keine Lizenz und hat kein Passwort, mit dem sich jemand anmelden könnte. Die App sendet ohne Anmeldung einer Person.

4.1 Freigegebenes Postfach anlegen (falls noch nicht vorhanden)

  1. Microsoft 365 Admin Center → Teams und Gruppen → Freigegebene Postfächer → Freigegebenes Postfach hinzufügen.
  2. Name Wabenhain, E-Mail [email protected] → Änderungen speichern.
  3. Optional: Mitglieder hinzufügen, die Antworten der Kundschaft lesen sollen.

4.2 App für genau dieses Postfach freischalten

In PowerShell (als Exchange-Administrator). Platzhalter ersetzen:

  • <CLIENT-ID> = ENTRA_CLIENT_ID aus 3.1
  • <OBJEKT-ID> = Objekt-ID der Unternehmensanwendung aus 3.4
Connect-ExchangeOnline

# 1. App in Exchange bekannt machen
New-ServicePrincipal -AppId <CLIENT-ID> -ObjectId <OBJEKT-ID> -DisplayName "Wabenhain Shop"

# 2. Zugriff NUR auf das Absender-Postfach
Add-MailboxPermission -Identity "[email protected]" -User <OBJEKT-ID> -AccessRights FullAccess

# 3. Authentifiziertes SMTP für dieses Postfach erlauben (auch OAuth läuft darüber)
Set-CASMailbox -Identity "[email protected]" -SmtpClientAuthenticationDisabled $false

Empfohlene Absicherung: Authentifiziertes SMTP für alle anderen Postfächer abschalten. Die Einstellung am Absender-Postfach aus Schritt 3 hat Vorrang, der Shop sendet weiter.

Set-TransportConfig -SmtpClientAuthenticationDisabled $true

4.3 Kontrolle

Get-ServicePrincipal | Where-Object DisplayName -eq "Wabenhain Shop"
Get-MailboxPermission -Identity "[email protected]" | Where-Object { $_.User -like "*Wabenhain*" }
Get-CASMailbox -Identity "[email protected]" | Format-List SmtpClientAuthenticationDisabled   # muss False sein
Disconnect-ExchangeOnline -Confirm:$false

Änderungen in Exchange brauchen bis zu 30 Minuten, bis sie greifen.

Ergebnis für die Konfiguration:

Wert Wo Inhalt
SMTP_USER Secret [email protected]
SMTP_FROM ConfigMap Wabenhain <[email protected]> — dieselbe Adresse wie SMTP_USER

Server (smtp.office365.com), Port (587, STARTTLS) und Anmeldeverfahren (OAuth 2.0) sind fest im Code — dafür gibt es keine Einstellungen. Ein SMTP-Passwort gibt es nicht.


5. Stripe

Im Stripe-Dashboard, Live-Modus (Schalter oben rechts, nicht „Testmodus“).

5.1 API-Schlüssel

Entwickler → API-Schlüssel:

Angezeigt als Gehört in Wo
Veröffentlichbarer Schlüssel (pk_live_…) STRIPE_PUBLISHABLE_KEY ConfigMap (nicht geheim)
Geheimer Schlüssel (sk_live_…) STRIPE_SECRET_KEY Secret

Sicherer als der volle geheime Schlüssel ist ein eingeschränkter Schlüssel (rk_live_…) mit Schreibrecht auf Checkout Sessions, Refunds und Leserecht auf Charges/Payment Intents.

Beide Schlüssel müssen aus demselben Modus stammen. Mit sk_test sehen Bestellungen erfolgreich aus, ohne dass Geld fließt.

5.2 Webhook

Entwickler → Webhooks → Endpunkt hinzufügen:

  • URL: https://wabenhain.com/api/checkout/webhook
  • Ereignisse:
    • checkout.session.completed
    • checkout.session.async_payment_succeeded
    • checkout.session.async_payment_failed
    • checkout.session.expired
    • charge.refunded
    • charge.dispute.created

Nach dem Anlegen Signaturgeheimnis anzeigen (whsec_…) → STRIPE_WEBHOOK_SECRET im Secret.

Ohne Webhook-Secret wird keine Zahlung bestätigt: Bestellungen bleiben auf „Neu“, es gibt weder Rechnung noch Bestätigungsmail.

5.3 Zahlungsarten

Einstellungen → Zahlungsmethoden: gewünschte Arten aktivieren (Karte, SEPA-Lastschrift, PayPal, Klarna, Apple Pay, Google Pay). Der Shop zeigt automatisch an, was dort aktiv ist. Für Apple Pay die Domain wabenhain.com unter Apple Pay registrieren.


6. Optionale Dienste

Alle folgenden Werte dürfen leer bleiben. Die Funktion ruht dann, der Shop läuft weiter. Ausführliche Anleitungen stehen in .env.example im jeweiligen Abschnitt.

Dienst Werte Ohne sie Einrichtung
Cloudflare Turnstile (CAPTCHA) — dringend empfohlen TURNSTILE_SECRET_KEY (Secret), NEXT_PUBLIC_TURNSTILE_SITE_KEY (ConfigMap) Keine Bot-Abwehr an Formularen dash.cloudflare.com → Turnstile → Widget für wabenhain.com anlegen. Beide setzen oder beide leer.
Google-Rezensionen GOOGLE_PLACES_API_KEY (Secret), GOOGLE_PLACE_ID (ConfigMap) Statische Beispiel-Stimmen Google Cloud: „Places API (New)“ aktivieren, Abrechnung an, API-Schlüssel ohne Referrer-Beschränkung
DHL (Versandmarken, Packstation, Sendungsverfolgung) DHL_API_KEY, DHL_API_SECRET, DHL_GKP_USERNAME, DHL_GKP_PASSWORD, DHL_ACCOUNT_NUMBER, DHL_TRACKING_API_KEY (Secret), DHL_ENVIRONMENT (ConfigMap) Kein Etikettendruck, keine Packstation im Checkout developer.dhl.com, Geschäftskundenvertrag — Schritt für Schritt in .env.example
ApiCore-Synchronisierung APICORE_API_KEY (Secret), APICORE_API_URL (ConfigMap) Sync-Knopf meldet Konfigurationsfehler API-Schlüssel aus ApiCore → Verwaltung → Automatisierung → API-Schlüssel, nur Berechtigung shop-sync:read; kein Benutzerkonto, kein Passwort

7. Secret und ConfigMap ausfüllen

7.1 Secret (k8s/02-secret.yaml)

cp k8s/02-secret.example.yaml k8s/02-secret.yaml

k8s/02-secret.yaml öffnen und jeden Wert aus der Tabelle eintragen. Werte in doppelten Anführungszeichen lassen (stringData — Kubernetes kodiert selbst, kein base64 von Hand).

Schlüssel Pflicht Inhalt Quelle
DATABASE_URL ja Verbindungs-URL mit URL-kodiertem Passwort Abschnitt 2
DB_PASSWORD ja Datenbank-Passwort roh Abschnitt 2
SESSION_SECRET ja ≥ 32 zufällige Zeichen Abschnitt 2
ADMIN_EMAIL ja nicht erratbare Admin-Adresse Abschnitt 2
ADMIN_PASSWORD ja ≥ 12 Zeichen, zufällig Abschnitt 2
STRIPE_SECRET_KEY ja sk_live_… bzw. rk_live_… 5.1
STRIPE_WEBHOOK_SECRET ja whsec_… 5.2
SMTP_USER ja [email protected] 4
ENTRA_TENANT_ID ja* Verzeichnis-ID 3.1
ENTRA_CLIENT_ID ja* Anwendungs-ID 3.1
ENTRA_CLIENT_SECRET ja* Wert des Clientschlüssels 3.2
ENTRA_REDIRECT_URI ja* https://wabenhain.com/api/auth/entra/callback 3.1
TURNSTILE_SECRET_KEY empfohlen Turnstile-Secret 6
GOOGLE_PLACES_API_KEY nein Google-API-Schlüssel 6
APICORE_API_KEY nein ApiCore-API-Schlüssel (ack_…) 6
DHL_* (6 Werte) nein DHL-Zugangsdaten 6

* Ohne die ENTRA_*-Werte gibt es weder „Mit Microsoft anmelden“ noch Mailversand — keine Bestellbestätigungen, keine Passwort-Resets. Für den Live-Betrieb daher Pflicht.

Nicht mehr verwendet (aus älteren Secrets entfernen): SMTP_HOST, SMTP_PORT, SMTP_PASSWORD, SMTP_OAUTH_TENANT_ID, SMTP_OAUTH_CLIENT_ID, SMTP_OAUTH_CLIENT_SECRET.

7.2 ConfigMap (k8s/01-configmap.yaml)

Nur diese Werte anpassen, der Rest sind sinnvolle Vorgaben:

Schlüssel Inhalt
NEXT_PUBLIC_SITE_URL https://wabenhain.com — muss zu host in k8s/06-ingress.yaml passen
SMTP_FROM Wabenhain <[email protected]> — dieselbe Adresse wie SMTP_USER
DATABASE_PROVIDER postgresql (oder mysql, passend zu DATABASE_URL)
STRIPE_PUBLISHABLE_KEY pk_live_…
NEXT_PUBLIC_TURNSTILE_SITE_KEY Turnstile-Site-Key (empfohlen)
VAT_RATE_PERCENT* Umsatzsteuersätze prüfen (Honig 7 %, Kerzen 19 %, Propolis je nach Einordnung)
DHL_ENVIRONMENT sandbox, bis DHL getestet ist, dann production
GOOGLE_PLACE_ID, APICORE_API_URL nur bei Nutzung

8. Ausrollen

Erstmalig, in dieser Reihenfolge (Details und Hintergründe: k8s/README.md):

kubectl apply -f k8s/00-namespace.yaml
kubectl apply -f k8s/01-configmap.yaml
kubectl apply -f k8s/02-secret.yaml

# Nur EINE Datenbank, passend zu DATABASE_PROVIDER:
kubectl apply -f k8s/03-postgres.yaml
kubectl wait --for=condition=ready pod -l app=wabenhain-postgres -n wabenhain --timeout=120s

kubectl apply -f k8s/04-uploads-pvc.yaml
kubectl apply -f k8s/05-app-deployment.yaml
kubectl apply -f k8s/06-ingress.yaml
kubectl apply -f k8s/07-backup-cronjob.yaml

kubectl rollout status deployment/wabenhain-app -n wabenhain

Datenbank-Migrationen und das Anlegen des Admin-Kontos laufen beim Start des Pods automatisch.

Später einen Wert ändern: Datei anpassen, erneut anwenden und den Pod neu starten — die App liest Secrets und ConfigMap nur beim Start:

kubectl apply -f k8s/02-secret.yaml      # bzw. 01-configmap.yaml
kubectl rollout restart deployment/wabenhain-app -n wabenhain

Einzelnen Wert ohne Datei ändern bzw. entfernen:

kubectl -n wabenhain patch secret wabenhain-secrets --type=merge -p '{"stringData":{"SMTP_USER":"[email protected]"}}'
kubectl -n wabenhain patch secret wabenhain-secrets --type=json -p '[{"op":"remove","path":"/data/SMTP_PASSWORD"}]'

Wer k8s/02-secret.yaml lokal pflegt, trägt jede Änderung auch dort ein — sonst setzt das nächste kubectl apply den alten Stand zurück.


9. Prüfen

Der Reihe nach:

  1. Vollständigkeit der Konfiguration im laufenden Pod:
    kubectl -n wabenhain exec deploy/wabenhain-app -- node_modules/.bin/tsx scripts/preflight.ts
    
    Jeder fehlende Pflichtwert erscheint als Blocker mit Begründung.
  2. Shop erreichbar: https://wabenhain.com öffnen.
  3. Admin-Login: https://wabenhain.com/konto mit ADMIN_EMAIL/ADMIN_PASSWORD anmelden → Zwei-Faktor-Authentifizierung einrichten → Admin-Bereich öffnet sich.
  4. Mailversand und Anbindungen: Admin → Einstellungen → System & Tests. Oben steht, welche Dienste eingerichtet sind (fehlende Werte werden benannt). Darunter „Test-Mail senden“: zuerst prüft der Test, ob smtp.office365.com:587 und login.microsoftonline.com:443 per IPv4 erreichbar sind, dann den Versand. Erfolg: grüne Meldung und eine Mail an die Admin-Adresse. Fehler: die Seite zeigt die Meldung von Microsoft und was zu tun ist (siehe Fehlerbehebung).
  5. Microsoft-Anmeldung: abmelden, auf /konto „Mit Microsoft anmelden“ wählen.
  6. Firmendaten: Admin → Einstellungen → Firma & Versand — Name, Anschrift und eine gültige E-Mail-Adresse eintragen. Sie erscheinen im Impressum und in der Widerrufsbelehrung.
  7. Bezahlung: eine kleine echte Bestellung aufgeben, bezahlen, im Admin prüfen: Status „Bezahlt“, Rechnung vorhanden, Bestätigungsmail angekommen. Danach im Admin erstatten.
  8. Widerruf: im Footer „Vertrag widerrufen“ testen → Eingangsbestätigung kommt an, Eintrag erscheint unter Admin → Widerrufe.

10. Wartung

Was Läuft ab / ändert sich Folge, wenn verpasst Was tun
Entra-Clientschlüssel (ENTRA_CLIENT_SECRET) nach höchstens 24 Monaten Microsoft-Anmeldung und Mailversand fallen gleichzeitig aus Vier Wochen vorher neuen Schlüssel anlegen (3.2), Secret ersetzen, Pod neu starten, E-Mail-Test, danach alten Schlüssel in Entra löschen
Stripe-Schlüssel bei Verdacht auf Abfluss Fremde könnten Erstattungen auslösen Im Stripe-Dashboard „Schlüssel rollen“, Secret ersetzen, Pod neu starten
SESSION_SECRET bei Verdacht auf Abfluss Sitzungen fälschbar Neuen Wert erzeugen, ersetzen, neu starten (meldet alle ab)
DHL-/Google-/Turnstile-Schlüssel je nach Anbieter Funktion ruht Beim Anbieter erneuern, Secret ersetzen
Postfach-Freigabe bei Umbenennung oder neuem Absender-Postfach Mailversand scheitert („Exchange lässt die App nicht …“) Abschnitt 4.2 für das neue Postfach wiederholen, SMTP_USER und SMTP_FROM anpassen

Nach jeder Änderung an Secret oder ConfigMap: kubectl rollout restart deployment/wabenhain-app -n wabenhain und kurz Admin → Einstellungen → System & Tests.


11. Fehlerbehebung

Mailversand (Anzeige in Admin → Einstellungen → System & Tests)

Meldung / Ursache Lösung
„nicht vollständig eingerichtet“ + Liste fehlender Werte Genannte Werte ins Secret, Pod neu starten
AADSTS7000215 — Client-Secret falsch In ENTRA_CLIENT_SECRET steht die Geheimnis-ID statt des Werts → neuen Schlüssel anlegen (3.2)
AADSTS7000222 — Client-Secret abgelaufen Neuen Schlüssel anlegen (3.2), ersetzen, neu starten
AADSTS700016 — App nicht gefunden ENTRA_CLIENT_ID und ENTRA_TENANT_ID gehören nicht zusammen
AADSTS90002 / 900023 — Tenant ungültig ENTRA_TENANT_ID prüfen (Verzeichnis-ID)
„Exchange lässt die App nicht mit dem Postfach senden“ (535 5.7.3) (1) SMTP.SendAsApp erteilt? (3.3) (2) New-ServicePrincipal mit der Objekt-ID der Unternehmensanwendung? (3.4/4.2) (3) Add-MailboxPermission für genau SMTP_USER? (4) 30 Minuten gewartet?
„Authentifiziertes SMTP ausgeschaltet“ (SmtpClientAuthentication is disabled) Set-CASMailbox -Identity "[email protected]" -SmtpClientAuthenticationDisabled $false
„SendAsDenied“ SMTP_FROM ist nicht die Adresse aus SMTP_USER → angleichen
„nicht erreichbar“ (Netzwerk) Die Netzwerkprüfung im Test zeigt, welcher Weg fehlt. Der Cluster muss ausgehend per IPv4 smtp.office365.com:587 und login.microsoftonline.com:443 erreichen (Firewall des Hosters, NetworkPolicy). Manche Hoster sperren Mail-Ports für neue Server — dort die Freigabe von Port 587 beantragen.
ENETUNREACH 2603:… (IPv6-Adresse) Stammte aus älteren Versionen, die zufällig eine IPv6-Adresse wählten; der Shop verbindet sich inzwischen nur per IPv4. Tritt es trotzdem auf: aktuelles Image ausrollen.

Ausführliche Fehler stehen außerdem im Pod-Log:

kubectl logs deploy/wabenhain-app -n wabenhain | grep "\[mail\]"

Microsoft-Anmeldung

Symptom Lösung
Knopf „Mit Microsoft anmelden“ fehlt Einer der vier ENTRA_*-Werte fehlt
Microsoft meldet „redirect_uri mismatch“ (AADSTS50011) ENTRA_REDIRECT_URI und die Umleitungs-URI in der App-Registrierung (3.1) müssen zeichengenau gleich sein

Sonstiges

Symptom Lösung
Pod startet nicht, Log nennt Datenbankfehler DATABASE_URL: Passwort URL-kodiert? Host passend zum Datenbank-Manifest?
Bestellungen bleiben auf „Neu“ Stripe-Webhook (5.2): URL, Ereignisse und STRIPE_WEBHOOK_SECRET prüfen; im Stripe-Dashboard unter Webhooks die Zustellversuche ansehen
Bilder weg nach Neustart k8s/README.md, Abschnitt „Bilder/Videos verschwinden nach jedem Redeploy“