퍼널 및 트리거
퍼널은 그래프예요. 실행은 행이에요. 대기는 시간이에요.
그게 모델의 전부이고, 이 제품이 가진 어려운 특성은 모두 이 세 문장에서 나와요. 살아 있는 퍼널을 편집해도 안전한 것도, 재시도가 이중 발송하지 못하는 것도, 화요일에 수신 거부한 사람이 목요일에 아무것도 받지 못하는 것도 모두 이 덕분이에요.
실행되면 실제로 어떤 일이 일어나나요
퍼널을 게시하면 불변 버전이 기록돼요. 누군가 진입하면 행이 하나 생겨요: 이 연락처를 위한 실행 한 건이, 그 버전에 고정되어, 현재 노드와 기상 시각, 자체 컨텍스트를 담고 있어요.
워커는 기상 시각이 지난 실행들을 임대하며, SKIP LOCKED로 클레임하기 때문에 두 워커가 같은 실행을 가져갈 수 없어요. 대기나 종료에 도달할 때까지 노드를 실행한 뒤, 다음 기상 시각을 기록하고 놓아줘요.
노드는 실행, 연락처, 노드의 순수 함수이고, 그 부수 효과는 실행 자체의 진행과 같은 트랜잭션에서 커밋돼요. 바로 이 한 가지 성질이 재시도를 안전하게 만들어요. "전송"과 "진행" 사이 반 초에 죽은 워커는 둘 다 남기거나 둘 다 남기지 않지, 하나만 남기는 일은 없어요.
그리고 시각을 데이터베이스가 아니라 앱이 제공하기 때문에, 지금 기록된 행은 지금 만기가 돼요. 데이터베이스 시계를 기본값으로 쓰는 스케줄링 컬럼은 기록되는 순간에는 아직 만기가 아니어서, 그 행을 만든 바로 그 틱을 놓쳐요 — 메시지가 한 시간 늦기 전까지는 전혀 보이지 않는 버그예요.
- 1임대
wake_at <= now(),FOR UPDATE SKIP LOCKED. 임대가 없는 큐는 기본적으로 이중 전송을 해요. - 2실행대기나 종료에 도달할 때까지 노드를 순서대로 실행해요. 각 노드는 스텝을 기록해요: 무엇을 결정했고 무엇을 내보냈는지.
- 3커밋부수 효과와 진행은 하나의 트랜잭션으로. 이게 전부예요.
- 4절전다음
wake_at이 기록돼요. 다운스트림 행은 아직 없고, 있을 필요도 없어요.
버전별 연락처당 실행 하나
열네 가지 트리거
실행을 시작할 수 있는 것들
이 중 일부는 여러분이 하는 일이에요. 대부분은 고객이 하거나 하지 않는 일이고 — 행동의 부재도 행동 자체만큼 좋은 트리거예요.
도착할 때
form_submit, list_join, contact_created, tag_added, segment_enter. 세그먼트는 살아있는 술어라서, "세그먼트에 진입함"은 표현할 수 있는 모든 "~가 됨"을 커버해요 — 주문 세 건, 90일간 침묵, 기준액 이상 지출 같은 것들이요.
행동할 때
사이트 스니펫에서 오는 page_visit, message_opened, link_clicked, booking_made, payment_made. 메시지가 열리는 것 자체가 연락처의 이벤트라서, 하나의 퍼널이 마지막으로 보낸 것에 대한 반응을 기다릴 수 있어요.
시간, 또는 여러분
오프셋이 붙은 date_field — 생일, 갱신일, "1년 경과", 7일 전 또는 3일 후. 반복되는 것은 schedule. 여러분이 결정해서 보내는 공지는 manual이에요.
여러분의 애플리케이션
api_event는 여러분의 코드에서 여러분의 토큰으로 게시하는 커스텀 이벤트예요. 제품이 아는 모든 것이 트리거가 돼요: 활성화, 동료 초대, 한도 도달, 취소, 내보내기. 메일링 도구를 여러분 제품의 일부로 바꿔주는 바로 그 기능이에요.
재진입, 한 번 결정
퍼널은 재진입 가능하거나 불가능하거나 둘 중 하나고, 데이터베이스가 이를 강제해요: 달리 지정하지 않으면 퍼널당 연락처당 실행은 하나예요. 같은 초에 트리거가 두 번 발화해도 실수로 두 개의 실행이 만들어질 수 없어요.
진입 조건
트리거가 발화하면 진입 스펙이 이 연락처가 자격이 있는지 결정해요 — 문 앞에 놓인 세그먼트 술어예요. 첫 단계에 조건 노드를 두는 것보다 저렴하고, 그래프가 누구를 들일지가 아니라 무슨 일이 일어나는지에 집중하게 해줘요.

시작시키는 것은 열네 가지. 그 다음에 일어날 수 있는 것은 열여섯 가지예요.
열여섯 가지 액션과 일곱 가지 플로우 노드
퍼널이 할 수 있는 일, 그리고 결정하는 방법
무언가 보내기
send_email, send_sms, send_push, send_letter — 네 개의 채널, 하나의 그래프 — 그리고 알려야 할 사람이 여러분 회사 직원일 때는 notify_team이 있어요.
연락처 변경
add_tag, remove_tag, add_to_list, remove_from_list, set_field. 진행하면서 표시를 남기는 것이 다음 퍼널의 세그먼트를 단순하게 만들어줘요.
외부 연결
원하는 무엇이든 향하는 http_request, 여러분의 연결된 계정으로 향하는 create_payment, 예약 리소스로 향하는 book_slot. 결제자는 여러분의 고객으로 남고, 누구도 우리의 고객이 되지 않아요.
퍼널 간 이동
enter_funnel과 exit_funnel. 환영 시퀀스를 하나 만들어 네 곳에서 호출하세요. 3월쯤에는 서로 어긋나는 네 개의 복사본을 유지하는 것보다 나아요.
전송 시점에 생성
ai_generate는 연락처마다 병합 변수를 채우며, 할당량에 따라 계량되고 크리에이티브와 연락처 속성 해시 기준으로 캐시됩니다. 그렇지 않으면 발송할 때마다 수신자마다 비용을 지불하게 돼요.
대기, 분기, 분할, 합류
wait는 기간, 날짜시간, 요일과 시간, 또는 오프셋이 있는 날짜 필드를 받아요. condition은 예/아니오가 있는 세그먼트 조건이에요. split은 가중치가 있는 A/B/n이에요. join은 분기된 경로를 다시 모아요.
goal — 가장 중요한 노드
목표에 도달한 사람의 실행은 거기서 끝나요. 예약했거나, 구매했거나, 답장했으면 그 사람에게만 시퀀스의 나머지가 취소돼요. 돌아온 다음 날 "보고 싶었어요" 메일을 받는 사람은 없고, 이를 막아주는 게 바로 이 노드예요.
throttle과 quiet_hours
스로틀은 발송 폭주를 속도 조절해 발송 한도가 낮은 메일함이 정지 상태로 몰리지 않게 해요. 방해 금지 시간은 메시지를 상식적인 시간까지 보류해요. 그래프의 모든 분기에 복사되는 게 아니라 메시지 계층에 적용돼요.
실행 전에 검사돼요
검증기는 사람에게도 AI에게도 똑같은 것을 거절해요
그래프는 게시 가능 여부를 검사받으며, 이 검사는 권고가 아니에요. 사람이 퍼널을 그렸든 모델이 생성했든 같은 코드 경로를 거치는데, 생성된 퍼널을 신뢰할 수 있는 유일한 이유가 바로 이거예요.
입력 노드는 정확히 하나
입력 노드가 둘이면, 목록의 엉뚱한 절반에 엉뚱한 시퀀스가 배정되기 전까지는 아무도 모호함을 눈치채지 못해요.
모든 노드가 도달 가능
고아가 된 분기는 실수이거나, 발송된다고 생각했지만 실제로는 발송되지 않는 메시지예요. 둘 다 알려줄 가치가 있어요.
순환에는 반드시 대기가 있어야 해요
대기 없는 루프는 가운데에 발송이 끼어 있는 무한 루프예요. 이 규칙이 퍼널과 사고를 가르는 기준이에요.
모든 발송에 크리에이티브가 있어요
크리에이티브와 제목이 없는 send_email 노드는 게시할 수 없어요. 제목은 맞는데 본문이 빈 메시지는 실제로 일어나는 장애 유형이고, 여기서 거절돼요.
모든 변수가 알려져 있어요
병합 변수는 게시 시점에 검증돼요. {{frist_name}}은 편집 중에 보는 오타일 뿐, 사만 개의 받은 편지함에 도착하는 "안녕하세요 ,"가 아니에요.
그다음 시뮬레이션해 보세요
실제 연락처를 전체 그래프로 드라이런해서, 통과한 경로와 발송될 모든 메시지를 날짜와 함께 되돌려 받아 보세요. 밖으로 나가는 건 아무것도 없어요.
까다로운 사례들
순진한 엔진을 무너뜨리는 일곱 가지
이것들 하나하나가 결정론적 시계를 깔고 만든 테스트예요. 현실을 만나기 전까지 완벽하게 올바르게 보이는 구현을 각각 무너뜨리기 때문이에요.
- 1자기 자신으로 루프되는 퍼널합법적이고 흔하며 전혀 문제없어요. 단, 검증기가 요구하는 대로 순환 안에 대기가 있어야 해요.
- 2진행 중에 누군가 수신 거부해요발송 억제는 큐에 넣을 때가 아니라 발송 시점에 확인돼요. 큐에 넣을 때 확인하면 대기 중에 떠난 사람들에게 메일이 가요.
- 3만 명이 안에 있는 상태에서 퍼널이 편집돼요기존 사람들은 자기가 들어온 버전을 끝까지 걸어가요. 새로 들어오는 사람은 새 버전을 걸어가요.
- 4같은 트리거가 1초 안에 두 번 발화해요실행은 하나예요. 고유성이 확인 후 삽입이 아니라 인덱스로 처리되기 때문이에요.
- 5실행 도중에 연락처가 삭제돼요실행이 끝나요. 누군가에게 여전히 향해 가는 시퀀스를 남겨 둔 삭제는 삭제가 아니에요.
- 6대기가 서머타임 전환을 걸쳐요"아홉 시에 사흘"은 시계 변경 전후 양쪽 모두 아홉 시를 뜻해요.
- 7발송과 진행 사이에서 워커가 죽어요효과와 선지급은 하나의 트랜잭션이에요. 둘 다 일어나거나, 둘 다 일어나지 않아요.
결정론적 시계각 테스트는 그럴듯한 구현 하나를 무너뜨려요
캠페인은 나머지 절반이에요
퍼널은 반응해요. 캠페인은 일회성 브로드캐스트예요: 오디언스, 일정, 스로틀, 그리고 발송. 서로 다른 형태의 존재이기 때문에, 노드 하나짜리 퍼널인 척하는 대신 의도적으로 분리해 두었어요.
두 가지 규칙이 이를 안전하게 만들어요. 오디언스는 한 번만 구체화되며 두 번 실행되는 것을 거부해요 — 다시 확인하면 발송이 시작된 이후 가입한 모든 사람이 조용히 추가될 수 있고, 중복 제거 인덱스는 자신이 만든 중복을 막음으로써 그 사실을 가려 버려요. 그리고 카운터는 제자리에서 증가하는 게 아니라 전송 이벤트로부터 재계산되기 때문에, 재시도된 웹훅이 리포트를 부풀릴 수 없어요.
무엇을 쓸지: 사람이 무언가를 했기 때문에 일어나는 일이라면 퍼널이에요. 화요일에 내가 결정한 것 때문에 일어나는 일이라면 캠페인이에요. 놀랄 만큼 많은 마케팅 자동화가 후자를 전자의 형태에 억지로 맞추려다가 잘못되어요.