Ausgangslage
Ein Heizungsbetrieb hat hunderte Bestandskunden mit wartungspflichtigen Anlagen — und keinen verlässlichen Weg, sie zum nächsten Termin zurückzuholen. Die Nachfassliste liegt im Kopf des Büros oder in einer Excel-Datei, die niemand pflegt. Also bleibt der Sommer leer, obwohl die Aufträge da wären. Große CRM- und ERP-Pakete lösen das nicht: Sie kosten mehr Einrichtung, als das Problem groß ist.
Lösung
Ein Produkt, das nur eine Sache tut. Aus letzter Wartung und Intervall berechnet das System den nächsten Termin und plant daraus eine dreistufige Erinnerungssequenz — gedrosselt auf 25 Mails pro Woche, dienstags bis donnerstags von 9 bis 11 Uhr, nie an bundesweiten Feiertagen. Der Kunde klickt in der gebrandeten Mail auf „Wartung anfragen“ und landet auf einer öffentlichen Mini-Seite ohne Konto; der Betrieb wird sofort benachrichtigt und arbeitet die Anfrage im Posteingang ab. Beim Abschluss plant sich der nächste Zyklus selbst. Die gesamte Terminlogik liegt als reine, datenbankfreie Funktionen im Code — testbar Regel für Regel.
Im Kern
Wellen-Drosselung
25 Erinnerungen pro Woche, festes Sendefenster, keine Feiertage. Wer 400 Kunden importiert, hat morgen nicht 400 Anrufe.
Anfrage ohne Konto
Der Kunde klickt in der Mail und landet auf einer öffentlichen Mini-Seite. Kein Login, keine Hürde — der Betrieb bekommt die Anfrage sofort aufs Handy.
Import aus der Praxis
CSV-Import, der Semikolon, Windows-1252, deutsche Datumsangaben und Excel-Serials versteht — so, wie Handwerkerprogramme wirklich exportieren.
Kunden ohne E-Mail
Anrufliste nach PLZ gruppiert und druckbar, Briefliste mit fertigen Word-Briefen im selben Wortlaut wie die Mail.
GEG-Pflichten benannt
Prüfpflichten nach dem Gebäudeenergiegesetz stehen typisiert mit Paragraph und Quellenstand im Code — das Produkt zitiert die Pflicht, es berät nicht.
Idempotenz als DB-Garantie
Ein Unique-Constraint plus „for update skip locked“: zwei parallel laufende Worker können keine doppelte Erinnerung erzeugen. Getestet, nicht behauptet.
Code-Einblick
// Erster erlaubter Sendezeitpunkt: Wochentag, Feiertag, Uhrzeit — alles Berlin-Zeit export function nextSendSlot(from: IsoDate, window = DEFAULT_SEND_WINDOW) { let candidate = from for (let offset = 0; offset < 400; offset += 1) { if (window.weekdays.includes(isoWeekday(candidate)) && // Di–Do !isGermanPublicHoliday(candidate)) { // nie feiertags return { date: candidate, instant: berlinToInstant(candidate, window.startHour) } } candidate = addDays(candidate, 1) } throw new Error(`Kein Sendefenster innerhalb eines Jahres ab ${from}`) } // Rein, ohne Datenbank — damit jede Regel einzeln testbar bleibt.
Tech-Stack
Ergebnis
Live unter servicewecker.de: Erinnerungssequenz, Anfragen-Posteingang und Anruf- bzw. Brieflisten laufen im Takt eines eigenen Worker-Containers, die Daten liegen auf einer selbst gehosteten Supabase-Instanz in Deutschland. Zeigt ein bewusst kleines Produkt, das seine Regeln ernst nimmt: reine Domänenlogik, Idempotenz auf Datenbankebene und ein Deployment, das ohne fremde Plattform auskommt.