Create and edit automated user lifecycle journeys from the CLI.
User Journeys
Automate actions based on user behavior over time. A journey is a graph of nodes (actions) connected by transitions (triggers). When a user fires the entry event, they enter the journey and progress through nodes automatically.
Use cases: send a welcome email after signup, deliver a survey after onboarding, nudge inactive users after 3 days, tag users who complete a milestone, or build multi-step onboarding sequences with timed follow-ups.
How it works: Each node performs an action (send an email, deliver a survey, tag a user). Transitions move users between nodes when an event occurs (--on user.login) or after a delay (--after 2d). Journeys are built incrementally — create a scaffold, then add nodes and transitions one at a time.
Create a journey
Creates a minimal journey with an auto-created entry node (defaults to none action):
ascendkit journey create \
--name "Onboarding Flow" \
--entry-event user.created \
--entry-node welcome_email
Then build it up with add-node and add-transition commands.
Inspect a journey
View full journey
ascendkit journey show jrn_abc123
Shows the full definition (nodes, transitions) and stats (enrolled, active, completed). Transition IDs are shown inline so you can reference them directly in transition update or transition remove:
Transitions:
welcome_email → nudge_1 [timer (2d)] (welcome_email-to-nudge_1)
welcome_email → done [on user.login] (welcome_email-to-done)
Note: Topology warnings are suppressed when the journey is paused, since mid-construction gaps are expected. Run
journey showafter activating to confirm the graph is valid.
List nodes
ascendkit journey node list jrn_abc123
Shows all nodes with their actions, terminal status, and transition counts.
List transitions
ascendkit journey transition list jrn_abc123
ascendkit journey transition list jrn_abc123 --from welcome_email
ascendkit journey transition list jrn_abc123 --to done
Shows transitions in human-readable format ("on user.login -> done", "after 2d -> nudge_1"). Filter by source or destination node.
Add nodes
ascendkit journey node add jrn_abc123 \
--name welcome_email \
--action '{"type": "send_email", "templateSlug": "welcome-email"}'
ascendkit journey node add jrn_abc123 \
--name nudge_1 \
--action '{"type": "send_email", "templateSlug": "login-nudge"}'
# With a custom sender identity
ascendkit journey node add jrn_abc123 \
--name welcome_email \
--action '{"type": "send_email", "templateSlug": "welcome-email"}' \
--email-id [email protected]
ascendkit journey node add jrn_abc123 \
--name done \
--terminal
Use --quiet to suppress topology warnings when building incrementally (e.g. wiring transitions next):
ascendkit journey node add jrn_abc123 --name post_session --action '...' --quiet
Edit nodes
Update a node's action or terminal status:
# Add a survey to an existing email node
ascendkit journey node update jrn_abc123 \
welcome_email \
--action '{"type": "send_email", "templateSlug": "welcome-email", "surveySlug": "onboarding-nps"}'
# Remove the survey (keep the email)
ascendkit journey node update jrn_abc123 \
welcome_email \
--action '{"type": "send_email", "templateSlug": "welcome-email"}'
# Make a node terminal
ascendkit journey node update jrn_abc123 \
done \
--terminal
# Change just the sender identity (keeps existing action)
ascendkit journey node update jrn_abc123 welcome_email \
--email-id [email protected]
Remove nodes
Removes the node and auto-cascades all transitions to/from it:
ascendkit journey node remove jrn_abc123 nudge_1
Add transitions
Use --on for event triggers or --after for timer delays:
# Event trigger: "on user.login, go to done"
ascendkit journey transition add jrn_abc123 \
--from welcome_email \
--to done \
--on user.login
# Timer trigger: "after 2 days, go to nudge_1"
ascendkit journey transition add jrn_abc123 \
--from welcome_email \
--to nudge_1 \
--after 2d
# With explicit name (otherwise auto-generated as "welcome_email-to-done")
ascendkit journey transition add jrn_abc123 \
--from welcome_email \
--to done \
--on user.login \
--name login-success
# Suppress topology warnings while wiring mid-construction
ascendkit journey transition add jrn_abc123 \
--from welcome_email \
--to nudge_1 \
--after 2d \
--quiet
The --after flag accepts durations like 30m, 12h, 2d, 1w. A bare number defaults to minutes.
Edit transitions
Target a transition by its name:
ascendkit journey transition update jrn_abc123 \
welcome_email-to-nudge_1 \
--after 3d
ascendkit journey transition update jrn_abc123 \
login-success \
--on user.completed_onboarding
Remove transitions
ascendkit journey transition remove jrn_abc123 welcome_email-to-nudge_1
List journeys
ascendkit journey list
ascendkit journey list --status active
Update journey metadata
Full-graph update for power users. Only provided fields are changed:
ascendkit journey update jrn_abc123 --name "Updated Onboarding"
ascendkit journey update jrn_abc123 --nodes '{...}' --transitions '[...]'
When you provide --nodes, any send_email node can also include fromIdentityEmail directly in the JSON:
ascendkit journey update jrn_abc123 \
--nodes '{
"welcome_email": {
"action": {
"type": "send_email",
"templateSlug": "welcome-email",
"fromIdentityEmail": "[email protected]"
},
"terminal": false
}
}'
Journey lifecycle
ascendkit journey activate jrn_abc123 # Start enrolling users
ascendkit journey pause jrn_abc123 # Pause — actions queue, transitions continue
ascendkit journey archive jrn_abc123 # Permanent — exits all users, no undo
When pausing a journey with active users, the CLI will ask for confirmation before proceeding. Use --yes to skip:
ascendkit journey pause jrn_abc123 --yes
Once active, users are automatically enrolled when the entry event fires.
Entry conditions
Filter which users enter the journey based on event properties. When the entry event fires, AscendKit checks the event's properties against the conditions — all key-value pairs must match for the user to enroll.
ascendkit journey create \
--name "Credentials Onboarding" \
--entry-event user.created \
--entry-conditions '{"provider": "credentials"}' \
--entry-node welcome_email
For custom app events, the properties you pass to track() are available as conditions:
# Only enroll users who enrolled in the silver tier
ascendkit journey create \
--name "Silver Tier Onboarding" \
--entry-event app.package.enrolled \
--entry-conditions '{"tier": "silver"}' \
--entry-node welcome_email
This matches events where track("package.enrolled", { tier: "silver" }) was called.
Re-entry policy
Control what happens if a user triggers the entry event again:
ascendkit journey update jrn_abc123 --re-entry-policy skip # Ignore (default)
ascendkit journey update jrn_abc123 --re-entry-policy restart # Re-enroll
Analytics
ascendkit journey analytics jrn_abc123
Shows user counts per node, conversion rates per transition, time-in-node metrics, and totals (enrolled, active, completed, failed).
Delete a journey
ascendkit journey remove jrn_abc123
Deletes the journey and all associated user states.
Node action types
| Type | Description | Required fields |
|---|---|---|
send_email | Send an email template, optionally with a survey | templateSlug, optional surveySlug, fromIdentityEmail |
tag_user | Tag the user | tagName |
advance_stage | Set lifecycle stage | stageName |
none | No-op, used for wait/branching nodes | — |
Email sender identity
Use the --email-id flag to control which verified email identity sends emails for a node:
ascendkit journey node add jrn_abc123 \
--name welcome_email \
--action '{"type": "send_email", "templateSlug": "welcome-email"}' \
--email-id [email protected]
Resolution order — when a send_email node executes, the sender is resolved as:
- Node identity — the
--email-idvalue set on this specific node - Environment default — the default verified identity configured in your email settings
- AscendKit default —
[email protected]if no custom identity is set up
Validation — when you set --email-id, the backend checks whether the identity exists and is verified in the current environment. If it's missing or unverified, a warning is returned but the node is still saved. This lets you configure nodes before completing domain verification.
The same optional sender override is supported inside --nodes JSON as fromIdentityEmail for any send_email node.
Transition triggers
| Type | Description | Fields |
|---|---|---|
event | On user event (e.g., --on user.login) | event (event name) |
timer | After time elapses (e.g., --after 3d) | delay (e.g., 3d, 24h, 30m) |
Transition event filters
Event transitions can filter on event properties so a transition only fires when specific property values match. Add an eventFilter to the trigger JSON:
ascendkit journey transition add jrn_abc123 \
--from trial_started \
--to pro_onboarding \
--trigger '{"type": "event", "event": "app.upgraded_plan", "eventFilter": {"plan": "pro"}}'
This transition only fires when track("upgraded_plan", { plan: "pro" }) is called — other plan values are ignored.
All key-value pairs in eventFilter must match (AND logic). Values are compared with strict equality.
See How event properties flow into journeys for end-to-end examples with both SDKs.
Available events
Built-in events
These are fired automatically by AscendKit:
| Event | Description |
|---|---|
user.created | User signs up (any method) |
user.login | User logs in |
user.signout | User signs out |
user.waitlisted | User placed on waitlist |
user.waitlist_approved | User approved from waitlist |
Application events (app.*)
Your application can fire custom events via the SDK. AscendKit prepends app. to all tracked events, so track("completed_onboarding") fires as app.completed_onboarding.
Tracking events from your app
JavaScript/TypeScript (client-side React hook):
const { track } = useAnalytics();
// Simple event
track("completed_onboarding");
// → fires app.completed_onboarding
// Event with properties — properties are used for journey routing
track("upgraded_plan", { plan: "pro", interval: "annual" });
// → fires app.upgraded_plan with properties { plan: "pro", interval: "annual" }
track("package.enrolled", { packageId: "pkg_abc", tier: "silver", paymentStatus: "paid" });
// → fires app.package.enrolled with those properties
Python (server-side, secret key auth):
from ascendkit import Analytics
analytics = Analytics() # reads ASCENDKIT_SECRET_KEY from env
analytics.track("usr_456", "completed_onboarding")
analytics.track("usr_456", "upgraded_plan", {"plan": "pro", "interval": "annual"})
analytics.track("usr_456", "package.enrolled", {"packageId": "pkg_abc", "tier": "silver"})
How event properties flow into journeys
Properties passed to track() are available at two points in the journey lifecycle:
1. Entry conditions — filter which users enroll in a journey (see Entry conditions):
# Only enroll users who upgraded to the pro plan
ascendkit journey create \
--name "Pro Plan Onboarding" \
--entry-event app.upgraded_plan \
--entry-conditions '{"plan": "pro"}' \
--entry-node welcome_email
When track("upgraded_plan", { plan: "pro" }) fires, the user enrolls. When track("upgraded_plan", { plan: "starter" }) fires, they don't.
2. Transition event filters — control which transitions fire based on properties (see Transition event filters):
# Only advance when the user upgrades to an annual plan
ascendkit journey transition add jrn_abc123 \
--from trial_active \
--to annual_onboarding \
--trigger '{"type": "event", "event": "app.upgraded_plan", "eventFilter": {"interval": "annual"}}'
Both use strict key-value equality with AND logic — all specified pairs must match.
Using app events as transition triggers
ascendkit journey transition add jrn_abc123 \
--from welcome_email \
--to done \
--on app.completed_onboarding
Event properties and email templates
Event properties are used for journey routing (entry conditions, transition filters) but are not automatically available as email template variables. Email templates use a separate variable system:
| Variable | Source | Available in |
|---|---|---|
| Recipient name or email prefix | Journey, campaign |
| Recipient email address | Journey, campaign |
| Environment name | Journey, campaign |
| Environment name | Journey, campaign |
| Recipient's OAuth provider | Journey |
| Generated survey invitation link | Journey |
You can also define custom variables at the environment level or on individual journey node actions. See the email templates docs for details.
Transition naming
Transitions are identified by name for editing and removal:
- Auto-generated:
{from}-to-{to}(e.g.,welcome_email-to-nudge_1) - Duplicates: suffixed (
welcome_email-to-done-2) - Custom: provide
--nameonadd-transitionto override - Env-stable: names are developer-defined strings, preserved across environment promotion