Trattar & utlösare
En tratt är en graf. Ett körningstillfälle är en rad. Ett väntande är en tid.
Det är hela modellen, och alla svåra egenskaper den här produkten har kommer ur de tre meningarna. Att redigera en live-tratt är säkert tack vare dem. Ett nytt försök kan inte skicka dubbelt tack vare dem. En person som avanmäler sig på tisdagen hör inget på torsdagen tack vare dem.
Vad som faktiskt händer när den körs
När du publicerar en tratt skrivs en oföränderlig version. Någon går in, och en rad dyker upp: en körning, för den här kontakten, fäst vid den versionen, med aktuell nod, en väckningstid och sitt eget sammanhang.
En worker leasar de körningar vars väckningstid passerats och claimar dem med SKIP LOCKED så att två workers inte kan ta samma. Den kör noder tills den når ett väntande eller en terminal, skriver sedan nästa väckningstid och släpper taget.
En nod är en ren funktion av körningen, kontakten och noden — och dess effekter committas i samma transaktion som körningens eget framsteg. Den enskilda egenskapen är det som gör ett nytt försök säkert. En worker som dör under halvsekunden mellan "skicka" och "avancera" lämnar antingen båda eller inget, aldrig ett.
Eftersom appen levererar tiden i stället för databasen är en rad som skrivs nu förfallen nu. En schemakolumn som defaultar till databasens klocka är inte förfallen i ögonblicket den skrivs och missar den tick som skapade den — en bugg som ser ut som ingenting alls tills ett meddelande är en timme sent.
- 1Leasa
wake_at <= now(),FOR UPDATE SKIP LOCKED. En kö utan lease skickar dubbelt som default. - 2KörNoder i ordning tills ett väntande eller en terminal. Varje nod skriver ett steg: vad den beslutade, vad den utlöste.
- 3BekräftaEffekter och framsteg, en transaktion. Det är hela tricket.
- 4VilaNästa
wake_atskrivs. Inga nedströmsrader finns ännu, och ingen behövs.
En körning per kontakt per version
Fjorton utlösare
Vad som kan starta en körning
Några av dessa är saker du gör. De flesta är saker din kund gör, eller inte gör — och avsaknaden av en handling är lika bra en utlösare som handlingen själv.
De anländer
form_submit, list_join, contact_created, tag_added och segment_enter. Ett segment är ett levande predikat, så "går in i segmentet" täcker varje "har blivit" du kan uttrycka — tre ordrar, nittio dagar tyst, spenderar över en tröskel.
De gör något
page_visit från webbplats-snippeten, message_opened, link_clicked, booking_made och payment_made. Att ett meddelande öppnas är i sig en händelse på kontakten, så en tratt kan vänta på en reaktion på det senaste den skickade.
Tiden, eller du
date_field med en offset — en födelsedag, en förnyelse, "ett år sedan", sju dagar före eller tre dagar efter. schedule för den återkommande. manual för utskicket du skickar för att du själv bestämde det.
Din egen applikation
api_event är en anpassad händelse du postar från din egen kod med din egen token. Allt din produkt vet blir en utlösare: aktiverad, bjöd in en kollega, nådde gränsen, avbröt, exporterade. Det är den som gör ett utskicksverktyg till en del av din produkt.
Återinträde, beslutat en gång
En tratt är antingen återinträdande eller inte, och databasen tvingar fram det: en körning per kontakt per tratt om du inte sagt annat. Samma utlösare som fyrar två gånger i samma sekund kan inte råka ge upphov till två körningar.
Inträdesvillkor
En utlösare fyrar och inträdesspecen avgör om just den här kontakten är behörig — ett segmentpredikat framför dörren. Billigare än en villkorsnod på första steget, och det håller grafen fokuserad på vad som händer snarare än på vem som släpps in.

Fjorton saker kan starta den. Sexton saker kan hända sedan.
Sexton åtgärder och sju flödesnoder
Vad en tratt kan göra, och hur den bestämmer
Skicka något
send_email, send_sms, send_push, send_letter — fyra kanaler, en graf — plus notify_team när personen som behöver veta jobbar för dig.
Ändra kontakten
add_tag, remove_tag, add_to_list, remove_from_list, set_field. Att märka upp någon på vägen är det som gör nästa tratts segment enkelt.
Nå utanför
http_request till vad du vill, create_payment mot ditt eget anslutna konto, och book_slot mot en bokningsresurs. Betalaren är fortfarande din; ingen blir vår kund.
Flytta mellan trakter
enter_funnel och exit_funnel. Bygg en välkomstsekvens och anropa den från fyra ställen, i stället för att underhålla fyra kopior som driver isär fram till mars.
Genereras vid utskickstillfället
ai_generate fyller merge-variabler per kontakt, mäts mot din kvot och cachas per kreativ-hash och kontaktattribut-hash — annars skulle du betala per mottagare vid varje enskilt utskick.
Vänta, gren, dela, sammanfoga
wait tar en varaktighet, ett datumtid, en veckodag och tidpunkt, eller ett datfält med offset. condition är ett segmentpredikat med ett ja och ett nej. split är A/B/n med vikter. join för grenarna tillbaka samman.
goal — den som spelar roll
Ett mål avslutar körningen för alla som når det. De bokade, de köpte, de svarade: resten av sekvensen avbryts för just dem. Ingen får "vi saknar dig" dagen efter att de kom tillbaka, och det är den noden som förhindrar det.
throttle och quiet_hours
Throttle takter en utskicksbrytning så att en brevlåda med låg hastighetsgräns inte översvämmas till spärrning. Quiet hours håller kvar ett meddelande till en civiliserad tidpunkt — tillämpas på meddelandenivån, inte kopieras in i varje gren av grafen.
Kontrolleras innan den får köras
Valideraren vägrar samma saker för dig som för AI:n
En graf kontrolleras innan den kan publiceras, och kontrollen är inte bara rådgivande. Det är samma kodväg oavsett om en person ritade tratten eller en modell genererade den, vilket är den enda anledningen till att genererade tratter är förtroendevärda överhuvudtaget.
Exakt en ingång
Två ingångsnoder är en tvetydighet som ingen lägger märke till förrän fel hälft av en lista får fel sekvens.
Alla noder nåbara
En föräldralös gren är antingen ett misstag eller ett meddelande du tror går ut men som inte gör det. Båda är värda att bli informerad om.
En cykel måste innehålla en väntan
En slinga utan väntan är en oändlig slinga med ett utskick i mitten. Den regeln är skillnaden mellan en trätt och en incident.
Varje utskick har sitt kreativa
En send_email-nod utan kreativ och ämnesrad kan inte publiceras. Meddelandet med rätt ämnesrad och tom kropp är ett verkligt felläge; det avvisas här.
Alla variabler är kända
Merge-variabler valideras vid publicering. {{frist_name}} är ett stavfel du ser medan du redigerar, inte "Hej ," som anländer i fyrtio tusen inkorgar.
Simulera den sedan
Kör en riktig kontakt torrt genom hela grafen och läs tillbaka den väg som togs och varje meddelande som skulle skickas, med dess dag. Inget lämnar huset.
De besvärliga fallen
Sju saker som krossar en naiv motor
Var och en av dessa är ett test med en deterministisk klocka bakom sig, för var och en krossar en implementation som ser helt korrekt ut tills den möter verkligheten.
- 1En trätt som loopar in i sig självLegitimt, vanligt och helt okej — förutsatt att cykeln innehåller en väntan, vilket valideraren insisterar på.
- 2Någon avregistrerar sig mitt iSpärrning kontrolleras vid utskick, inte vid köläggning. Att kontrollera vid köläggning postar till de personer som lämnade under väntan.
- 3Trätten redigeras med tiotusen personer inne i denDe fortsätter gå den version de gick in på, till slutet. Nya deltagare går den nya.
- 4Samma trigger utlöses två gånger på en sekundEn körning, eftersom unikhet är ett index snarare än en kontroll följt av en insättning.
- 5En kontakt raderas mitt i körningenKörningen avslutas. En radering som lämnar en sekvens fortfarande adresserad till någon är ingen radering.
- 6En väntan spänner över en sommartidsändring"Tre dagar klockan nio på morgonen" betyder nio på morgonen på båda sidor om tidsomställningen.
- 7En worker dödas mellan send och advanceEffekter och advance är en transaktion. Antingen hände båda, eller inget av dem.
Deterministisk klockaVarje test bryter en rimlig implementation
Kampanjer är den andra halvan
En tratt reagerar. En kampanj är engångssändningen: en målgrupp, ett schema, en strypning och en utskick. Det är en annan sorts sak och hålls medvetet åtskild i stället för att låtsas vara en tratt med en nod.
Två regler gör det säkert. En målgrupp materialiseras en gång och vägrar köras två gånger — att slå upp den igen skulle tyst lägga till alla som anslutit sedan utskicket började, och dedupliceringsindexet skulle dölja det genom att hindra dubbletterna det skapade. Och räknare räknas om från leveranshändelser, aldrig ökas på plats, så en webhook som körs om kan inte blåsa upp en rapport.
Vad du ska använda: händer det på grund av något personen gjorde är det en tratt. Händer det på grund av något du bestämde dig för en tisdag är det en kampanj. Förvånansvärt mycket marknadsautomatisering går fel genom att tvinga det senare in i den förras form.