Kostenlos testen
Handbuch

Shop-Service-Check und Shop-Vorschau in der Kaltmail

Kostenloser Check unter /shop-check: öffentliche Shop-Seiten lesen, Lücken zeigen, 3 Beispielantworten mit Quelle, Test mit schon befülltem Wissen. Dieselbe Technik als Vorschau auf der Landing für Outreach-Leads.

Der Shop-Service-Check ist der Einstieg vor der Registrierung (Backlog I-040) und das Ziel der Anzeigen: Ein Shop-Betreiber gibt unter /shop-check seine Shop-Adresse ein und sieht nach rund 30 Sekunden

  1. das erkannte Shopsystem,
  2. was Kunden bei ihm finden (Versand & Lieferzeit, Rücksendung, Zahlungsarten, Kontakt & Erreichbarkeit, FAQ, jeweils mit Quelle),
  3. wo Antworten fehlen („Keine Angaben zum Versand nach Österreich oder in die Schweiz“, „Rücksendefrist nicht eindeutig“, „Keine Kontaktzeiten gefunden“ …),
  4. 3 Beispielantworten auf typische Kundenfragen, nur aus seinen Seiten, mit Link zur Quelle,
  5. den CTA „14 Tage kostenlos testen – Ihr Shop-Wissen ist schon drin“ → /register?branche=shop-kundenservice&src=shop-check&check=<id>.

Formulierung immer „Hinweise für Kundenfragen“, nie Rechtsprüfung.

Die Shop-Vorschau in der Kaltmail (I-038) nutzt denselben Kern: Vor dem Versand einer Outreach-Mail läuft der Check für die Website des Leads, die System-Landing mit ?o=<outreach_id> zeigt dann den Kasten „So würde antworten“.

Ablauf

Schritt Wo Was
Formular ShopCheckController::index (GET/POST /shop-check) Honeypot website, signierter Formular-Zeitstempel (≥ 2 s), URL-Prüfung inkl. EgressGuard (nur öffentliche https-Adressen), gleicher Shop innerhalb 24 h → vorhandenes Ergebnis, Deckel (siehe unten) → Zeile in shop_checks, Weiterleitung auf /shop-check/<id>
Start POST /shop-check/<id>/run (von webroot/js/shop-check.js) Einmal (atomarer Claim queued → running), Session-Lock frei, läuft weiter, wenn der Tab zugeht
Fortschritt GET /shop-check/<id>/status alle 1,5 s: system → pages → answers
1. System _shared-Tool detectShopSystem Startseite, max. 1 Weiterleitung, über apis.shop_homepage (egress_guard: true)
2. Seiten ShopPageImporter::collect über _shared-Tool shopFetchPage apis.shop_site nur im Speicher, egress_guard: true (auch im getrackten Kontext meco: https:443, IP gepinnt, keine Weiterleitungen). Nicht gelesen: Impressum, Über uns, Konto, Warenkorb, Kasse, Datenschutz. Versteckter Text fliegt raus (Prompt-Injection). Zeitbudget 45 s
3. Analyse ShopCheckAnalyzer lokal, deterministisch (Regeln, kein LLM): Themen + Lücken, nur so weit die Seiten wirklich gelesen wurden (siehe unten)
3. Antworten ShopCheckService::answers ein LLM-Aufruf, Profil haiku, max_tokens 900, Kontext ≤ 14.000 Zeichen, strikter Prompt (nur aus den nummerierten Auszügen, Auszüge = Daten), Quelle = Auszugsnummer → Seiten-URL; fremde Links werden entfernt, erfundene Quellen verworfen
Ohne JavaScript bin/cake shop_checks (cronjobs.sh, alle 5 min) führt liegengebliebene Checks aus, markiert hängende (> 5 min) als fehlgeschlagen, löscht nach 30 Tagen

Seitenauswahl und ehrliche Lücken (Prüflauf 29.09.)

Anlass: Der Marketing-Agent hat senf.de, braeckers.com, climatenuts.de, rau-gmbh.de und cayin.com geprüft. Nachgebaut als Fixtures in tests/fixtures/shop_check/, Tests in ShopCheckRealCasesTest.

  • Seitenauswahl (ShopPageImporter::discoverLinks): Jeder Link bekommt Punkte. 4 gibt es für ein Stichwort im letzten Pfad-Segment (/service-hilfe/versand-zahlung/), 3 für eins im Linktext, 2 für eins weiter vorn im Pfad. Schwache Wörter (hilfe, service, fragen) zählen nur als FAQ und nur, wenn es keinen besseren Treffer gibt. Links in Kopf, Fuß oder Navigation bekommen +1. Gelesen wird in der Reihenfolge Versand, Widerruf, Zahlung, FAQ, Kontakt, AGB, je Art höchstens 1–2 Seiten, insgesamt bis zu 12. Mit „Rechtliches“ im Pfad wird nichts ausgeschlossen, gesperrt sind nur Impressum, Datenschutz, Konto, Warenkorb und Kasse.
  • Große Startseiten: shopFetchPage liefert bis zu 2 MB (vorher 600 KB, bei senf.de fehlten dadurch die Footer-Links).
  • Sitemap-Rückfall: Verlinkt die Startseite weniger als 4 Seitenarten, liest der Check /sitemap.xml, auch einen Sitemap-Index und .xml.gz-Teile (höchstens 2, Seiten-Sitemaps zuerst). Es gelten nur Adressen desselben Hosts.
  • Weiterleitungen auf demselben Host (z. B. /?v=<hash> bei cayin.com) werden einmal verfolgt. shopFetchPage gibt dafür bei 30x den location-Header zurück.
  • Ehrliche Lücken: Zu jeder Seitenart hält coverage fest, ob sie gelesen wurde (read), leer war (empty) oder nicht erreichbar (unreachable); fehlt der Eintrag, war sie nirgends verlinkt. Lücken wie „Keine Lieferzeit“ gibt es nur für gelesene Seiten. „Nicht gefunden“ steht nur, wenn die Art nirgends verlinkt war, andere Infoseiten aber gelesen wurden. Nicht erreichbare Seiten heißen „nicht geprüft“. Widerruf ohne gelesene Seite heißt immer „nicht geprüft“, nie „fehlt“.
  • Nichts lesbar (Shop nicht erreichbar oder keine Infoseite lesbar): eigener Fehlerzustand ShopCheckService::UNREADABLE („Wir konnten Ihren Shop gerade nicht lesen …“), ohne Themen und ohne Lücken.
  • Bot-Abwehr (Prod 29.09.: erster Ads-Klick, Shop hinter Cloudflare, 403 „Just a moment…“): detectShopSystem bzw. ShopPageImporter::looksBlocked erkennen HTTP 403, cf-mitigated: challenge, Challenge-Seiten („Just a moment“, „Attention Required“, cf-chl, challenge-platform) und Cloudflare/Captcha bei 429/503. Dann genau ein zweiter Versuch mit Browser-Kopfzeilen (ShopCheckService::BROWSER_HEADERS: Chrome-User-Agent, Accept, Accept-Language; ehrliche Kennung in X-TaskMonkey-Check), auch für die Unterseiten. Challenges werden nie gelöst. Klappt es nicht, Status blocked (ShopCheckService::BLOCKED, kein „später versuchen“), result.blocked_by = cloudflare/firewall. Die Seite zeigt ein E-Mail-Feld („Wir prüfen Ihren Shop manuell …“), POST /shop-check/<id>/contact speichert contact_email + contact_requested_at einmal pro Check und schickt eine Plattform-Mail an Onboarding.alert.recipients.email (Shop, Schutz, E-Mail als Reply-To, Herkunft nur nach Einwilligung wie beim Signup, Link zum Check). Die manuelle Prüfung macht Marcus.
  • Antworten passen zur Regel: Jede Frage bekommt nur Auszüge ihrer Seitenarten (QUESTION_KINDS), und als Quelle zählen nur diese. Danach vergleicht contradictions() die Antworten mit der Regel-Erkennung. Beispiel: Die Regel hat Versand ins Ausland gefunden, die Antwort sagt aber „steht nichts“. Oder die Antwort sagt „Rechnung ja“, obwohl die gelesene Zahlungsseite keine Rechnung nennt. Umgekehrt widerspricht „Rechnung nicht angeboten“, wenn die Regel Rechnung gefunden hat. Dann folgt ein zweiter Aufruf mit Hinweis; widerspricht die Antwort danach immer noch, wird sie weggelassen.
  • Verneinung zählt nicht als Treffer: Zahlarten (und der Rechnungs-Abgleich der Antworten) nutzen ShopCheckAnalyzer::affirms(). Ein Treffer mit Verneinung im selben Satzteil („Kauf auf Rechnung wird derzeit nicht angeboten“, „keinen Kauf auf Rechnung“) ist kein Beleg; Satzende, Gedankenstrich und „aber/sondern/jedoch“ begrenzen den Satzteil. „Rechnung“ allein zählt nur in einer Aufzählung mit anderen Zahlarten („PayPal, Kreditkarte, Rechnung“).
  • Zeiträume werden vereinheitlicht: „2 tagen“ → „2 Tage“, „1-3 werktagen“ → „1–3 Werktage“, „binnen vierzehn Tagen“ → „14 Tage“.
  • Vorschau (I-038) nur mit mindestens 2 gelesenen Infoseiten und mindestens 2 belegten Antworten.

Übernahme beim Signup

/register?…&check=<id> legt die ID als SignupIntent-Quelle shop_check ab (nur aus der Query, 24 Hex). Sie steht danach auch in tenants.signup_source.attribution.shop_check (Auswertung Checks → Signups). Beim Anlegen eines Shop-Kundenservice-Workspaces (branche = shop-service, Aliase shop-kundenservice …) übernimmt ShopCheckService::adopt:

  • die gelesenen Abschnitte als Wissen (knowledge_entries, model = shop_pages, dasselbe wie „Wissen aus Shop-Seiten einlesen“; der Schritt gilt als erledigt),
  • Shopsystem (WooCommerce, Gambio, Shopware 6) und Shop-Adresse als Vorschlag in tenant_settings shop_setup.state.prefill. /manage/shop-setup belegt Schritt 1 damit vor (ShopSetupService::withPrefill); gespeichert wird erst mit dem Formular, also mit Prüfung und AV-Vertrag.

Andere Branchen übernehmen nichts. Die Register-Seite nennt den übernommenen Shop.

Vorschau in der Kaltmail (I-038)

  • Kampagne: lead_campaigns.<id>.shop_preview = ['enabled' => true] (heute taskmonkey_wismo, Kampagne pausiert).
  • sendOutreach ruft vor dem Versand buildShopPreview(url, outreach_id) (meco, Core-Handler App\Tooling\ShopPreviewTools, nur meco laut CoreHandlerRegistry). Gleicher Shop innerhalb 24 h: Kopie des vorhandenen Ergebnisses. Fehler blockieren den Versand nie; der Mailtext bleibt, wie er freigegeben wurde.
  • Landings unter /shop-kundenservice/ (Hauptseite + System-Seiten) laden /js/shop-preview.js. Mit ?o=<outreach_id> holt es GET /shop-check/preview/<o>. Angezeigt wird nur, was nutzbar ist: Check fertig, System unterstützt, mind. 2 Antworten mit Quelle. Sonst bleibt die Landing unverändert.
  • Der CTA im Kasten führt auf /register?branche=shop-kundenservice&src=shop-preview&o=<o>&check=<id>, also mit Übernahme.
  • ?o= hängt sendOutreach/runFollowupSequence seit 29.09. auch an mehrstufige Pfade (/shop-kundenservice/woocommerce/).

Kosten, Missbrauch, Daten

Alles in der Plattform-Config config/platform.php → Platform.shop_check (nie Tenant-Config):

Schlüssel Default Bedeutung
enabled true false → /shop-check liefert 404
limits 3 pro Besucher/Tag, 2 pro Minute, 150 gesamt/Tag PublicChatBudget::consumeLimits, Topf _shop-check, Besucher = HMAC der IP (keine IP gespeichert), Dateien nach 2 Tagen gelöscht
tenant meco ToolRunner-Kontext (Tools, LLM-Profil, Logs in task_executions, Chat-ID shopcheck_<id>)
llm_profile, max_tokens, max_context_chars haiku, 900, 14.000 ein LLM-Aufruf pro Check, ca. 1 Cent
reuse_hours 24 gleicher Host → vorhandenes Ergebnis, nur mit aktueller ShopCheckService::ENGINE_VERSION
retention_days 30 danach löscht bin/cake shop_checks die Zeile

Tabelle shop_checks: ID (24 Hex, 96 bit Zufall, die einzige „Berechtigung“ für die Ergebnis-URL), Quelle (check/outreach), Outreach-ID, Shop-Adresse, System, engine_version (Version der Auswertung; ändert sich die Auswertung fachlich, ENGINE_VERSION hochzählen, sonst gehen bis zu 24 h alte Ergebnisse an Seite und Kaltmail), Status/Fortschritt, Ergebnis-JSON, gelesene Abschnitte (für die Übernahme), adopted_at. Keine IP, keine Namen (Impressum wird nicht gelesen). E-Mail nur bei blocked, wenn der Besucher sie selbst für die manuelle Prüfung einträgt (contact_email, gleiche Löschfrist).

Captcha: Das Projekt hat keins. Heute Honeypot + Formular-Zeitstempel + Deckel. Kommt Turnstile/hCaptcha dazu, gehört die Prüfung in ShopCheckController::index vor consumeBudget.

Vertrauensmodell

Gilt für die Plattform (getrackter Kontext meco). Eingabe ist nur eine URL, kein Code. Jede Verbindung zum Shop läuft über den deklarativen api-Zweig des ToolRunners mit erzwungenem EgressGuard (egress_guard: true), die frühe Formular-Prüfung nutzt denselben Guard. Das LLM bekommt nur sichtbaren Seitentext, seine Antwort wird geprüft (nur 3 Fragen, Quelle aus den Auszügen, keine fremden Links, gekürzt) und auf der Seite escaped ausgegeben.

Tests

tests/TestCase/Service/Shop/ShopCheckServiceTest.php (gefaktes HTTP + LLM: Ablauf, EgressGuard-Pinning, kein Impressum, versteckter Text, Lücken, Quellen, Übernahme, Wiederverwendung, Vorschau, Wartung), tests/TestCase/Service/Shop/ShopCheckBlockedTest.php (Cloudflare-403, Browser-Retry, blocked, Lead-Mail), tests/TestCase/Controller/ShopCheckControllerTest.php (Formular-Schutz, Deckel, Ablauf bis Ergebnis, Vorschau-JSON, E-Mail-Feld bei blocked), tests/TestCase/Service/Onboarding/SignupShopCheckTest.php (Signup), tests/TestCase/Tooling/ShopPreviewToolsTest.php (buildShopPreview).

Zuletzt aktualisiert: 2026-09-29