[{"data":1,"prerenderedAt":1375},["ShallowReactive",2],{"search-de":3,"content-de-developer\u002Fautomation-internals":4,"surround-de-\u002Fdeveloper\u002Fautomation-internals":1366},[],{"id":5,"title":6,"body":7,"description":1358,"extension":1359,"meta":1360,"navigation":1361,"path":1362,"seo":1363,"stem":1364,"__hash__":1365},"content_de\u002F3.developer\u002F18.automation-internals.md","Automationen im Detail",{"type":8,"value":9,"toc":1332},"minimark",[10,25,65,70,81,116,121,140,167,171,178,236,270,274,293,297,309,317,321,350,360,378,410,414,495,514,518,528,613,631,651,654,661,737,744,838,865,886,890,915,919,956,966,983,987,1015,1038,1042,1059,1086,1090,1100,1158,1188,1192,1199,1246,1274,1310,1314,1318,1323,1328],[11,12,13,14,19,20,24],"p",{},"Diese Seite ist die Entwicklerreferenz dafür, wie trigger-basierte Automationen intern ausgeführt werden. Die produktseitige Einführung zum Bauen von Automationen in der Oberfläche finden Sie unter ",[15,16,18],"a",{"href":17},"\u002Fguide\u002Fautomations","Automationen",". Alles, was hier beschrieben wird, liegt in ",[21,22,23],"code",{},"apps\u002Fapi\u002Fconvex\u002Fautomations\u002F",".",[11,26,27,28,31,32,35,36,39,40,43,44,47,48,52,53,56,57,60,61,64],{},"Eine Automation ist eine Zeile in der Tabelle ",[21,29,30],{},"automations",": ein ",[21,33,34],{},"triggerType",", eine optionale ",[21,37,38],{},"triggerConfig",", ein ",[21,41,42],{},"status"," und eine geordnete Liste von ",[21,45,46],{},"automationSteps",". Wenn ein Trigger für einen Kontakt auslöst, fügt der ",[49,50,51],"strong",{},"Trigger-Fanout"," eine Zeile in ",[21,54,55],{},"automationRuns"," ein und plant den ",[49,58,59],{},"Step Walker"," ein, der die Schritte einen nach dem anderen ausführt und jeden davon als Zeile in ",[21,62,63],{},"automationStepRuns"," protokolliert.",[66,67,69],"h2",{"id":68},"die-ausführungs-engine-step-walker","Die Ausführungs-Engine (Step Walker)",[11,71,72,73,76,77,80],{},"Der Walker ist ",[21,74,75],{},"apps\u002Fapi\u002Fconvex\u002Fautomations\u002FstepWalker.ts"," — ein ",[21,78,79],{},"'use node'","-Modul, das die eigentliche Ausführung einer Automation verantwortet. Es ist der einzige Einstiegspunkt zum Ausführen von Schritten; die Logik pro Schritt liegt in den Schritt-Modulen, an die er dispatcht.",[11,82,83,84,87,88,91,92,95,96,99,100,103,104,107,108,111,112,115],{},"Ein Durchlauf rückt pro ",[21,85,86],{},"executeStep","-Aufruf um genau einen Schritt vor. Es gibt im Walker keine ",[21,89,90],{},"if (step.kind === ...)","-Verzweigung — er ermittelt das Modul des Schritts über ",[21,93,94],{},"stepModuleFor(step.stepType)"," und ruft zuerst ",[21,97,98],{},"parseConfig",", dann ",[21,101,102],{},"execute"," auf. Das Modul liefert ein ",[21,105,106],{},"StepOutcome"," von ",[21,109,110],{},"{ status: 'completed', emailSendId?, nextStepIndex? }"," oder ",[21,113,114],{},"{ status: 'failed', error }"," zurück.",[117,118,120],"h3",{"id":119},"atomares-beanspruchen-eines-schritts","Atomares Beanspruchen eines Schritts",[11,122,123,124,127,128,131,132,135,136,139],{},"Zwei unabhängige Scheduler können denselben ausstehenden Schritt ansteuern: das ursprüngliche ",[21,125,126],{},"ctx.scheduler.runAfter(...)"," aus dem Moment, in dem der Schritt eingeplant wurde, ",[49,129,130],{},"und"," der Cron ",[21,133,134],{},"process pending delays"," (der jeden ausstehenden Schritt erneut dispatcht, dessen Verzögerung abgelaufen ist). Damit eine verzögerungsgesteuerte E-Mail nicht zweimal hinausgeht, muss ein frischer Dispatch (",[21,137,138],{},"retryCount === 0",") den Schritt atomar beanspruchen, bevor er ausgeführt wird.",[11,141,142,143,146,147,150,151,154,155,158,159,162,163,166],{},"Der Claim ist ",[21,144,145],{},"markStepExecuting"," in ",[21,148,149],{},"apps\u002Fapi\u002Fconvex\u002Fautomations\u002FstepExecutorQueries.ts"," — ein einzelnes Compare-and-Set von ",[21,152,153],{},"pending → executing",". Nur der erste Aufrufer gewinnt; ein zweiter Aufruf sieht ",[21,156,157],{},"executing"," (oder einen späteren Status), erhält ",[21,160,161],{},"{ claimed: false }"," und bricht still ab. Retries (",[21,164,165],{},"retryCount > 0",") setzen auf einem Step Run auf, den dieselbe Kette bereits besitzt, und überspringen deshalb den Claim.",[117,168,170],{"id":169},"retries","Retries",[11,172,173,174,177],{},"Ein Schritt, dessen Modul ",[21,175,176],{},"failed"," zurückgibt (oder eine Exception wirft), wird nach einem festen Backoff-Schema wiederholt, bevor der Durchlauf aufgegeben wird:",[179,180,181,194],"table",{},[182,183,184],"thead",{},[185,186,187,191],"tr",{},[188,189,190],"th",{},"Versuch",[188,192,193],{},"Verzögerung vor dem Retry",[195,196,197,206,214,222],"tbody",{},[185,198,199,203],{},[200,201,202],"td",{},"1. Retry",[200,204,205],{},"1 s",[185,207,208,211],{},[200,209,210],{},"2. Retry",[200,212,213],{},"5 s",[185,215,216,219],{},[200,217,218],{},"3. Retry",[200,220,221],{},"30 s",[185,223,224,227],{},[200,225,226],{},"Nach 3 Retries",[200,228,229,230,232,233],{},"Schritt als ",[21,231,176],{}," markiert, Durchlauf ",[21,234,235],{},"cancelled",[11,237,238,241,242,245,246,249,250,253,254,257,258,261,262,265,266,269],{},[21,239,240],{},"MAX_RETRY_ATTEMPTS"," ist 3 und ",[21,243,244],{},"RETRY_DELAYS_MS"," ist ",[21,247,248],{},"[1000, 5000, 30000]"," (ms), beide in ",[21,251,252],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002Fconstants.ts",". Beim endgültigen Fehlschlag ruft der Walker ",[21,255,256],{},"markStepFailed"," und anschließend ",[21,259,260],{},"cancelAutomationRun"," auf — ein Schritt, der seine Retries aufbraucht, bricht den gesamten Durchlauf ab. Der Walker ruft außerdem ",[21,263,264],{},"internal.automations.lifecycle.recordRunFailure"," auf; nach 5 aufeinanderfolgenden fehlgeschlagenen Durchläufen (",[21,267,268],{},"AUTOMATION_FAILURE_BREAKER_THRESHOLD",") wird die Automation über den Circuit Breaker automatisch pausiert.",[117,271,273],{"id":272},"schleifen-obergrenze","Schleifen-Obergrenze",[11,275,276,277,280,281,284,285,288,289,292],{},"Ein Bedingungsschritt kann auf ",[49,278,279],{},"jeden beliebigen"," Zielindex verzweigen, auch auf einen früheren Schritt (der Editor lässt das zu). Ohne Schutzmechanismus würde eine Rückwärtsverzweigung endlos schleifen und den E-Mail-Schritt bei jedem Durchgang erneut versenden. Der Walker erzwingt ",[21,282,283],{},"MAX_STEPS_PER_RUN = 100",": Jeder erfolgreiche Claim erhöht ",[21,286,287],{},"automationRuns.stepsExecuted",", und sobald dieser Zähler die Obergrenze überschreitet, wird der Durchlauf als fehlgeschlagen markiert und mit der Meldung ",[21,290,291],{},"Automation exceeded 100 step executions — cancelled to prevent a loop"," abgebrochen. 100 liegt weit über jeder legitimen linearen Automationslänge.",[117,294,296],{"id":295},"schutz-über-den-aktiv-status","Schutz über den Aktiv-Status",[11,298,299,301,302,305,306,24],{},[21,300,86],{}," liest die übergeordnete Automation bei jedem Schritt neu ein. Gilt ",[21,303,304],{},"automation.status !== 'active'",", wird der aktuelle Schritt als fehlgeschlagen markiert und der ",[49,307,308],{},"Durchlauf abgebrochen",[310,311,314],"callout",{"title":312,"type":313},"Pausieren bricht fällige Durchläufe ab","warning",[11,315,316],{},"Das Pausieren einer Automation greift nicht rückwirkend in laufende Durchläufe ein, aber der nächste fällig werdende Schritt eines laufenden Durchlaufs findet die Automation als nicht aktiv vor und bricht diesen Durchlauf ab. In der Praxis läuft die laufende Kohorte einer pausierten Automation aus, während der nächste Schritt jedes Kontakts fällig wird — es ist kein Einfrieren mit späterem Fortsetzen.",[66,318,320],{"id":319},"lifecycle-zustandsmaschine-der-automation","Lifecycle-Zustandsmaschine der Automation",[11,322,323,324,327,328,331,332,107,335,337,338,341,342,345,346,349],{},"Die Zustandsmaschine für ",[21,325,326],{},"automations.status"," liegt in ",[21,329,330],{},"apps\u002Fapi\u002Fconvex\u002Fautomations\u002Flifecycle.ts"," — dem ",[49,333,334],{},"einzigen Schreiber",[21,336,42],{}," und den zugehörigen Feldern ",[21,339,340],{},"activatedAt",", ",[21,343,344],{},"pausedAt"," und ",[21,347,348],{},"updatedAt"," (ADR-0024). Drei Zustände mit drei produktseitig adressierbaren Kanten:",[351,352,357],"pre",{"className":353,"code":355,"language":356},[354],"language-text","draft   → active            (activate; validates trigger config + ≥1 step)\nactive  → paused            (pause)\npaused  → active            (resume; re-validates trigger config + ≥1 step)\n","text",[21,358,355],{"__ignoreMap":359},"",[11,361,362,365,366,369,370,373,374,377],{},[21,363,364],{},"active → draft"," wird als ",[21,367,368],{},"illegal_edge"," abgelehnt — Admins müssen zuerst pausieren. Der Graph der zulässigen Kanten ist eine einzige Konstante ",[21,371,372],{},"LEGAL_EDGES"," statt verstreuter ",[21,375,376],{},"if (status !== ...)","-Prüfungen.",[11,379,380,381,341,384,345,387,146,390,393,394,397,398,401,402,405,406,409],{},"Die öffentlichen Mutations (",[21,382,383],{},"activate",[21,385,386],{},"pause",[21,388,389],{},"resume",[21,391,392],{},"automations\u002Fautomations.ts",") sind dünne Auth-Hüllen: Sie prüfen die Berechtigung ",[21,395,396],{},"automations:manage"," und dispatchen an ",[21,399,400],{},"internal.automations.lifecycle.transition",", das ein typisiertes Ergebnis zurückgibt, statt eine Exception zu werfen. Das ",[21,403,404],{},"reasonToMessage"," der Hülle bildet den typisierten ",[21,407,408],{},"reason"," auf einen für Nutzer sichtbaren Text ab.",[117,411,413],{"id":412},"ergebnisse-von-übergängen","Ergebnisse von Übergängen",[179,415,416,435],{},[182,417,418],{},[185,419,420,429,432],{},[188,421,422,424,425,428],{},[21,423,408],{}," (bei ",[21,426,427],{},"ok: false",")",[188,430,431],{},"Bedeutung",[188,433,434],{},"Meldung der Hülle",[195,436,437,450,464,480],{},[185,438,439,444,447],{},[200,440,441],{},[21,442,443],{},"automation_not_found",[200,445,446],{},"Zeile fehlt",[200,448,449],{},"„Automation not found“",[185,451,452,456,461],{},[200,453,454],{},[21,455,368],{},[200,457,458,459],{},"Kante nicht in ",[21,460,372],{},[200,462,463],{},"„Automation is not in a state that allows this transition“",[185,465,466,471,477],{},[200,467,468],{},[21,469,470],{},"no_steps",[200,472,473,476],{},[21,474,475],{},"→ active"," ohne Schritte",[200,478,479],{},"„Automation must have at least one step to be activated“",[185,481,482,487,492],{},[200,483,484],{},[21,485,486],{},"invalid_trigger_config",[200,488,489,491],{},[21,490,475],{}," mit fehlender Trigger-Konfiguration",[200,493,494],{},"„Automation trigger is missing required configuration“",[11,496,497,498,501,502,505,506,509,510,513],{},"Ein doppelter Versuch in denselben Zustand (",[21,499,500],{},"from === to",") ist idempotent: Er liefert ",[21,503,504],{},"{ ok: true, applied: 'recorded' }",", schreibt eine Audit-Log-Zeile mit der Markierung ",[21,507,508],{},"no_op: true"," und emittiert ",[49,511,512],{},"kein"," PostHog-Event und keinen Patch.",[117,515,517],{"id":516},"effekte-pro-übergang","Effekte pro Übergang",[11,519,520,521,523,524,527],{},"Jeder echte Übergang schreibt eine Audit-Log-Zeile ",[49,522,130],{}," ein PostHog-",[21,525,526],{},"track_event","; Selbstschleifen schreiben nur die Audit-Zeile.",[179,529,530,543],{},[182,531,532],{},[185,533,534,537,540],{},[188,535,536],{},"Übergang",[188,538,539],{},"Audit-Aktion",[188,541,542],{},"PostHog-Event",[195,544,545,562,579,596],{},[185,546,547,552,557],{},[200,548,549],{},[21,550,551],{},"draft → active",[200,553,554],{},[21,555,556],{},"automation.activated",[200,558,559],{},[21,560,561],{},"automation_activated",[185,563,564,569,574],{},[200,565,566],{},[21,567,568],{},"active → paused",[200,570,571],{},[21,572,573],{},"automation.paused",[200,575,576],{},[21,577,578],{},"automation_paused",[185,580,581,586,591],{},[200,582,583],{},[21,584,585],{},"paused → active",[200,587,588],{},[21,589,590],{},"automation.resumed",[200,592,593],{},[21,594,595],{},"automation_resumed",[185,597,598,603,608],{},[200,599,600],{},[21,601,602],{},"paused → draft",[200,604,605],{},[21,606,607],{},"automation.reverted_to_draft",[200,609,610],{},[21,611,612],{},"automation_reverted_to_draft",[11,614,615,617,618,620,621,623,624,627,628,630],{},[21,616,475],{}," prüft vor dem Patchen zwei Vorbedingungen: mindestens eine Zeile in ",[21,619,46],{}," und eine gültige Trigger-Konfiguration für den Trigger-Typ. Beide laufen sowohl bei ",[21,622,551],{}," ",[49,625,626],{},"als auch"," bei ",[21,629,585],{},", sodass ein Resume nicht stillschweigend in einen kaputten aktiven Zustand zurückführen kann.",[11,632,633,634,341,637,341,640,643,644,647,648,24],{},"Die Lifetime-Statistikzähler (",[21,635,636],{},"statsEntered",[21,638,639],{},"statsActive",[21,641,642],{},"statsCompleted",") werden vom Lifecycle ",[49,645,646],{},"nicht"," angefasst — sie gehören dem Trigger-Fanout sowie dem Abschluss bzw. Abbruch von Durchläufen in ",[21,649,650],{},"stepExecutorQueries.ts",[66,652,51],{"id":653},"trigger-fanout",[11,655,656,657,660],{},"Trigger sind über Module pro Art verdrahtet, die in ",[21,658,659],{},"apps\u002Fapi\u002Fconvex\u002Fautomations\u002Ftriggers.ts"," registriert werden. Vier Trigger-Arten sind enthalten:",[179,662,663,677],{},[182,664,665],{},[185,666,667,670,673],{},[188,668,669],{},"Trigger-Art",[188,671,672],{},"Löst aus, wenn",[188,674,675],{},[21,676,38],{},[195,678,679,692,707,722],{},[185,680,681,686,689],{},[200,682,683],{},[21,684,685],{},"contact_created",[200,687,688],{},"Ein Kontakt angelegt wird",[200,690,691],{},"keine (trifft immer zu)",[185,693,694,699,702],{},[200,695,696],{},[21,697,698],{},"contact_updated",[200,700,701],{},"Eine beobachtete Eigenschaft sich ändert",[200,703,704],{},[21,705,706],{},"{ propertyKey }",[185,708,709,714,717],{},[200,710,711],{},[21,712,713],{},"event_received",[200,715,716],{},"Ein benanntes Event für einen Kontakt gesendet wird",[200,718,719],{},[21,720,721],{},"{ eventName }",[185,723,724,729,732],{},[200,725,726],{},[21,727,728],{},"topic_subscribed",[200,730,731],{},"Ein Kontakt ein Thema abonniert",[200,733,734],{},[21,735,736],{},"{ topicId }",[11,738,739,740,743],{},"Der gemeinsame Walker ",[21,741,742],{},"fireTrigger"," führt die Fanout-Pipeline für eine Art aus:",[745,746,747,751,765,769,785,789,804,808],"steps",{},[117,748,750],{"id":749},"passende-automationen-abrufen","Passende Automationen abrufen",[11,752,753,754,757,758,341,761,764],{},"Aktive Automationen mit diesem Trigger über den Index ",[21,755,756],{},"by_status_trigger"," abfragen (",[21,759,760],{},"status = 'active'",[21,762,763],{},"triggerType = kind",").",[117,766,768],{"id":767},"jede-einzeln-auswerten","Jede einzeln auswerten",[11,770,771,772,774,775,777,778,781,782,784],{},"Pro Automation: ",[21,773,38],{}," über das ",[21,776,98],{}," des Moduls verengen, dann ",[21,779,780],{},"module.matches(input, config)"," auswerten. ",[21,783,685],{}," trifft immer zu; die anderen drei vergleichen die ausgelöste Eingabe mit der gespeicherten Konfiguration.",[117,786,788],{"id":787},"duplikate-und-leerläufe-überspringen","Duplikate und Leerläufe überspringen",[11,790,791,792,795,796,799,800,803],{},"Die Automation überspringen, wenn der Kontakt für sie bereits einen ",[21,793,794],{},"running","-Durchlauf hat (der Index ",[21,797,798],{},"by_automation_and_contact",", gefiltert auf ",[21,801,802],{},"status: 'running'","), und überspringen, wenn die Automation keine Schritte hat.",[117,805,807],{"id":806},"einfügen-und-einplanen","Einfügen und einplanen",[11,809,810,811,813,814,345,817,819,820,822,823,825,826,829,830,833,834,837],{},"Eine Zeile in ",[21,812,55],{}," mit ",[21,815,816],{},"currentStepIndex: 0",[21,818,802],{}," einfügen, den ",[21,821,636],{},"-Shard erhöhen (",[21,824,639],{}," wird vom Rollup als ",[21,827,828],{},"entered − completed − cancelled"," abgeleitet und nicht hochgezählt), etwaige vom Modul erzeugte ",[21,831,832],{},"triggerData"," anhängen und ",[21,835,836],{},"internal.automations.stepWalker.startAutomationRun"," einplanen.",[11,839,840,841,341,844,341,847,850,851,854,855,858,859,861,862,864],{},"Die Wrapper-Mutations für Kontakte und Themen (",[21,842,843],{},"fireContactCreatedTrigger",[21,845,846],{},"fireContactUpdatedTrigger",[21,848,849],{},"fireTopicSubscribedTrigger",") sind intern und werden aus den jeweils zuständigen Schreibpfaden aufgerufen. Events kommen über ",[21,852,853],{},"sendEvent"," herein, dessen einziger öffentlicher Einstiegspunkt die per API-Key authentifizierte Route ",[21,856,857],{},"POST \u002Fapi\u002Fv1\u002Fevents"," ist; sie ruft den gemeinsamen ",[21,860,742],{},"-Kern direkt auf und liegt bewusst ",[49,863,646],{}," auf der öffentlichen Convex-Client-API, damit ein anonymer Aufrufer keine Events fabrizieren kann.",[310,866,869],{"title":867,"type":868},"Start eines Durchlaufs vs. Beanspruchen eines Schritts","info",[11,870,871,874,875,877,878,881,882,885],{},[21,872,873],{},"startAutomationRun"," plant Schritt 0 ein (unter Berücksichtigung seiner Eintrittsverzögerung) und legt die erste Zeile in ",[21,876,63],{}," an. Von da an ruft jeder abgeschlossene Schritt ",[21,879,880],{},"advanceToStep"," auf, das den nächsten Schritt einplant oder — wenn der Index über den letzten Schritt hinausläuft — den Durchlauf als ",[21,883,884],{},"completed"," markiert.",[66,887,889],{"id":888},"schritt-typen","Schritt-Typen",[11,891,892,893,896,897,900,901,903,904,906,907,910,911,914],{},"Drei Schritt-Arten sind enthalten, registriert in ",[21,894,895],{},"apps\u002Fapi\u002Fconvex\u002Fautomations\u002Fsteps.ts"," und jeweils unter ",[21,898,899],{},"steps\u002F\u003Ckind>\u002F"," liegend. Der Walker dispatcht einheitlich an sie; nur ihr ",[21,902,98],{},", ihr ",[21,905,102],{}," und das optionale ",[21,908,909],{},"entryDelay","\u002F",[21,912,913],{},"enrichForQuery"," unterscheiden sich.",[117,916,918],{"id":917},"e-mail","E-Mail",[11,920,921,924,925,928,929,932,933,936,937,52,940,943,944,947,948,950,951,955],{},[21,922,923],{},"steps\u002Femail\u002Findex.ts",". Die Konfiguration ist ",[21,926,927],{},"{ emailTemplateId, subjectOverride? }",". Bei der Ausführung lädt der Schritt die Vorlage, ermittelt den Standardabsender der Organisation, setzt Betreff und Inhalt für die Sendeart ",[21,930,931],{},"automation"," zusammen (kein Tracking, keine Fußzeile) und ",[49,934,935],{},"stellt"," über ",[21,938,939],{},"internal.delivery.enqueue.enqueueNonCampaignSend",[21,941,942],{},"transactionalSends"," in den transaktionalen Pool ein (wobei die ID des eingereihten Sendevorgangs als ",[21,945,946],{},"emailSendId"," zurückgegeben wird). Die Provider-Auflösung, der Versand und der Übergang im Send-Lifecycle geschehen allesamt asynchron auf dem Worker — ",[21,949,884],{}," bedeutet hier, dass der Sendevorgang ",[952,953,954],"em",{},"eingereiht"," wurde, nicht dass er zugestellt ist.",[310,957,959],{"title":958,"type":868},"E-Mail-Schritte brauchen eine E-Mail-Adresse",[11,960,961,962,965],{},"Kontakte, die über Telefon-, SMS-, WhatsApp- oder generische Kanäle hereinkamen, haben keine E-Mail-Adresse. Der E-Mail-Schritt scheitert für diese Kontakte explizit mit ",[21,963,964],{},"Contact has no email address",", sodass im Protokoll des Durchlaufs festgehalten wird, warum der Versand übersprungen wurde.",[11,967,968,969,971,972,975,976,978,979,982],{},"Der E-Mail-Schritt ist der einzige Schritt, der eine ",[21,970,946],{}," zurückgibt (die ID der eingereihten Send-Zeile, nicht die Message-ID eines Providers); der Walker speichert sie über ",[21,973,974],{},"markStepCompleted"," am Step Run. Der Schritt implementiert außerdem ",[21,977,913],{},", damit ",[21,980,981],{},"getWithRelations"," die referenzierte Vorlage für den Editor mitladen kann.",[117,984,986],{"id":985},"verzögerung","Verzögerung",[11,988,989,924,992,995,996,999,1000,1003,1004,1006,1007,1010,1011,1014],{},[21,990,991],{},"steps\u002Fdelay\u002Findex.ts",[21,993,994],{},"{ duration, unit }",", wobei ",[21,997,998],{},"unit"," einen der Werte ",[21,1001,1002],{},"minutes | hours | days | weeks"," annimmt. Der Verzögerungsschritt ist die einzige Art, die ",[21,1005,909],{}," implementiert: ",[21,1008,1009],{},"computeEntryDelay"," rechnet die Konfiguration über ",[21,1012,1013],{},"delayConfigToMs"," in Millisekunden um, und der Walker nutzt diesen Wert als Vorlaufverzögerung beim Einplanen des Schritts.",[11,1016,1017,1018,1021,1022,1024,1025,1027,1028,1030,1031,1034,1035,1037],{},"Die Verzögerung findet also ",[49,1019,1020],{},"vor"," dem Dispatch des Schritts statt, nicht während der Ausführung — wenn das ",[21,1023,102],{}," des Verzögerungsschritts läuft, ist die Wartezeit bereits verstrichen, sodass ",[21,1026,102],{}," eine No-Op ist, die sofort ",[21,1029,884],{}," zurückgibt. Das ",[21,1032,1033],{},"delayUntil"," des ausstehenden Schritts wird auf der Zeile in ",[21,1036,63],{}," festgehalten, damit der Cron ihn wiederherstellen kann.",[117,1039,1041],{"id":1040},"bedingung","Bedingung",[11,1043,1044,924,1047,1050,1051,1054,1055,1058],{},[21,1045,1046],{},"steps\u002Fcondition\u002Findex.ts",[21,1048,1049],{},"{ condition, yesBranchStepIndex, noBranchStepIndex }",". Bei der Ausführung serialisiert der Schritt die kanonische ",[21,1052,1053],{},"Condition"," und wertet sie über ",[21,1056,1057],{},"internal.automations.steps.condition.queries.evaluateConditionForContact"," gegen den Kontakt aus (eine Action kann die Datenbank nicht direkt lesen, deshalb läuft die Auswertung in einer Query).",[11,1060,1061,1062,1065,1066,1069,1070,1073,1074,1077,1078,1081,1082,1085],{},"Ein wahres Ergebnis verzweigt zu ",[21,1063,1064],{},"yesBranchStepIndex",", ein falsches zu ",[21,1067,1068],{},"noBranchStepIndex",". Der gewählte Index wird als ",[21,1071,1072],{},"nextStepIndex"," des Ergebnisses zurückgegeben und überschreibt den standardmäßigen sequentiellen ",[21,1075,1076],{},"currentStepIndex + 1",". Ein Verzweigungsziel von ",[21,1079,1080],{},"null"," bedeutet „zum nächsten sequentiellen Schritt durchfallen“. Weil eine Verzweigung rückwärts zeigen kann, ist der Bedingungsschritt genau der Grund, warum der Walker die Schleifen-Obergrenze ",[21,1083,1084],{},"MAX_STEPS_PER_RUN"," erzwingt.",[66,1087,1089],{"id":1088},"crons-und-resilienz","Crons und Resilienz",[11,1091,1092,1093,1095,1096,1099],{},"Der Walker plant den jeweils nächsten Schritt direkt über ",[21,1094,126],{}," ein, sodass im Normalbetrieb kein Cron nötig ist, um einen Durchlauf voranzubringen. Der einzige Sicherheitsnetz-Cron liegt in ",[21,1097,1098],{},"apps\u002Fapi\u002Fconvex\u002Fcrons.ts",":",[351,1101,1105],{"className":1102,"code":1103,"language":1104,"meta":359,"style":359},"language-ts shiki shiki-themes github-light github-dark-dimmed","crons.interval(\n  'process pending delays',\n  { minutes: 5 },\n  internal.automations.stepWalker.processPendingDelays,\n);\n","ts",[21,1106,1107,1123,1133,1146,1152],{"__ignoreMap":359},[1108,1109,1112,1116,1120],"span",{"class":1110,"line":1111},"line",1,[1108,1113,1115],{"class":1114},"sYgZi","crons.",[1108,1117,1119],{"class":1118},"sPO5f","interval",[1108,1121,1122],{"class":1114},"(\n",[1108,1124,1126,1130],{"class":1110,"line":1125},2,[1108,1127,1129],{"class":1128},"s-HuK","  'process pending delays'",[1108,1131,1132],{"class":1114},",\n",[1108,1134,1136,1139,1143],{"class":1110,"line":1135},3,[1108,1137,1138],{"class":1114},"  { minutes: ",[1108,1140,1142],{"class":1141},"sviXB","5",[1108,1144,1145],{"class":1114}," },\n",[1108,1147,1149],{"class":1110,"line":1148},4,[1108,1150,1151],{"class":1114},"  internal.automations.stepWalker.processPendingDelays,\n",[1108,1153,1155],{"class":1110,"line":1154},5,[1108,1156,1157],{"class":1114},");\n",[11,1159,1160,1163,1164,1166,1167,1170,1171,1174,1175,1177,1178,1180,1181,1184,1185,1187],{},[21,1161,1162],{},"processPendingDelays"," fragt ",[21,1165,63],{}," über den Index ",[21,1168,1169],{},"by_status_and_delay_until"," nach Zeilen ab, die noch ",[21,1172,1173],{},"pending"," sind und ein in der Vergangenheit liegendes ",[21,1176,1033],{}," haben, und dispatcht für jede erneut ",[21,1179,86],{},". Das fängt Verzögerungsschritte auf, deren ursprüngliches ",[21,1182,1183],{},"runAfter"," verloren ging — etwa weil das Deployment offline war, als die Verzögerung ablief. Weil ein erneuter Dispatch und die ursprüngliche Einplanung beide feuern könnten, ist genau das das Duplikat, gegen das der atomare Step Claim schützt: Nur einer von beiden beansprucht den Übergang ",[21,1186,153],{},"; der Verlierer bricht ab.",[66,1189,1191],{"id":1190},"statusräume-von-durchläufen-und-schritt-durchläufen","Statusräume von Durchläufen und Schritt-Durchläufen",[11,1193,1194,345,1196,1198],{},[21,1195,55],{},[21,1197,63],{}," haben ihre eigenen Status-Enums, getrennt vom Lifecycle der übergeordneten Automation:",[179,1200,1201,1211],{},[182,1202,1203],{},[185,1204,1205,1208],{},[188,1206,1207],{},"Tabelle",[188,1209,1210],{},"Status",[195,1212,1213,1227],{},[185,1214,1215,1219],{},[200,1216,1217],{},[21,1218,55],{},[200,1220,1221,341,1223,341,1225],{},[21,1222,794],{},[21,1224,884],{},[21,1226,235],{},[185,1228,1229,1233],{},[200,1230,1231],{},[21,1232,63],{},[200,1234,1235,341,1237,341,1239,341,1241,341,1243],{},[21,1236,1173],{},[21,1238,157],{},[21,1240,884],{},[21,1242,176],{},[21,1244,1245],{},"skipped",[11,1247,1248,1249,1251,1252,1254,1255,1258,1259,1261,1262,1258,1264,1267,1268,1270,1271,1273],{},"Das sind nebengeordnete Zustandsräume — ein laufender ",[21,1250,794],{},"-Durchlauf ist kein Zustand der Zustandsmaschine ",[21,1253,326],{}," der übergeordneten Automation. Der Abschluss eines Durchlaufs (",[21,1256,1257],{},"completeAutomationRun",") erhöht den ",[21,1260,642],{},"-Shard; ein Abbruch (",[21,1263,260],{},[21,1265,1266],{},"statsCancelled","-Shard. ",[21,1269,639],{}," wird nie direkt geschrieben — es wird vom Rollup als ",[21,1272,828],{}," abgeleitet.",[11,1275,1276,1277,1279,1280,1283,1284,1287,1288,1290,1291,1293,1294,1297,1298,1301,1302,1305,1306,1309],{},"Ein ",[21,1278,1245],{},"-Step-Run ist terminal und wird von ",[21,1281,1282],{},"markStepsSkipped"," geschrieben: Wenn ein Bedingungsschritt ",[49,1285,1286],{},"vorwärts"," verzweigt (sein Ziel liegt jenseits des nächsten sequentiellen Index), schreibt der Walker für jeden übersprungenen Schritt einen ",[21,1289,1245],{},"-Step-Run, bevor er das Ziel einplant. Das ist der einzige Erzeuger von ",[21,1292,1245],{}," — eine Rückwärtsverzweigung oder ein normales Vorrücken um ",[21,1295,1296],{},"+1"," überspringt nichts. Der Funnel (",[21,1299,1300],{},"getStepAnalytics"," \u002F ",[21,1303,1304],{},"getAutomationStats",") summiert den Zähler ",[21,1307,1308],{},"statSkipped"," jedes Schritts, damit die Analytics die von einem Kontakt übersprungenen Schritte abbilden.",[66,1311,1313],{"id":1312},"verwandte-themen","Verwandte Themen",[1315,1316],"link-card",{"description":1317,"title":18,"to":17},"Der Produktleitfaden zum Bauen trigger-basierter Automations-Workflows.",[1315,1319],{"description":1320,"title":1321,"to":1322},"Funktionstypen, die secure-by-default Builder und das Domain-Ordner-Layout.","Convex-Backend","\u002Fdeveloper\u002Fconvex",[1315,1324],{"description":1325,"title":1326,"to":1327},"Die API-Key-Route, die event_received-Trigger für einen Kontakt auslöst.","Events-API","\u002Fapi\u002Fevents",[1329,1330,1331],"style",{},"html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":359,"searchDepth":1125,"depth":1125,"links":1333},[1334,1340,1344,1350,1355,1356,1357],{"id":68,"depth":1125,"text":69,"children":1335},[1336,1337,1338,1339],{"id":119,"depth":1135,"text":120},{"id":169,"depth":1135,"text":170},{"id":272,"depth":1135,"text":273},{"id":295,"depth":1135,"text":296},{"id":319,"depth":1125,"text":320,"children":1341},[1342,1343],{"id":412,"depth":1135,"text":413},{"id":516,"depth":1135,"text":517},{"id":653,"depth":1125,"text":51,"children":1345},[1346,1347,1348,1349],{"id":749,"depth":1135,"text":750},{"id":767,"depth":1135,"text":768},{"id":787,"depth":1135,"text":788},{"id":806,"depth":1135,"text":807},{"id":888,"depth":1125,"text":889,"children":1351},[1352,1353,1354],{"id":917,"depth":1135,"text":918},{"id":985,"depth":1135,"text":986},{"id":1040,"depth":1135,"text":1041},{"id":1088,"depth":1125,"text":1089},{"id":1190,"depth":1125,"text":1191},{"id":1312,"depth":1125,"text":1313},"Wie die Ausführungs-Engine für Automationen arbeitet: der Step Walker, die Lifecycle-Zustandsmaschine, der Trigger-Fanout, die drei Schritt-Typen und der Resilienz-Cron.","md",{},true,"\u002Fdeveloper\u002Fautomation-internals",{"title":6,"description":1358},"3.developer\u002F18.automation-internals","3AcN0rtOsOhNSqknoi4TEZ-QMVceDzVIk3WBIB2kAq8",[1367,1371],{"title":1368,"path":1369,"stem":1370,"children":-1},"Zielgruppen-Interna","\u002Fdeveloper\u002Faudience-internals","3.developer\u002F17.audience-internals",{"title":1372,"path":1373,"stem":1374,"children":-1},"Zustellbarkeits-Infrastruktur","\u002Fdeveloper\u002Fdeliverability-infrastructure","3.developer\u002F19.deliverability-infrastructure",1786915108965]