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 show after 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

TypeDescriptionRequired fields
send_emailSend an email template, optionally with a surveytemplateSlug, optional surveySlug, fromIdentityEmail
tag_userTag the usertagName
advance_stageSet lifecycle stagestageName
noneNo-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:

  1. Node identity — the --email-id value set on this specific node
  2. Environment default — the default verified identity configured in your email settings
  3. 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

TypeDescriptionFields
eventOn user event (e.g., --on user.login)event (event name)
timerAfter 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:

EventDescription
user.createdUser signs up (any method)
user.loginUser logs in
user.signoutUser signs out
user.waitlistedUser placed on waitlist
user.waitlist_approvedUser 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:

VariableSourceAvailable in
Recipient name or email prefixJourney, campaign
Recipient email addressJourney, campaign
Environment nameJourney, campaign
Environment nameJourney, campaign
Recipient's OAuth providerJourney
Generated survey invitation linkJourney

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 --name on add-transition to override
  • Env-stable: names are developer-defined strings, preserved across environment promotion