Repository navigation
Releases
2.18.0
Added
- [new] Contact import (Contacts → Import contacts, permission
import leadhub contacts): CSV/TXT upload or text
pasted from Excel, column mapping with suggestions (also to custom fields, or a new one), preview, then a queued
job that works through the file in chunks. Duplicates are merged throughContactResolver, no mail and no
"new lead" alert goes out, consent is never invented, opted-out addresses are skipped, bad rows land in a
downloadable report instead of stopping the run. New config blockleadhub.imports. See the README. - [new]
Support\BulkWrite: while it is active (a contact import), the "new lead" staff alert and the outbound
webhook bridge skip each contact. Segments and CRM destinations still see every contact.
v2.17.0
2.16.1
- Tests: opening a segment in the editor and saving it hands the server the same rules, for
custom-field, radius (geo) and nested-group conditions (tests/js/segment-roundtrip.test.mjs,
npm test). The behaviour itself has held since 2.15.0 (before, a Save turned such conditions
into plain field conditions that match nobody); there was no test that would have caught it
coming back. The editor's rule round trip moved fromSegments/Edit.vueinto
resources/js/support/segmentRules.js(normalizeRules,serializeRules) so it can be tested.
No change in behaviour, rebuilt CP bundle.
2.16.0
Changed
- Settings: the addon now declares
settingsNavParent()([section, name]of its own Control Panel navigation entry). With statamic-brand-context 1.16 or newer its settings show as the sub-item "Einstellungen" under that entry; addons without a navigation of their own share one "Addon-Suite" page under Settings. Older brand-context versions ignore the method and keep the previous layout. - The addon no longer registers its own settings sub-item under its navigation entry; statamic-brand-context 1.16 adds it. With an older statamic-brand-context the sub-item is gone, so update that package to 1.16 or newer together with this one.
Upgrading
- No migration, no new config key. Update
goldnead/statamic-brand-contextto 1.16 or newer.
v2.15.1
2.15.0
Behoben: eine Umkreis-Bedingung ohne Land bleibt ohne Land
Der Segment-Editor setzte einer geo-Bedingung ohne country beim Öffnen das erste Land aus
leadhub.postal_codes.countries ein; stand dort AT vorn, verschob schon Öffnen und Speichern einen
deutschen Umkreis nach Österreich. Jetzt bleibt das Feld leer (Auswahl zeigt „Vorgabe (DE)"), und es
gilt wie bisher die Vorgabe des Auswerters, DE; auch die angezeigte Ortsauflösung fragt dann DE.
Die Normalisierung steht in resources/js/support/segmentRules.js und hat einen Test
(npm test, node --test, ohne Abhängigkeiten; auch im Build-Check-Workflow).
Neu: PLZ-Umkreis im Segment-Editor, verwaltete Segmente sichtbar
- Segment-Editor kennt
geo. „Kontakt wohnt im Umkreis von / außerhalb von [km] um
Postleitzahl [PLZ] ([Land])", mit Live-Zahl wie die anderen Bedingungen. Unter der Bedingung
steht der aufgelöste Mittelpunkt („Mittelpunkt: 89073 Ulm") oder, rot, dass das PLZ-Verzeichnis
die Postleitzahl nicht kennt. Unter der Trefferzahl steht, wie viele Kontakte keine PLZ haben
und deshalb in keine Umkreis-Bedingung fallen. Die Vorschau liefert dafür zusätzlich
places({"DE:89073": "Ulm"},nullfür unbekannt) undwithout_postal_code(nullohne
geo-Bedingung). Länder ausleadhub.postal_codes.countries. - Neues Feld
managed_byam Segment (Migration2026_10_01_000001, im Flat-Treiber im
segments.yaml):['source' => …, 'label' => …, 'url' => …], gesetzt über
SegmentRepository::create()/update(),nullgibt das Segment frei.sourceist Pflicht,
sonst gilt das Feld als leer (Segment::managedBy(),isManaged()). Nicht über das
CP-Formular setzbar. Die Segmentliste zeigt ein Abzeichen „Verwaltet von …" mit Link aufurl;
im Editor sind Name, Handle und Regeln nur lesbar, und das Update ignoriert sie auch serverseitig,
weil der nächste Abgleich des Besitzers sie zurückschreiben würde. Beschreibung und Aktiv
bleiben bearbeitbar. Für die Serien-Segmente aus statamic-marketing gedacht. - Kontakt-Detail: Standort. PLZ und Land sichtbar (Kontakt-Kasten, „79100 Freiburg im
Breisgau · DE") und in der Seitenleiste bearbeitbar. Store- und Update-Request nehmen
postal_code(max. 16) undcountry(zwei Buchstaben); gespeichert wird normalisiert wie im
PLZ-Verzeichnis („ 79 098 " →79098,de→DE).
Behoben
- Der Segment-Editor schrieb beim Öffnen und Speichern jede Bedingung, die keine Tag- oder
Ereignis-Bedingung war, in einefield-Bedingung um: eigene Felder,geound verschachtelte
Gruppen trafen danach niemanden mehr. Alle Formen überleben jetzt den Weg durchs Formular;
Gruppen bleiben unverändert und werden als Gruppe angezeigt. - Segmentliste: Mitgliederzahl als Zahl statt als Knopf-artiges Badge, Aktiv-Status als
pill,
lange Handles abgeschnitten statt zweizeilig. Bedingungstypen im Editor mit Namen statt
Rohwert (custom,geo).
2.14.0
Neu: lesende Kommandos für Agenten
Ein Agent außerhalb der App (per SSH, php artisan …) kann jetzt fragen, was LeadHub über eine
Person weiß und was heute ansteht. Beide Kommandos schreiben nichts, lösen keine Events aus und
loggen keine Inhalte; das ist durch Tests belegt (Query-Log ohne INSERT/UPDATE/DELETE, keine
Events, keine Logzeilen, unveränderte Flat-File-Dateien).
leadhub:kontakt {suche} {--json} {--timeline=20} {--brand=}:sucheist eine E-Mail, eine
ID, eine UUID oder ein Namensteil. Ein Treffer liefert das Profil (present()plus Marke,
Käufe, die letzten N Einträge der zusammengeführten Timeline, offene Follow-ups, offene Aufgaben
und offene Verkaufschancen mit Phase), mehrere eine kurze Liste (höchstens 10), keiner
status: none. Alle drei enden mit Exit-Code 0; nur eine unbekannte Marke mit 1.leadhub:heute {--json} {--brand=}: neue Kontakte der letzten 24 Stunden und 7 Tage, heute
fällige und überfällige Follow-ups und Aufgaben, je Liste die Zahl und die ersten fünf.- Multi-Brand: ohne
--brandüber alle Marken, jeder Treffer und jeder Block nennt seine Marke.
Das Profil wird in der Marke gebaut, der der Kontakt gehört.
JSON-Schlüssel und Beispiel im README unter „For agents / CLI".
Geändert
LeadHubManagerhat vier neue lesende Getter:timelineFor(),followupsFor(),tasksFor()
undopportunitiesFor()(je Kontakt, ID oder UUID;tasksFor/opportunitiesFormit
openOnly). Die Kontaktseite im CP liest Timeline, aktives Follow-up, Aufgaben und
Verkaufschancen jetzt über diese Getter statt über eigene Abfragen, damit Bildschirm und
Kommando dieselbe Antwort geben. An den Props der Seite ändert sich nichts.Contactdeklariertuuid,created_at,last_activity_atundarchived_at; die
PHPStan-Baseline verliert dadurch 198 Zeilen eingefrorener Einträge.
2.13.1
Behoben
- Die Einstellungsseite der Benachrichtigungen bietet „Aufgabe zugewiesen" nur noch Super-Usern und Benutzern mit dem Recht
view leadhuban. Bisher reichte ein Konto, und in ChoirLive hat jede Newsletter-Adresse eines. Am Versand ändert sich nichts.
2.13.0
Neu: „wohnt im Umkreis von X km um diese Postleitzahl"
Regionale Ansprache ist bei Konzerten, Workshops und Coaching vor Ort der Normalfall, und bis
heute gab es dafür keine Bedingung. Was stattdessen passiert, ist gut dokumentiert: dieselbe
Auswahl wird von Hand nachgebaut, in zwei Systemen, mit zwei Namensformaten, einem
Radius-Fehler und einem stillen Deckel — weil kein System sie besitzt.
{ "type": "geo", "operator": "within_km", "plz": "50667", "value": 35 }outside_km ist das Gegenstück. Ausgewertet wird zur Sendezeit, nicht beim Gruppenbau: eine
Neuanmeldung ist sofort drin, ohne dass jemand nachträgt.
Ein Kontakt ohne Postleitzahl trifft bei keinem der beiden Operatoren. „Wir wissen nicht,
wo sie sind" ist nicht „sie sind weit weg", und eine regionale Einladung an einen unbekannten
Ort ist genau der Fehler, den diese Bedingung verhindern soll. Die Zahl dieser Kontakte gehört
neben die Segmentgröße, sonst liest sich ein kleines Segment wie ein zu enger Radius statt wie
fehlende Daten.
Dazu:
postal_codeundcountryam Kontakt, als echte Spalten und in
SegmentEvaluator::FIELDS. Aus demselben Grund wie die zwischengespeicherten
Umsatzsummen: ein Custom Field ist weder Spalte noch indizierbar.- Eine Postleitzahlen-Tabelle mit Koordinaten, gefüllt von
leadhub:postal-codesaus den
offenen GeoNames-Daten. Standardmäßig DE, AT und CH — nicht nur Deutschland, denn eine
deutsche Tabelle antwortet aufA-1070mit nichts, und Wien ist ein echtes Konzert. Eine
Postleitzahl, die im Datensatz mehrfach vorkommt, wird zu einer Zeile in der Mitte ihrer
Punkte; trägt die Datei eine fremde Länderzeile, wird sie gemeldet statt umetikettiert. - Zweifacher Cache, je Prozess und in der Anwendung: ein Sweep stellt dieselbe Frage für
jeden der dreitausend Kontakte, und die Antwort ändert sich nur beim Neu-Import.
Die genaue Entfernung wird in PHP gerechnet, nicht im SQL. Die Bounding Box läuft als
whereBetween über zwei indizierte Spalten und wirft fast alles weg; was übrig bleibt, misst
PHP in Mikrosekunden. Der Grund ist Portabilität: der Großkreis-Term braucht least,
greatest und radians, und SQLite hat keine davon — dort scheitert er mit „no such
function" statt mit einer falschen Zahl.
Der Segment-Builder im Control Panel kennt die Bedingung noch nicht; bis dahin ist sie über die
Regel-JSON erreichbar.
Geändert: die offenen Aufgaben stehen als Satz im Digest, nicht als Datensatz
Die Quelle, die offene Aufgaben in den Digest von goldnead/statamic-notifications einspeist,
lieferte bisher nur Zahlen (open_tasks, overdue_tasks). Die Vorlage dort hatte damit nichts
anzufangen und hat sie als JSON-Block in die Mail gedruckt.
Ab statamic-notifications 1.10.0 druckt der Digest genau einen Schlüssel: line, einen fertigen
Satz. Die Quelle schreibt ihn jetzt, in Deutsch und Englisch. Die Zahlen bleiben daneben stehen,
sie sind die Grundlage des Satzes.
Wichtig für ältere Fassungen des Digests: dort ist der neue Schlüssel schlicht unbenutzt. Nichts
bricht, in keiner Richtung.
2.12.1
Fixed: installierbar auf aktuellem Statamic 6
Das Paket verlangte inertiajs/inertia-laravel ^1.0|^2.0. Statamic 6.33.0 ist die erste
Version, die ^2.0 || ^3.0 erlaubt, und löst dort auf v3 auf — wer sie einsetzt, konnte dieses
Addon nicht mehr installieren. Composer meldete das als unlösbare Anforderung, nicht als
veraltete Angabe, und das liest sich auf den ersten Blick wie ein Fehler am eigenen Projekt.
Die Anforderung steht jetzt auf ^2.0 || ^3.0, wie in statamic-brand-context. Am Code war
nichts zu tun: genutzt werden Inertia::render und der Typ Inertia\Response, beide unter v3
unverändert; es gibt keine eigene Middleware und keinen Gebrauch der Testing-Helfer, deren API
sich hätte drehen können. Die Suite lief unter Inertia 3.3.4 mit 728 grünen Tests durch.
Auf Statamic vor 6.33.0 ändert sich nichts, dort wird weiterhin Inertia 2 gezogen.