# Der Versandtakt

Entwurf vom 2. August 2026, beschlossen mit dem Betreiber. Erstes von fünf
Vorhaben rund um den Mailversand; die übrigen stehen am Ende unter „Was danach
kommt".

## Warum

Der Betreiber will nicht, dass ein großer Lauf — zweihundert Rechnungen an einem
Abend — als Schub bei einem einzelnen Empfängerserver ankommt und die
Absenderadresse dort als Spam eingestuft wird. Ein einmal beschädigter
Absenderruf ist schwer zu reparieren, und die Regel muss stehen, **bevor** der
erste große Lauf kommt.

Zur Einordnung, damit niemand den Aufwand für dringlicher hält als er ist: heute
sind es **11 Kunden, 5 laufende Verträge, 2 Rechnungen**. Bei dieser Größe
braucht es keine Drosselung. Gebaut wird sie für den Zustand, in dem sie fehlen
würde.

## Was heute steht

- **Vierzehn Mailklassen**, ausnahmslos `ShouldQueue` — es geht heute schon alles
  über die Warteschlange (Redis).
- **Ein Arbeiter** für alles: `php artisan queue:work redis --tries=3 --timeout=90`.
- **Eine zweite Schlange existiert bereits**: die Bereitstellung fährt über
  `onQueue('provisioning')` mit eigenem Arbeiter. Das Muster ist also im Haus.
- **`sent_mails`** protokolliert, was hinausging (Empfänger, Betreff, Mailklasse,
  Zeitpunkt) — und heißt bewusst nicht „delivered": angenommen ist nicht
  zugestellt.
- **Keine Drosselung, kein Takt, keine Priorität.**

## Die drei Spuren

Rund um die Uhr, ohne Zeitfenster. Der Betreiber hat das ausdrücklich so
entschieden: ob eine Rechnung um 18 Uhr oder um 2 Uhr ankommt, ändert nichts —
sie wird abgelegt, und die Abbuchung macht Stripe von selbst.

| Spur | Schlange | Takt | Mailklassen |
|---|---|---|---|
| **Direkt** | `mail-direkt` | ungedrosselt | ResetPassword, VerifyEmail, NewDeviceSignIn, SecurityBlock, ContactRequest, OrderConfirmation, OperatorMessage |
| **Wichtig** | `mail-wichtig` | 30 je 5 Minuten | MaintenanceAnnouncement, MaintenanceCancelled, CloudSuspended, CloudResumed |
| **Zeit lassen** | `mail-ruhig` | 20 je 10 Minuten | Invoice, DunningNotice, DormantAccountWarning |

**Warum „Direkt" ungedrosselt ist:** Diese Mails entstehen **einzeln**, weil ein
einzelner Mensch gerade etwas getan hat und auf die Antwort wartet. Sie können
gar keinen Schub bilden. Drosseln nützt dort nichts und kostet einen Supportfall
je verzögertem Kennwort.

`OperatorMessageMail` steht dort aus demselben Grund: der Betreiber hat gerade
auf „senden" geklickt und sieht zu. **Das ist meine Zuordnung, nicht seine** —
sie gehört gegengelesen.

**Warum die Mahnung unten steht:** Sie hat eine Frist von Tagen, nicht von
Minuten, und geht an mehrere gleichzeitig. Bei 120 Mails je Stunde ist sie
spätestens nach einer Stunde draußen, lange vor jeder Frist.

**Warum eine Ausfallmeldung oben steht, obwohl sie an alle geht:** Massenversand
und Dringlichkeit sind zwei verschiedene Achsen. Eine Wartungsankündigung ist
beides — deshalb bekommt sie ein höheres Kontingent statt gar keines.

## Die Zuordnung ist änderbar, aber nicht überall

Die Vorgaben stehen im Code. Der Betreiber kann eine Mailklasse in der Konsole
verschieben; die Einstellung liegt darüber.

**Die sieben Direkt-Mails lassen sich nicht nach unten schieben.** Ein
Kennwort-Zurücksetzen, das zwanzig Minuten liegt, ist ein Supportfall und kein
gespartes Ansehen. Die Sperre sitzt in der Zuordnung selbst, nicht nur im
Formular.

**Eine neue Mailklasse ohne Eintrag landet in „Zeit lassen".** Das ist die
vorsichtige Richtung: eine zu langsam verschickte Mail ist ein kleinerer Fehler
als ein ungedrosselter Schub, den niemand vorhergesehen hat.

## Wie gedrosselt wird

**Eine Zwischenschicht an der Mail selbst.** Alle vierzehn Klassen sind bereits
`ShouldQueue`; sie bekommen eine gemeinsame Zwischenschicht, die vor dem Versand
das Kontingent ihrer Spur prüft. Ist es erschöpft, geht die Mail mit Verzögerung
zurück in die Schlange.

**Kein einziger Aufrufer ändert sich.** `Mail::to(...)->queue(...)` bleibt an
allen rund zwanzig Absendestellen stehen. Das ist der Grund für diesen Weg: der
Betreiber hat ausdrücklich verlangt, nichts kaputtzumachen, und dieser Weg fasst
die Absendestellen nicht an.

Verworfen wurden:

- **Eine eigene Ausgangstabelle**, aus der ein Befehl im Takt schöpft. Volle
  Sicht und von Hand steuerbar — aber alle Absendestellen müssten umgebaut
  werden, und es gäbe zwei Warteschlangen nebeneinander, die auseinanderlaufen
  können.
- **Drei Arbeiter mit eigenem Takt** im Container. Am wenigsten Code — aber die
  Zahlen stünden im Container statt in der Konsole, und niemand sähe, was wartet.

## Der Arbeiter muss die Spuren kennen

Drei Schlangen nützen nichts, wenn niemand sie liest. Der vorhandene Arbeiter
fährt heute `queue:work redis` ohne Angabe und bedient damit nur `default`.

Er bekommt die Spuren in dieser Reihenfolge:

```
queue:work redis --queue=mail-direkt,mail-wichtig,mail-ruhig,default --tries=3 --timeout=90
```

Die Reihenfolge **ist** die Priorität: der Arbeiter sieht erst nach, ob in
`mail-direkt` etwas liegt, und geht erst weiter, wenn dort nichts ist. Deshalb
steht ein Kennwort-Zurücksetzen nie hinter zweihundert Rechnungen — nicht wegen
einer Sortierung, sondern weil es in einer anderen Schlange liegt.

`default` bleibt am Ende stehen: dort läuft alles, was keine Mail ist, und es
darf durch diese Änderung nicht verhungern. Der eigene Arbeiter der
Bereitstellung (`provisioning`) bleibt unberührt.

**Das ist eine Änderung an `docker-compose.yml`** und damit am Deployment. Sie
muss zusammen mit dem Code ausgerollt werden: ein Arbeiter, der die neuen
Schlangen nicht liest, sammelt Mails an, die niemand versendet — still, ohne
Fehlermeldung.

## Die Falle, an der dieser Bau scheitern kann

Der Arbeiter läuft mit `--tries=3`. **Eine zurückgelegte Mail zählt als
Versuch.** Ohne Vorkehrung wäre jede Rechnung nach dem dritten Drosseln
endgültig gescheitert statt verschickt — still, im Fehlerprotokoll, ohne dass
jemand etwas merkt.

Eine gedrosselte Rückstellung darf den Versuchszähler deshalb **nicht** erhöhen.
Das ist der erste Test dieses Vorhabens, vor der ersten Zeile Drossel: fünfzig
Mails in eine Spur mit Kontingent zwanzig, und danach müssen fünfzig verschickt
sein und null gescheitert.

## Der Notschalter

Ein Schalter in der Konsole macht die Drossel wirkungslos — dann fährt alles wie
heute, ungedrosselt über die drei Schlangen. Wenn die Drossel je klemmt, muss der
Ausweg ein Klick sein und kein Deployment.

## Was sichtbar wird

Je Spur: wie viele Mails warten (Redis kennt die Länge), welcher Takt eingestellt
ist, und ob die Drossel an ist. Darunter das vorhandene Versandprotokoll.

Die Bereitschaftsseite schlägt an, wenn in einer Spur etwas liegt, das **älter
als eine Stunde** ist. Die Zahl ist so gewählt, dass sie beim langsamsten Takt
nicht von selbst anschlägt: 20 je 10 Minuten sind 120 in der Stunde, und ein Lauf
dieser Größe steht heute nicht an. Schlägt sie trotzdem an, klemmt etwas — und
das ist die Lage, die sonst niemand bemerkt.

## Tests

- **Der Versuchszähler.** Fünfzig Mails, Kontingent zwanzig: fünfzig verschickt,
  null gescheitert. Der wichtigste Test dieses Vorhabens.
- **Die Spurzuordnung.** Jede der vierzehn Mailklassen landet in der Schlange
  ihrer Vorgabe; eine unbekannte Klasse landet in „Zeit lassen".
- **Die Sperre.** Eine Direkt-Mail lässt sich nicht in eine gedrosselte Spur
  verschieben — auch nicht an der Oberfläche vorbei.
- **Der Takt.** In einem Fenster gehen höchstens so viele Mails hinaus, wie das
  Kontingent erlaubt; im nächsten wieder.
- **Direkt ist ungedrosselt.** Hundert Direkt-Mails hintereinander werden nicht
  zurückgelegt.
- **Der Notschalter.** Ausgeschaltet fährt alles ohne Verzögerung.
- **Die Reihenfolge.** Eine Direkt-Mail, die nach zweihundert Rechnungen
  eingereiht wird, geht vor ihnen hinaus.

## Was NICHT dazugehört

- **Ein Zeitfenster.** Ausdrücklich nicht gewollt: rund um die Uhr.
- **Umsortieren innerhalb einer Spur.** Die Trennung in drei Schlangen leistet,
  was die Priorität leisten soll.
- **Der Zeitplan** (wann welche Mail ausgelöst wird), **der Vorlauf** (Rechnung
  einige Tage vor der Abbuchung), **die Vorlagen für Systemmails samt Editor**
  und **die Aufteilung der Mailseite**. Vier eigene Vorhaben.

## Was danach kommt

In dieser Reihenfolge, jedes mit eigenem Entwurf:

1. **Die Mailseite aufteilen** — Zustellung, Postfächer, Spuren, Protokoll.
2. **Vorlagen für Systemmails** mit Editor und Vorschau. Heikel: eine
   Rechnungsmail trägt Pflichtangaben, frei bearbeitbar heißt auch frei
   kaputtmachbar.
3. **Vorlauf** — die Rechnung einige Tage vor der Abbuchung statt danach. Braucht
   Stripes Vorschau auf die nächste Abbuchung.
4. **Eigene Mails mit eigener Auslöseregel.** Die eigentliche Frage dort ist
   nicht die Vorlage, sondern: wann wird sie verschickt.

Siehe `docs/superpowers/specs/2026-07-31-zahlungsmittel-und-mahnlauf-design.md`
für das Mahnwesen, das heute täglich um 09:00 läuft — mit derselben Begründung,
aus der die Spuren rund um die Uhr fahren dürfen und die Mahnung trotzdem unten
steht.
