Automation
FunnelFizz automations are workflows — DAGs of Send / Wait / Split steps — that fire when a specific event happens (a profile enters a stage, a trial starts, a subscription activates).
For conceptual overview, see Concepts → Automations. This page is the feature reference.
The canvas editor
Open any stage → Automations tab → Create automation.
You get a ReactFlow canvas with:
- Step palette (left) — drag any step type onto the canvas.
- Canvas (center) — arrange steps and connect them with edges.
- Settings (right, when a step is selected) — configure that specific step.
- Toolbar — save, validate, activate/deactivate.
Each step is a card with a handle on the left (input) and one or two handles on the right (outputs). Drag from an output handle to another step's input handle to connect them. Splits have two output handles; everything else has one.
Triggers
Pick the trigger when you create the automation:
| Trigger | When it fires |
|---|---|
subscription_trial | Stripe subscription enters status = "trialing" |
subscription_paid | Stripe subscription enters status = "active" |
stage_enter (legacy) | Profile enters any stage |
Only one trigger per automation. If you want the same sequence to fire on both trial and paid events, create two automations.
Steps
SEND_EMAIL
Composes and queues an email send.
Config:
- Subject — string (supports
{{variables}}). - Sender — pick from your verified senders.
- Blocks — the email body (same MJML block system as standalone campaigns).
What happens: the step writes an EmailSend row with status = "QUEUED". The email-queue
cron picks it up within a minute and actually sends it.
The step completes immediately after queuing — it doesn't wait for the email to be delivered.
If you want the workflow to wait for an open or click, use a WAIT_UNTIL step next.
WAIT
Pauses for a fixed duration.
Config:
- Months / Days / Hours / Minutes — pick any combination.
What happens: sets nextStepAt = now + delay. The cron re-claims the run when the delay
elapses.
WAIT_UNTIL
Pauses until an event fires, with optional timeout.
Config:
- Event name —
email_opened,email_clicked,pageview, custom event names. - Timeout (optional) — "give up after X duration."
What happens: sets status to WAITING_FOR_EVENT. When the event fires on this profile, the
run flips back to RUNNING and continues. If the timeout expires first, the run also continues,
but you can branch based on which happened via a CONDITIONAL_SPLIT immediately after.
CONDITIONAL_SPLIT
Branches based on a condition.
Config:
- Condition — examples:
email_opened == truepageview_count >= 3country in ['US', 'CA']profile.plan == 'pro'
- Output A — the "matched" branch.
- Output B — the "didn't match" branch.
What happens: the cron evaluates the condition against the profile's current state, follows the matching edge.
PERCENTAGE_SPLIT
Deterministic A/B split.
Config:
- Percent to A — e.g., 50 (other 50% goes to B).
What happens: hash(runId + stepId) mod 100 < percent determines the branch. Same profile
always takes the same branch, so you can A/B test variants of downstream content.
Saving, validating, activating
- Save — writes the DAG to the database. Automation stays in
DRAFT. - Validate — checks for issues: disconnected nodes, cycles (DAG must be acyclic), missing config on any step.
- Activate — flips status to
ACTIVE. New profiles matching the trigger enter the flow.
Deactivating is symmetric: existing runs keep walking through the graph, but no new profiles enter.
Per-profile runs
When an automation is ACTIVE and the trigger fires for a profile, FunnelFizz creates an
AutomationRun row:
automationId— which automation.profileId— which profile.currentStepId— where they are in the graph.status— RUNNING, WAITING_FOR_EVENT, COMPLETED, CANCELLED.nextStepAt— when to wake them up.
The cron every 5 minutes claims due runs, advances them by one step, updates the row. Each profile is independent of every other.
Cancellation
A run is cancelled when:
- The profile unsubscribes from your emails (workspace-wide).
- You manually pause the automation and explicitly cancel in-flight runs.
- The profile is deleted (rare; happens on data-export/delete requests).
Common patterns
Trial-to-paid conversion
trigger: subscription_trial
│
[SEND_EMAIL: "Welcome, here's the quickstart"]
│
[WAIT: 2 days]
│
[SEND_EMAIL: "Here are 3 things Pro users do"]
│
[WAIT: 4 days]
│
[CONDITIONAL_SPLIT: profile.subscriptionStatus == "active"]
│ │
▼ true (already upgraded) ▼ false
[END] [SEND_EMAIL: "Trial ends tomorrow"]
Churn recovery
trigger: stage_enter CUSTOMER where bucket == "Churned"
│
[WAIT: 7 days]
│
[SEND_EMAIL: "Can you tell us why you left?"]
│
[WAIT_UNTIL: email_clicked (timeout: 14 days)]
│ │
▼ clicked ▼ timed out
[SEND_EMAIL: 20% off comeback] [SEND_EMAIL: 30% off last chance]
A/B test your onboarding
trigger: subscription_trial
│
[PERCENTAGE_SPLIT: 50% to A]
│ │
▼ A ▼ B
[SEND: variant A] [SEND: variant B]
After enough runs, compare the downstream conversion rates in the campaign dashboard.
Plan limits
| FREE | HOBBY | PRO | |
|---|---|---|---|
| Automations per stage | 0 | 1 | 5 |
| Max step depth | — | 3 | 5 |
Depth is the longest path from trigger to any leaf. A 3-step linear sequence with one branch is depth 3, not 4.
What's not here (yet)
- Cross-funnel automations. Automations are scoped to one stage in one funnel. Global automations are on the roadmap.
- Custom HTTP action step. No "make an API call" step today. Use Zapier or n8n for outbound webhooks if you need them.
- Conditional entry filters. Every profile matching the trigger enters. If you need to
filter, put a
CONDITIONAL_SPLITas the first step with an "end" branch for non-matches.
Next: Splitting → — the eight split conditions.