[{"data":1,"prerenderedAt":1492},["ShallowReactive",2],{"search-de":3,"content-de-developer\u002Faudience-internals":4,"surround-de-\u002Fdeveloper\u002Faudience-internals":1486},[],{"id":5,"title":6,"body":7,"description":1478,"extension":1479,"meta":1480,"navigation":1481,"path":1482,"seo":1483,"stem":1484,"__hash__":1485},"content_de\u002F3.developer\u002F17.audience-internals.md","Zielgruppen-Interna",{"type":8,"value":9,"toc":1454},"minimark",[10,30,51,56,62,104,119,182,215,220,227,280,284,306,345,364,380,384,411,458,480,548,555,606,631,635,661,665,680,730,758,762,799,803,813,890,915,919,959,1075,1100,1142,1151,1155,1187,1190,1232,1253,1280,1284,1287,1291,1318,1364,1368,1396,1400,1432,1436,1440,1443,1446,1450],[11,12,13,14,19,20,24,25,29],"p",{},"Dies ist die Entwicklerreferenz für die Zielgruppen-\u002FKontaktschicht des Convex-Backends: wie ein Kontakt gefunden oder angelegt wird, wie Double Opt-in (DOI) gesteuert wird, wie Themen-Mitgliedschaften geschrieben werden und wie Segmente und Automations-Bedingungen ausgewertet werden. Jeder Pfad der Art „gegeben ein Identifier, finde den Kontakt\" und „passt dieser Kontakt\" in der Codebase läuft über die hier beschriebenen Module. Die produktseitige Sicht auf diese Funktionen finden Sie unter ",[15,16,18],"a",{"href":17},"\u002Fguide\u002Fcontacts","Kontakte",", ",[15,21,23],{"href":22},"\u002Fguide\u002Ftopics","Themen"," und ",[15,26,28],{"href":27},"\u002Fguide\u002Fsegments","Segmente",".",[11,31,32,33,19,37,19,40,24,43,46,47,50],{},"Die Module liegen unter ",[34,35,36],"code",{},"apps\u002Fapi\u002Fconvex\u002Fcontacts\u002F",[34,38,39],{},"apps\u002Fapi\u002Fconvex\u002Ftopics\u002F",[34,41,42],{},"apps\u002Fapi\u002Fconvex\u002Fconditions\u002F",[34,44,45],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002F",". Zu mehreren gibt es ein ADR; die ADRs sind die maßgebliche Designdokumentation, und die Quellkommentare zitieren sie über ihren Pfad (",[34,48,49],{},"docs\u002Fadr\u002F00xx-*.md",").",[52,53,55],"h2",{"id":54},"kontaktauflösung-anlage","Kontaktauflösung & -anlage",[11,57,58,61],{},[34,59,60],{},"apps\u002Fapi\u002Fconvex\u002Fcontacts\u002Fresolution.ts"," ist das eine Find-or-Create-Primitiv. Jeder Eingangspfad — eingehende E-Mail, Channel-Webhook, Massenimport, HTTP-API, Automations-Trigger, Formularübermittlung — erreicht einen Kontakt darüber.",[11,63,64,65,68,69,72,73,77,78,81,82,85,86,89,90,93,94,19,97,19,100,103],{},"Der Lookup ist einheitlich: Jeder Kontakt ist über ",[34,66,67],{},"contactIdentities.by_identifier"," (den ",[34,70,71],{},"(channel, identifier)","-Index) geschlüsselt, ",[74,75,76],"strong",{},"nie"," über ",[34,79,80],{},"contacts.by_email",". Für den Channel ",[34,83,84],{},"email"," wird ",[34,87,88],{},"contacts.email"," aus der primären Identitätszeile denormalisiert, damit alte Lesezugriffe auf ",[34,91,92],{},"contact.email"," weiter funktionieren, aber die Identitätszeile ist das Lookup-Primitiv. E-Mail-Identifier werden kleingeschrieben; telefonbasierte Channels (",[34,95,96],{},"sms",[34,98,99],{},"whatsapp",[34,101,102],{},"phone",") werden wortgetreu übernommen — die Aufrufer normalisieren zuvor auf E.164.",[11,105,106,107,110,111,114,115,118],{},"Der exportierte Helfer ",[34,108,109],{},"resolveContact(ctx, signal)"," (und seine Wire-Mutation ",[34,112,113],{},"resolve",") verzweigt über ",[34,116,117],{},"mode",":",[120,121,122,138],"table",{},[123,124,125],"thead",{},[126,127,128,132,135],"tr",{},[129,130,131],"th",{},"Modus",[129,133,134],{},"Bei Treffer",[129,136,137],{},"Ohne Treffer",[139,140,141,158,170],"tbody",{},[126,142,143,149,155],{},[144,145,146],"td",{},[34,147,148],{},"strict",[144,150,151,152],{},"wirft ",[34,153,154],{},"ALREADY_EXISTS",[144,156,157],{},"anlegen",[126,159,160,165,168],{},[144,161,162],{},[34,163,164],{},"upsert",[144,166,167],{},"gibt die getroffene ID zurück, keine Feldaktualisierung",[144,169,157],{},[126,171,172,177,180],{},[144,173,174],{},[34,175,176],{},"merge",[144,178,179],{},"patcht Felder, bei denen der neue Wert nicht leer ist (bei leer\u002Fundefined gewinnt der Bestand)",[144,181,157],{},[11,183,184,187,188,191,192,195,196,199,200,203,204,207,208,212,213,29],{},[34,185,186],{},"ResolveResult"," ist ",[34,189,190],{},"{ contactId, action }",", wobei ",[34,193,194],{},"action"," ",[34,197,198],{},"'matched' | 'created' | 'updated'"," ist. Das Resolution-Modul ist bewusst ",[74,201,202],{},"effektfrei",": Es besitzt beim Anlegen den Schreibvorgang der Identitätszeile, die Berechnung von ",[34,205,206],{},"searchableText"," und den Soft-Delete-Filter beim Lookup — aber es protokolliert ",[209,210,211],"em",{},"keine"," Aktivität, fächert keine Automations-Trigger auf und pflegt nicht die Kontaktanzahl. Das bleibt bei den Aufrufern, verzweigt über ",[34,214,194],{},[216,217,219],"h3",{"id":218},"channels-und-quellen","Channels und Quellen",[11,221,222,223,226],{},"Zwei Aufzählungen begrenzen die Eingaben (definiert in ",[34,224,225],{},"resolution.ts","):",[228,229,230,251],"ul",{},[231,232,233,236,237,19,239,19,241,19,243,19,245,19,248,29],"li",{},[34,234,235],{},"ChannelKind",": ",[34,238,84],{},[34,240,96],{},[34,242,99],{},[34,244,102],{},[34,246,247],{},"generic",[34,249,250],{},"chat",[231,252,253,236,256,19,259,19,262,19,265,19,268,271,272,275,276,279],{},[34,254,255],{},"ContactSource",[34,257,258],{},"api",[34,260,261],{},"import",[34,263,264],{},"form",[34,266,267],{},"transactional",[34,269,270],{},"inbound",". Das wird zu ",[34,273,274],{},"contacts.source"," und wird in den Metadaten der ",[34,277,278],{},"created","-Aktivität festgehalten.",[216,281,283],{"id":282},"das-effektbündel-beim-anlegen","Das Effektbündel beim Anlegen",[11,285,286,287,290,291,294,295,298,299,305],{},"Aufrufer, die einzeln anlegen, rufen ",[34,288,289],{},"resolveContact"," nicht direkt auf — sie gehen über ",[34,292,293],{},"createContact"," in ",[34,296,297],{},"apps\u002Fapi\u002Fconvex\u002Fcontacts\u002Fcreation.ts",", das das effektfreie Primitiv umschließt und ",[74,300,301,302],{},"nur bei ",[34,303,304],{},"action === 'created'"," das einheitliche Created-Effektbündel auslöst:",[307,308,309,319,325,334],"ol",{},[231,310,311,314,315,318],{},[34,312,313],{},"incrementContactCount(ctx, 1)"," — hält ",[34,316,317],{},"cachedContactCount"," korrekt.",[231,320,321,322,29],{},"Den Automations-Trigger ",[34,323,324],{},"contact_created",[231,326,327,328,330,331,29],{},"Eine ",[34,329,278],{},"-Kontaktaktivitätszeile, getaggt mit ",[34,332,333],{},"metadata.source",[231,335,336,337,340,341,344],{},"Ein ",[34,338,339],{},"contact.created","-Kunden-Webhook-Fanout über ",[34,342,343],{},"scheduleFanout"," (nur Channel E-Mail).",[11,346,347,348,352,353,355,356,359,360,363],{},"Das Modul für den ",[15,349,351],{"href":350},"\u002Fguide\u002Fimporting-contacts","Kontaktimport"," in großen Mengen ist die einzige Ausnahme: Es ruft ",[34,354,289],{}," direkt auf und bündelt ein ",[34,357,358],{},"incrementContactCount"," pro Seite. Diese Effekte in dieser Schicht ",[209,361,362],{},"oberhalb"," des weiterhin effektfreien Primitivs zu halten, ist es, was die gebündelte Zählung des Imports und das Einzel-Anlege-Bündel ohne Doppelzählung koexistieren lässt.",[365,366,369],"callout",{"title":367,"type":368},"Wiederverwendbare Identifier","info",[11,370,371,372,375,376,379],{},"Beim Soft Delete löscht ",[34,373,374],{},"deleteIdentitiesForContact"," jede ",[34,377,378],{},"contactIdentities","-Zeile des Kontakts sofort hart (nicht erst nach dem 30-tägigen Aufbewahrungsfenster). Der Identifier — das datenschutzsensible Datum — wird ab dem ersten Tag wieder verwendbar, sodass das Anlegen eines frischen Kontakts für eine zuvor genutzte Adresse nie kollidiert.",[52,381,383],{"id":382},"double-opt-in-lebenszyklus","Double-Opt-in-Lebenszyklus",[11,385,386,389,390,393,394,397,398,19,401,19,404,19,407,410],{},[34,387,388],{},"apps\u002Fapi\u002Fconvex\u002Fcontacts\u002FdoiLifecycle.ts"," ist der ",[74,391,392],{},"einzige Schreiber"," von ",[34,395,396],{},"contacts.doiStatus"," und dessen Begleitfeldern (",[34,399,400],{},"doiConfirmationToken",[34,402,403],{},"doiTokenExpiresAt",[34,405,406],{},"doiConfirmedAt",[34,408,409],{},"doiAttestedSource","). Es implementiert eine Drei-Zustands-Maschine mit diesen erlaubten Kanten:",[120,412,413,423],{},[123,414,415],{},[126,416,417,420],{},[129,418,419],{},"Von",[129,421,422],{},"Erlaubte Übergänge",[139,424,425,438,449],{},[126,426,427,432],{},[144,428,429],{},[34,430,431],{},"not_required",[144,433,434,435],{},"→ ",[34,436,437],{},"pending",[126,439,440,444],{},[144,441,442],{},[34,443,437],{},[144,445,434,446],{},[34,447,448],{},"confirmed",[126,450,451,455],{},[144,452,453],{},[34,454,448],{},[144,456,457],{},"terminal (keine Kanten)",[11,459,460,461,464,465,468,469,472,473,476,477,118],{},"Es gibt zwei Einstiegspunkte: ",[34,462,463],{},"transition"," (über ",[34,466,467],{},"contactId"," geschlüsselt) und ",[34,470,471],{},"transitionByConfirmationToken"," (über das URL-Token geschlüsselt, genutzt von den kundenseitigen Bestätigungs-Endpunkten). Beide geben ein ",[34,474,475],{},"TransitionOutcome"," zurück — Duplikate, unzulässige, terminale, abgelaufene und nicht gefundene Fälle werden ",[74,478,479],{},"gemeldet, nie geworfen",[120,481,482,494],{},[123,483,484],{},[126,485,486,491],{},[129,487,488],{},[34,489,490],{},"reason",[129,492,493],{},"Bedeutung",[139,495,496,506,516,528,538],{},[126,497,498,503],{},[144,499,500],{},[34,501,502],{},"contact_not_found",[144,504,505],{},"Kein Kontakt zu dieser ID",[126,507,508,513],{},[144,509,510],{},[34,511,512],{},"token_not_found",[144,514,515],{},"Kein Kontakt hält dieses Bestätigungstoken",[126,517,518,523],{},[144,519,520],{},[34,521,522],{},"token_expired",[144,524,525,526],{},"Token liegt hinter ",[34,527,403],{},[126,529,530,535],{},[144,531,532],{},[34,533,534],{},"illegal_edge",[144,536,537],{},"Übergang aus dem aktuellen Zustand nicht erlaubt",[126,539,540,545],{},[144,541,542],{},[34,543,544],{},"terminal",[144,546,547],{},"Bereits in einem Zustand ohne ausgehende Kanten",[11,549,550,551,554],{},"Übergänge sind reducer-getrieben: Ein Reducer gibt ",[34,552,553],{},"{ patch, effects, applied }"," zurück, und der Runner ist die einzige Stelle, die die DB und den Scheduler anfasst. Die Effekte sind:",[228,556,557,577,583,597],{},[231,558,559,562,563,566,567,570,571,573,574,576],{},[34,560,561],{},"send_confirmation_email"," — plant die Bestätigungs-E-Mail ein. Löst nur aus, wenn der Aufrufer eine ",[34,564,565],{},"siteUrl"," liefert ",[74,568,569],{},"und"," der Kontakt eine ",[34,572,84],{}," hat (Admin-Importe, die außerhalb der Reihe vorbestätigen, lassen ",[34,575,565],{}," weg).",[231,578,579,582],{},[34,580,581],{},"fire_topic_subscribed_triggers"," — fächert zum Bestätigungszeitpunkt auf jede DOI-pflichtige Themen-Mitgliedschaft auf, die der Kontakt aktuell hält.",[231,584,585,588,589,592,593,596],{},[34,586,587],{},"contact_activity"," — eine ",[34,590,591],{},"topic_confirmed","-Zeile pro DOI-pflichtiger Mitgliedschaft, plus eine ",[34,594,595],{},"doi_attested","-Zeile auf dem Admin-Attest-Pfad.",[231,598,599,602,603,50],{},[34,600,601],{},"audit_log"," — löst nur auf dem Admin-Attest-Pfad aus (",[34,604,605],{},"doi.admin_attested",[11,607,608,609,612,613,616,617,195,620,623,624,627,628,630],{},"Tokens haben eine TTL von 7 Tagen (",[34,610,611],{},"DOI_TOKEN_TTL_MS","). Eine separate Operation, ",[34,614,615],{},"refreshPendingToken",", erzeugt das Token neu und versendet die E-Mail erneut, ",[74,618,619],{},"ohne",[34,621,622],{},"doiStatus"," zu ändern — sie verweigert mit ",[34,625,626],{},"not_pending",", wenn der Kontakt aktuell nicht ",[34,629,437],{}," ist. Sie liegt in diesem Modul, damit jeder Schreibzugriff auf die DOI-Felder über eine Datei läuft.",[216,632,634],{"id":633},"admin-attest","Admin-Attest",[11,636,637,638,641,642,645,646,648,649,651,652,654,655,657,658,660],{},"Wenn ein Kontakt bei einer Quellplattform bereits DOI-bestätigt war (Mailchimp, Klaviyo, Stripe, eine vertrauenswürdige CSV), kann der Importpfad das außerhalb der Reihe attestieren. Die Admin-Attest-Variante (",[34,639,640],{},"{ to: 'confirmed', source: 'admin_attest', attestSource }",") lockert die sonst verweigerte Kante ",[34,643,644],{},"not_required → confirmed",", hält ",[34,647,409],{}," fest und erzeugt den ",[34,650,601],{},"-Effekt plus die ",[34,653,595],{},"-Aktivität. Der token-geschlüsselte Bestätigungspfad kann ",[34,656,448],{}," nie aus ",[34,659,431],{}," erreichen.",[52,662,664],{"id":663},"themen-abonnement","Themen-Abonnement",[11,666,667,389,670,393,672,675,676,679],{},[34,668,669],{},"apps\u002Fapi\u002Fconvex\u002Ftopics\u002Fsubscription.ts",[74,671,392],{},[34,673,674],{},"contactTopics",". Es besitzt außerdem jede Pflege von ",[34,677,678],{},"topics.cachedMemberCount",", das DOI-Gate beim Abonnieren und das quellenabhängige Effektbündel beim Abbestellen. Es gibt fünf Einstiegspunkte, nach Form geschlüsselt:",[120,681,682,692],{},[123,683,684],{},[126,685,686,689],{},[129,687,688],{},"Einstiegspunkt",[129,690,691],{},"Form",[139,693,694,708,720],{},[126,695,696,705],{},[144,697,698,701,702],{},[34,699,700],{},"subscribe"," \u002F ",[34,703,704],{},"subscribeMany",[144,706,707],{},"ein Thema, ein oder mehrere Kontakte",[126,709,710,718],{},[144,711,712,701,715],{},[34,713,714],{},"unsubscribe",[34,716,717],{},"unsubscribeMany",[144,719,707],{},[126,721,722,727],{},[144,723,724],{},[34,725,726],{},"unsubscribeAllForContact",[144,728,729],{},"ein Kontakt, ein Thema oder alle Themen",[11,731,732,735,736,19,739,742,743,746,747,749,750,753,754,757],{},[34,733,734],{},"SubscribeOutcome"," löst zu einem von ",[34,737,738],{},"subscribed",[34,740,741],{},"pending_doi"," oder ",[34,744,745],{},"already_member"," auf. Das Ergebnis ",[34,748,741],{}," trägt das frisch geschriebene ",[34,751,752],{},"doiToken",", damit ein Aufrufer, der eine Geschwisterzeile festhält (das ",[34,755,756],{},"formSubmissions.confirmationToken"," des Formularmoduls), den Kontakt nicht erneut lesen muss.",[216,759,761],{"id":760},"das-doi-gate-beim-abonnieren","Das DOI-Gate beim Abonnieren",[11,763,764,765,187,768,770,771,774,775,778,779,781,782,785,786,789,790,792,793,795,796,798],{},"Beim Abonnieren ist DOI genau dann erforderlich, wenn ",[34,766,767],{},"topic.requireDoubleOptIn === true",[74,769,569],{}," der Aufrufer kein ",[34,772,773],{},"skipDoi"," übergeben hat. Steht DOI ",[209,776,777],{},"nicht"," im Weg (oder ist der Kontakt bereits ",[34,780,448],{},"), löst der Trigger ",[34,783,784],{},"topic_subscribed"," sofort aus. Andernfalls übergibt das Modul an ",[34,787,788],{},"transition(... to: 'pending')"," des DOI-Lebenszyklus und gibt ",[34,791,741],{}," zurück — es löst den Trigger ",[74,794,777],{}," selbst aus, denn der Effekt ",[34,797,581],{}," des Lebenszyklus deckt zum Bestätigungszeitpunkt jede DOI-pflichtige Mitgliedschaft auf einmal ab und würde sonst doppelt auslösen.",[216,800,802],{"id":801},"quellenabhängige-unsubscribe-effekte","Quellenabhängige Unsubscribe-Effekte",[11,804,805,806,809,810,226],{},"Effekte pro Aufruf beim Abbestellen werden über die ",[34,807,808],{},"source"," des Unsubscribes gegatet. Diese Tabelle ist die einzige Stelle, an der „welche Seiteneffekte für welchen Auslöser feuern\" steht (",[34,811,812],{},"effectFlagsForUnsubscribeSource",[120,814,815,834],{},[123,816,817],{},[126,818,819,822,825,828],{},[129,820,821],{},"Quelle",[129,823,824],{},"Formularbestätigungen löschen",[129,826,827],{},"Kampagnen-Unsub-Statistiken erhöhen",[129,829,830,833],{},[34,831,832],{},"topic.unsubscribed","-Webhook auslösen",[139,835,836,850,864,877],{},[126,837,838,843,846,848],{},[144,839,840],{},[34,841,842],{},"public_email_link",[144,844,845],{},"ja",[144,847,845],{},[144,849,845],{},[126,851,852,857,859,862],{},[144,853,854],{},[34,855,856],{},"preferences_page",[144,858,845],{},[144,860,861],{},"nein",[144,863,845],{},[126,865,866,871,873,875],{},[144,867,868],{},[34,869,870],{},"admin",[144,872,861],{},[144,874,861],{},[144,876,861],{},[126,878,879,884,886,888],{},[144,880,881],{},[34,882,883],{},"public_api",[144,885,861],{},[144,887,861],{},[144,889,861],{},[11,891,892,893,896,897,900,901,904,905,908,909,911,912,914],{},"Effekte pro Aufruf (das ",[34,894,895],{},"cachedMemberCount","-Patch, ",[34,898,899],{},"contact.updatedAt",", das Löschen der Formularbestätigungen, die Kampagnenstatistiken, der Webhook) feuern ",[74,902,903],{},"einmal pro Aufruf",", unabhängig davon, wie viele Mitgliedschaften betroffen sind; Effekte pro Mitgliedschaft (das Löschen der Mitgliedschaftszeile und die ",[34,906,907],{},"topic_unsubscribed","-Aktivitätszeile) feuern N-mal. ",[34,910,726],{}," ist der Einstiegspunkt, den der öffentliche Abmeldelink nutzt; er gruppiert die Löschungen pro Thema, sodass der ",[34,913,895],{}," jedes Themas einmal gepatcht wird.",[52,916,918],{"id":917},"conditions-registry","Conditions-Registry",[11,920,921,923,924,926,927,931,932,935,936,701,939,701,942,701,945,948,949,952,953,955,956,958],{},[34,922,42],{}," enthält die typisierte, austauschbare Registry, die sowohl ",[15,925,28],{"href":27}," als auch den Bedingungsschritt der ",[15,928,930],{"href":929},"\u002Fdeveloper\u002Fautomation-internals","Automation"," antreibt. Es gibt drei Bedingungsarten, jede ein ",[34,933,934],{},"ConditionTypeModule"," mit ",[34,937,938],{},"parseCondition",[34,940,941],{},"preloadLookup",[34,943,944],{},"preloadLookupForContacts",[34,946,947],{},"evaluate"," (",[34,950,951],{},"conditions\u002Ftypes.ts","). ",[34,954,941],{}," ist der Preload über die gesamte Population; ",[34,957,944],{}," ist die beschränkte Variante pro Kontakt, die der Einzelkontakt-Automationspfad und der seitenweise Segment-Builder nutzen:",[120,960,961,974],{},[123,962,963],{},[126,964,965,968,971],{},[129,966,967],{},"Art",[129,969,970],{},"Felder",[129,972,973],{},"Operatoren",[139,975,976,1035,1056],{},[126,977,978,983,997],{},[144,979,980],{},[34,981,982],{},"contact_property",[144,984,985,986,19,988,19,991,19,994,996],{},"jedes eingebaute Feld (",[34,987,84],{},[34,989,990],{},"firstName",[34,992,993],{},"lastName",[34,995,808],{},") oder ein eigener Property-Key",[144,998,999,19,1002,19,1005,19,1008,19,1011,19,1014,19,1017,19,1020,19,1023,19,1026,19,1029,19,1032],{},[34,1000,1001],{},"equals",[34,1003,1004],{},"not_equals",[34,1006,1007],{},"contains",[34,1009,1010],{},"not_contains",[34,1012,1013],{},"gt",[34,1015,1016],{},"lt",[34,1018,1019],{},"gte",[34,1021,1022],{},"lte",[34,1024,1025],{},"is_empty",[34,1027,1028],{},"not_empty",[34,1030,1031],{},"is_true",[34,1033,1034],{},"is_false",[126,1036,1037,1042,1050],{},[144,1038,1039],{},[34,1040,1041],{},"email_activity",[144,1043,1044,19,1047],{},[34,1045,1046],{},"opened",[34,1048,1049],{},"clicked",[144,1051,1052,19,1054],{},[34,1053,1031],{},[34,1055,1034],{},[126,1057,1058,1063,1069],{},[144,1059,1060],{},[34,1061,1062],{},"topic_membership",[144,1064,1065,1066],{},"eine ",[34,1067,1068],{},"topicId",[144,1070,1071,19,1073],{},[34,1072,1001],{},[34,1074,1004],{},[11,1076,1077,1078,948,1081,1084,1085,1088,1089,1092,1093,1095,1096,1099],{},"Der Wert der Registry liegt in der Nahtstelle der ",[74,1079,1080],{},"gebündelten Auswertung",[34,1082,1083],{},"conditions\u002Findex.ts","): Statt pro Kontakt erneut abzufragen, gruppiert ",[34,1086,1087],{},"preloadConditionsLookup"," die Bedingungen nach Art, übergibt jeden Batch an sein Modul und legt ein typisiertes, nach Art geschlüsseltes Lookup ab. (",[34,1090,1091],{},"preloadConditionsLookupForContacts"," ist das beschränkte Geschwister, das für die Pfade pro Kontakt jeweils ",[34,1094,944],{}," des Moduls antreibt.) Danach ist ",[34,1097,1098],{},"evaluateOne(condition, contact, lookup)"," eine pure O(1)-Prüfung. Die Preloads:",[228,1101,1102,1114,1130],{},[231,1103,1104,1106,1107,1110,1111,29],{},[34,1105,982],{}," löst IDs eigener Properties über den Key auf und lädt dann ",[34,1108,1109],{},"contactPropertyValues"," für diese Properties vor; eingebaute Felder werden direkt vom Contact-Dokument gelesen. String-Operatoren sind Groß-\u002FKleinschreibung-unabhängig; numerische Operatoren konvertieren über ",[34,1112,1113],{},"Number()",[231,1115,1116,1118,1119,701,1122,1125,1126,1129],{},[34,1117,1041],{}," wird direkt über die denormalisierten Flags ",[34,1120,1121],{},"contact.hasOpened",[34,1123,1124],{},"contact.hasClicked"," ausgewertet (gepflegt von ",[34,1127,1128],{},"contactActivities\u002Fwriter.ts","); es hat kein vorgeladenes Lookup und führt keinen Scan durch — ein O(1)-Read auf der bereits geladenen Kontaktzeile.",[231,1131,1132,1134,1135,1138,1139,29],{},[34,1133,1062],{}," liest ",[34,1136,1137],{},"contactTopics.by_topic"," für jedes referenzierte Thema in ein Mitgliedschafts-",[34,1140,1141],{},"Set",[11,1143,1144,1146,1147,1150],{},[34,1145,938],{}," wirft bei einem Verstoß gegen Form, Operator oder Feld — Aufrufer behandeln einen Parse-Fehler als ",[74,1148,1149],{},"korrupte gespeicherte Daten, nicht als Nutzereingabe",", denn Bedingungen werden zur Schreibzeit gegen das Storage validiert.",[52,1152,1154],{"id":1153},"segmentauswertung-die-listing-engine","Segmentauswertung & die Listing-Engine",[11,1156,1157,1158,1161,1162,1165,1166,1169,1170,19,1173,19,1176,1179,1180,77,1183,1186],{},"Die Schicht über der Auswertung einzelner Bedingungen — Filternormalisierung, die Kombination leer\u002FUND\u002FODER und der Scan lebender Kontakte — liegt in ",[34,1159,1160],{},"apps\u002Fapi\u002Fconvex\u002Fconditions\u002FsegmentMatch.ts",". Sie ist die alleinige Eigentümerin von „gleiche eine Kontaktpopulation gegen einen gespeicherten Filtersatz ab\" und konsolidiert Logik, die zuvor über fünf offen ausgeschriebene Kopien auseinandergedriftet war. ",[34,1163,1164],{},"apps\u002Fapi\u002Fconvex\u002Fconditions\u002Findex.ts"," re-exportiert aus ",[34,1167,1168],{},"segmentMatch.ts"," eine rückwärtskompatible Oberfläche (",[34,1171,1172],{},"evaluateCondition",[34,1174,1175],{},"evaluateSegmentCount",[34,1177,1178],{},"countLiveMatchesForSegments","), die ",[34,1181,1182],{},"apps\u002Fapi\u002Fconvex\u002Fsegments.ts",[34,1184,1185],{},".\u002Fconditions"," importiert.",[11,1188,1189],{},"Das Modul hat zwei Schichten:",[228,1191,1192,1210],{},[231,1193,1194,1197,1198,1201,1202,1205,1206,1209],{},[74,1195,1196],{},"Purer Kern"," — ",[34,1199,1200],{},"parseSegmentFilters"," + ",[34,1203,1204],{},"makeSegmentPredicate",". Diese ",[209,1207,1208],{},"werfen"," bei korrupten Filtern. Leere Bedingungen matchen jeden Kontakt; andernfalls werden Bedingungen mit kurzschließendem UND\u002FODER kombiniert. Das ist die Testoberfläche und die Nahtstelle, die der Versandpfad nutzt (er baut das Prädikat direkt, um die Eignungsfilterung in einem einzigen Durchlauf einzuflechten).",[231,1211,1212,1197,1215,19,1218,19,1221,1223,1224,1227,1228,1231],{},[74,1213,1214],{},"Nachsichtige asynchrone Bequemlichkeiten",[34,1216,1217],{},"countLiveMatches",[34,1219,1220],{},"matchLiveContacts",[34,1222,1178],{},". Diese backen den paginierten, Soft-Deletes ausschließenden ",[34,1225,1226],{},"by_deleted_at","-Scan ein und behandeln korrupte Filter als ",[74,1229,1230],{},"null Treffer"," — die Haltung, die Vorschau-, Zähl- und Cron-Pfade wollen.",[11,1233,1234,1235,1238,1239,1242,1243,701,1246,1249,1250,1252],{},"Ein Cron (",[34,1236,1237],{},"crons.ts",", alle 30 Minuten) ruft ",[34,1240,1241],{},"refreshAllSegmentCounts"," auf, um ",[34,1244,1245],{},"segments.cachedCount",[34,1247,1248],{},"cachedCountUpdatedAt"," für die Segmentliste in der UI frisch zu halten. ",[34,1251,1178],{}," wertet viele Segmente in einem Durchlauf aus: Es flacht die Bedingungen jedes Segments in ein einziges vorgeladenes Lookup ab und nutzt einen Kontakt-Scan wieder.",[365,1254,1256],{"title":1255,"type":368},"Listing-Engine vs. Segment-Scan",[11,1257,1258,1259,1263,1264,1267,1268,1271,1272,1275,1276,1279],{},"Die seitenweise, sortierbare Kontaktliste im Dashboard reitet über indizierte Reads auf der Resource-Listing-Engine (siehe ",[15,1260,1262],{"href":1261},"\u002Fdeveloper\u002Fdecisions\u002F010-listing-engine","ADR-010: Listing Engine"," und den Index ",[34,1265,1266],{},"by_deleted_at_and_created_at",", in dem ",[34,1269,1270],{},"deletedAt"," führt, sodass soft-gelöschte Zeilen ",[209,1273,1274],{},"innerhalb"," des Index wegfallen). Segment-Matching ist eine andere Operation: beliebige Filterprädikate, die nicht auf einem einzelnen Index reiten können, also ein bewusst ",[74,1277,1278],{},"beschränkter Full-Table-Scan"," lebender Kontakte (siehe „Beschränkte Scans\" weiter unten).",[52,1281,1283],{"id":1282},"überraschende-verhaltensweisen","Überraschende Verhaltensweisen",[11,1285,1286],{},"Ein paar Verhaltensweisen bringen neue Leser ins Stolpern — sie sind beabsichtigt, aber erwähnenswert.",[216,1288,1290],{"id":1289},"doi-ist-standardmäßig-erforderlich","DOI ist standardmäßig erforderlich",[11,1292,1293,1294,948,1297,19,1300,1303,1304,1306,1307,1309,1310,1313,1314,1317],{},"Neue Themen haben standardmäßig ",[34,1295,1296],{},"requireDoubleOptIn: true",[34,1298,1299],{},"topics\u002Ftopics.ts",[34,1301,1302],{},"args.requireDoubleOptIn ?? true","). Ein Kontakt, der ein frisches Thema abonniert, landet daher in ",[34,1305,437],{}," und muss bestätigen, es sei denn, die Betreiberin stellt das Thema ausdrücklich auf Single Opt-in oder der Aufrufer übergibt ",[34,1308,773],{},". Neu angelegte Kontakte selbst starten bei ",[34,1311,1312],{},"doiStatus: 'not_required'"," — DOI wird ",[74,1315,1316],{},"auf Themenebene"," gegatet, angewendet beim Anlegen einer Mitgliedschaft, nicht beim Anlegen des Kontakts.",[365,1319,1321],{"title":1320,"type":368},"Der doubleOptIn-Schalter pro Formular erzwingt DOI",[11,1322,1323,1324,1327,1328,1331,1332,1335,1336,948,1340,1343,1344,1347,1348,1351,1352,1355,1356,1359,1360,1363],{},"Das Feld ",[34,1325,1326],{},"doubleOptIn"," eines Formulars ist die ",[74,1329,1330],{},"Vereinigung"," von Formular- und Themensteuerung: Ist ",[34,1333,1334],{},"form.doubleOptIn === true",", übergibt der Übermittlungspfad der ",[15,1337,1339],{"href":1338},"\u002Fguide\u002Fforms","Formulare",[34,1341,1342],{},"forms\u002Fsubmission.ts",") ",[34,1345,1346],{},"forceDoi: true"," an ",[34,1349,1350],{},"topics.subscription.subscribe",", und das Subscribe-Gate (",[34,1353,1354],{},"topics\u002Fsubscription.ts",") verlangt DOI, wenn ",[34,1357,1358],{},"topic.requireDoubleOptIn === true || forceDoi === true"," (und ",[34,1361,1362],{},"skipDoi !== true","). Ein Formular kann also selbst bei einem Single-Opt-in-Thema eine Bestätigung verlangen, es kann ein DOI-pflichtiges Thema aber nie aufweichen. Segment-Zielgruppen sind überhaupt nie DOI-gegatet.",[216,1365,1367],{"id":1366},"kontakte-ohne-e-mail","Kontakte ohne E-Mail",[11,1369,1370,1372,1373,19,1375,19,1377,742,1379,1381,1382,1384,1385,1387,1388,1391,1392,1395],{},[34,1371,88],{}," ist optional. Ein Kontakt, der über ",[34,1374,96],{},[34,1376,99],{},[34,1378,102],{},[34,1380,247],{}," hereinkam, hat überhaupt keine E-Mail. Identitäts-Lookups laufen über ",[34,1383,67],{},", sodass ein Kontakt ohne E-Mail über seinen Channel-Identifier vollständig adressierbar ist. Code, der eine E-Mail braucht, sichert sich entsprechend ab: Der ",[34,1386,832],{},"-Webhook fällt auf ",[34,1389,1390],{},"''"," zurück, wenn ein Kontakt keine E-Mail hat (der Payload-Vertrag verlangt einen String), und ",[34,1393,1394],{},"ensureEmailIdentity"," ist bei Kontakten ohne E-Mail ein No-Op.",[216,1397,1399],{"id":1398},"beschränkte-scans","Beschränkte Scans",[11,1401,1402,1403,1405,1406,1408,1409,1412,1413,948,1415,1418,1419,1421,1422,701,1424,1426,1427,1431],{},"Segment-Matching ist ein ",[74,1404,1278],{},", kein indizierter Read — ein beliebiges Filterprädikat kann nicht auf einem einzelnen Index reiten. ",[34,1407,1168],{}," scannt lebende Kontakte in beschränkten Seiten zu 500 Zeilen über den auf ",[34,1410,1411],{},"deletedAt === undefined"," fixierten Index ",[34,1414,1226],{},[34,1416,1417],{},"forEachLiveContact","), sodass soft-gelöschte Zeilen innerhalb des Index wegfallen. (",[34,1420,1041],{}," ist kein Scan mehr — es ist ein O(1)-Read auf den denormalisierten Flags ",[34,1423,1121],{},[34,1425,1124],{},".) Behalten Sie das bei sehr großen Zielgruppen im Kopf — der Zähl-Cron amortisiert den Segment-Scan, und der ",[15,1428,1430],{"href":1429},"\u002Fdeveloper\u002Fcampaign-internals","Kampagnen-Versandpfad"," führt dasselbe Prädikat einmal pro Versand statt einmal pro Vorschau aus.",[52,1433,1435],{"id":1434},"siehe-auch","Siehe auch",[1437,1438],"link-card",{"description":1439,"title":18,"to":17},"Die produktseitige Sicht auf Kontakte, Identitäten und die Aktivitätszeitleiste.",[1437,1441],{"description":1442,"title":23,"to":22},"Wie Themen, Double Opt-in und Präferenzen für Endnutzer funktionieren.",[1437,1444],{"description":1445,"title":28,"to":27},"Wiederverwendbare Zielgruppenfilter im Dashboard bauen.",[1437,1447],{"description":1448,"title":1449,"to":929},"Die Automations-Engine, die sich die Conditions-Registry teilt.","Automations-Interna",[1437,1451],{"description":1452,"title":1453,"to":1429},"Der Zielgruppenauflösungs- und Versandpfad, der Themen und Segmente konsumiert.","Kampagnen-Interna",{"title":1455,"searchDepth":1456,"depth":1456,"links":1457},"",2,[1458,1463,1466,1470,1471,1472,1477],{"id":54,"depth":1456,"text":55,"children":1459},[1460,1462],{"id":218,"depth":1461,"text":219},3,{"id":282,"depth":1461,"text":283},{"id":382,"depth":1456,"text":383,"children":1464},[1465],{"id":633,"depth":1461,"text":634},{"id":663,"depth":1456,"text":664,"children":1467},[1468,1469],{"id":760,"depth":1461,"text":761},{"id":801,"depth":1461,"text":802},{"id":917,"depth":1456,"text":918},{"id":1153,"depth":1456,"text":1154},{"id":1282,"depth":1456,"text":1283,"children":1473},[1474,1475,1476],{"id":1289,"depth":1461,"text":1290},{"id":1366,"depth":1461,"text":1367},{"id":1398,"depth":1461,"text":1399},{"id":1434,"depth":1456,"text":1435},"Backend-Referenz für Kontaktauflösung, den Double-Opt-in-Lebenszyklus, Themen-Abonnements, die Conditions-Registry und die Segmentauswertung.","md",{},true,"\u002Fdeveloper\u002Faudience-internals",{"title":6,"description":1478},"3.developer\u002F17.audience-internals","cYqIawnErCsNFyjks_Sb-8sOTvvEBtaEto17voX1mMw",[1487,1489],{"title":1453,"path":1429,"stem":1488,"children":-1},"3.developer\u002F16.campaign-internals",{"title":1490,"path":929,"stem":1491,"children":-1},"Automationen im Detail","3.developer\u002F18.automation-internals",1786915108889]