Funnels & triggers
Funnel na graph. Run na row. Wait na time.
Na so the whole model be, and every difficult property wey this product get na from those three sentences e dey come out. To edit live funnel dey safe because of dem. Retry no fit double-send because of dem. Person wey unsubscribe on Tuesday no go hear anything on Thursday because of dem.
Wetin really happen when e run
When you publish funnel, e go write immutable version. Person enter, and one row go appear: one run, for this contact, pinned to that version, e dey hold the current node, one wake-up time and im own context.
One worker go lease the runs wey dem wake-up time don pass, e go claim dem with SKIP LOCKED so two workers no fit take the same one. E go execute nodes until e reach wait or terminal, then e go write the next wake-up time and release am.
Node na pure function of the run, the contact and the node — and im effects go commit inside the same transaction as the run own advance. Na that single property dey make retry safe. Worker wey dem kill inside the half-second between "send" and "advance" go leave either both or none, never one.
And because the app dey supply the time instead of the database, row wey dem write now dey due now. Scheduling column wey dey default to database clock no go still due at the instant dem write am, and e go miss the very tick wey create am — bug wey dey look like nothing at all until message late by one hour.
- 1Lease
wake_at <= now(),FOR UPDATE SKIP LOCKED. Queue wey no get lease go double-send by default. - 2ExecuteNodes in order until wait or terminal. Each one go write one step: wetin e decide, wetin e emit.
- 3Confirm workEffects and advance, one transaction. Na the whole trick be this.
- 4SleepNext
wake_atdon write. No downstream row dey exist yet, and none need to.
Wan ron for each kontakt for each vashon
Fourteen triggers
Wetin fit start one run
Some of dis na things wey you do. Most na things wey your customer do, or no do — and the absence of one action na as good trigger as the action itself.
Dem arrive
form_submit, list_join, contact_created, tag_added and segment_enter. Segment na live predicate, so "enters the segment" go cover every "has become" wey you fit express — three orders, ninety days quiet, spend pass one threshold.
Dem do something
page_visit from the site snippet, message_opened, link_clicked, booking_made and payment_made. Message wey dem open na imsef one event on top the contact, so one funnel fit wait for reaction to the last thing wey e send.
Time, or you
date_field with one offset — birthday, renewal, "one year since", seven days before or three days after. schedule for the recurring one. manual for the announcement wey you send because you decide am.
Your own application
api_event na custom event wey you post from your own code with your own token. Anything wey your product know go become trigger: activated, invite colleague, hit the limit, cancel, export. Na this one dey turn mailing tool into part of your product.
Re-entry, dem decide am once
Funnel na either re-entrant or e no be, and the database dey enforce am: one run per contact per funnel unless you talk say make e no be so. The same trigger wey fire two times inside the same second no fit produce two runs by mistake.
Entry conditions
Trigger go fire and the entry spec go decide whether this particular contact dey eligible — na segment predicate for front of the door. E cheap pass condition node for the first step, and e go keep the graph about wetin go happen instead of who dem allow enter.

Fourteen things fit start am. Sixteen things fit happen next.
Sistin akshons and sevin flow nods
Wetin funnel fit do, and how e dey decide
Send something
send_email, send_sms, send_push, send_letter — four channels, one graph — plus notify_team when the person wey need to hear am dey work for you.
Change di contact
add_tag, remove_tag, add_to_list, remove_from_list, set_field. To mark person as you dey go na wetin go make di next funnel segment easy.
Reach outside
http_request to anything wey you like, create_payment against your own connected account, and book_slot against booking resource. Di payer still be your own; nobody come turn our customer.
Waka between funnels
enter_funnel and exit_funnel. Build one welcome sequence and call am from four places, instead of to maintain four copies wey go dey different by March.
Generate am when dem dey send
ai_generate dey fill merge variables per contact, e dey count against your quota and e dey cache am by creative and contact-attribute hash — if no be so, you go pay per recipient for every single send.
Wet, bransh, split, join
wait dey take duration, datetime, weekday and time, or date field with offset. condition na segment predicate with yes and no. split na A/B/n with weights. join dey bring branches back together.
goal — di one wey matter
Goal dey end di run for anybody wey reach am. Dem book, dem buy, dem reply: di rest of di sequence cancel for dem alone. Nobody go receive "we miss you" di day after dem come back, and na dis node dey stop am.
throttle and quiet_hours
Throttle dey pace burst so mailbox wey get low rate cap no go receive too much till dem suspend am. Quiet hours go hold message until better time — e dey apply at message level, e no dey copy into every branch of di graph.
Dem check am before e fit run
Di validator dey refuse di same things for you and for di AI
Dem dey check graph before e fit publish, and di check no be advice. Na di same code path whether person draw di funnel or model generate am, and na di only reason why generated funnels fit trusted at all.
Exactly one entry
Two entry nodes na confusion wey nobody go notice until di wrong half of list receive di wrong sequence.
Every node fit reach
Orphaned branch na either mistake or message wey you think say e dey go but e no dey go. Both ones worth make dem tell you.
Every cycle must get wait
Loop wey no get wait inside na infinite loop with send for di middle. Dis rule na di difference between funnel and incident.
Every send get im creative
send_email node wey no get creative and subject no fit publish. Message wey get di right subject and empty body na real failure mode; dem dey refuse am here.
Every variable dey known
Dem dey validate merge variables at publish. {{frist_name}} na typo wey you go see while you dey edit, no be "Hi ," wey go land inside forty thousand inbox.
Den simulate am
Dry-run real contact through di whole graph and read back di path wey e take and every message wey dem for send, with im day. Nothing comot from di building.
Di awkward cases
Seven things wey dey break naive engine
Every one of dis na test with deterministic clock behind am, because each one dey break implementation wey look correct well well until e meet reality.
- 1Funnel wey dey loop into itselfE legitimate, e common, and e fine — as long as di cycle get wait inside, wey di validator dey insist on.
- 2Person unsubscribe mid-flightDem dey check suppression at send, no be at enqueue. If dem check am at enqueue, e go mail di people wey comot during di wait.
- 3Dem edit di funnel while ten thousand people dey insideDem go continue to walk di version wey dem enter on, till di end. New people go walk di new one.
- 4Di same trigger fire two times inside one secondOne run only, because uniqueness na index, no be check wey dem follow with insert.
- 5Dem delete one contact while di run dey go onDi run go end. Erasure wey leave sequence wey still point to person no be erasure.
- 6One wait cross daylight-saving change"Three days at nine for morning" mean nine for morning for both side of di clock change.
- 7Dem kill one worker between send and advanceEffects and advance na one transaction. Either both happen or none happen.
Clock wey dey deterministicEvery test break one implementation wey sound possible
Campaigns na di other half
Funnel dey react. Campaign na di one-time broadcast: audience, schedule, throttle, and send. Na different kind of thing, and dem keep am separate on purpose instead of to pretend say na funnel wey get one node.
Two rules make am safe. Dem materialise audience once and e no gree run two times — if dem resolve am again, e go quietly add everybody wey join since di send start, and di deduplication index go hide am by blocking di duplicates wey e create. And counters dey recompute from delivery events, dem no dey add am for place, so webhook wey dem retry no fit blow up report.
Which one to use: if e happen because of wetin di person do, na funnel. If e happen because of wetin you decide on Tuesday, na campaign. Plenty marketing automation dey spoil because dem force di second one into di shape of di first one.