Generated banner for Building Workflows
Generated logo for Building Workflows

Building Workflows

PostHogOfficialPublished by PostHog

  • Skill
  • Workflow
  • Data and AI
  • MIT
  • No issues found

From the author

Build, edit, test, enable, and monitor PostHog workflows over MCP. Author the action/edge graph so it runs and opens cleanly in the visual editor, then change drafts surgically with patch operations. Use when asked to build, set up, automate, change, fix, or debug a workflow, campaign, broadcast, drip sequence, or event-triggered automation in the workflows product, including first-time messaging setup (email channel, sending domain, frequency cap) before the first workflow.
— the description by PostHog

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a systematic lifecycle for creating and maintaining PostHog workflows. It guides users through composing directed graphs of actions and edges, testing them step-by-step with sample payloads, and managing the transition from draft to active status. It emphasizes structural integrity to ensure compatibility with the visual editor and provides methods for surgical updates to workflows and email content.

What it does

  • Author workflow graphs as JSON structures containing triggers, actions, and edges.
  • Validate workflow structure to prevent visual editor errors.
  • Execute step-by-step test runs using sample event or person payloads.
  • Manage workflow lifecycles from draft creation to active deployment.
  • Apply surgical updates to active workflows using atomic patch operations.
  • Monitor workflow execution through logs and per-step trace analysis.

When to use it

  • When you need to build or automate event-triggered sequences, drip campaigns, or broadcasts.
  • When setting up initial messaging infrastructure like email channels and sending domains.
  • When debugging or fixing existing workflow logic or execution paths.
  • When modifying active workflows while ensuring minimal disruption to in-flight runs.

Advantages

  • Ensures structural consistency between JSON definitions and the visual editor.
  • Allows for granular testing of individual workflow steps before full activation.
  • Supports atomic updates to drafts without replacing the entire workflow configuration.
  • Provides clear visibility into execution traces and failure points.

Usage examples

“Set up a new workflow that sends a reminder email one day after a user signs up.”
The agent will verify the project's messaging setup, compose the workflow graph with a trigger and delay node, and guide you through testing the draft.
“I need to change the subject line of an email in my active 'Welcome' workflow.”
The agent will stage a draft of the workflow, apply the email patch to the specific action, and help you preview the impact before publishing.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/posthog/building-workflows/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip building-workflows.zip -d .agents/skills

Documentation by PostHog

Building workflows

A PostHog workflow is a directed graph: a list of action nodes (actions) wired by edges (edges), with exactly one trigger node that starts every run. You author that graph as JSON and ship it over MCP. Always call it a "workflow" to the user. "Hog flow" is the internal code name (HogFlow), not a user-facing term.

The single biggest failure mode is getting the graph JSON structurally wrong. The backend stores actions/config as loose JSON, but the visual editor parses every node against a strict schema, so a malformed node saves but then breaks the editor view for the whole workflow. Before composing or editing any graph, read references/graph-schema.md. It is the contract; do not improvise node shapes from these examples alone.

A one-off email that the user's code sends to one person, such as a password reset, a sign-in code or a receipt, is a transactional email, not a workflow. Use the sending-transactional-emails skill for it.

Before the first messaging workflow

When the user asks to set up workflows or messaging, or asks for an email, SMS or push workflow in a project that may not send yet, call workflows-setup-status before composing anything. It says which setup steps are open and links each one in the app. Walk the user through the open steps, set the sending rules, then offer suggested workflows. Read references/onboarding.md for the flow and the defaults to recommend.

Skip this for automations that only post to Slack or call webhooks: they need no email setup. If workflows-setup-status is not available, go straight to the lifecycle below.

The lifecycle

Work the workflow through these stages. Don't jump straight to enabling it.

  1. Compose the graph. Build actions + edges per references/graph-schema.md. For any function node, don't guess the template: list the live catalog with cdp-function-templates-list and read its required inputs with cdp-function-templates-retrieve.

  2. Create as a draft. workflows-create. Every workflow is created draft; it does not execute yet.

  3. Test-run it. workflows-test-run runs one step at a time. Start at the first step (omit current_action_id, or point it at the trigger) with sample globals ({event, person, groups}), shaped like the trigger's real payload: an event trigger needs an event matching its filters, and an internal-event trigger needs an event named in its filters.events (for the Slack trigger, a $slack_message_received event with the Slack property bag) and no person (see references/graph-schema.md). A status=skipped result means this payload would not fire the trigger: if you fabricated the payload, fix it to match the trigger; if it came from a real past run (workflows-get-invocation), the trigger's filter is wrong, not the payload. The result includes the next step's id (nextActionId). Feed that back as current_action_id and run again, walking step by step to the end. Skip delay nodes by jumping to the action after them (delays aren't simulated). Async side effects (HTTP/email/SMS/push) are mocked unless you set mock_async_functions=false. Read each step's trace to confirm the path taken.

  4. Read logs while iterating. workflows-logs shows the per-step execution trace (levels DEBUG to ERROR). This is how you see why a step skipped, branched, or errored.

  5. Edit, then re-test. Patch the graph with workflows-patch-graph (see Editing a draft). Every edit invalidates your earlier test — re-run the affected path before moving on. On a draft workflow, edits apply directly; on an active one they stage a draft (see Changing a live workflow).

  6. Enable (needs the user's explicit sign-off). workflows-enable flips it to active and an event/webhook/manual trigger starts firing on matching activity. From then on it runs on real people, and every change goes through the draft → test → publish cycle before taking effect — so finish testing, then get the user's explicit go before enabling. Don't enable on your own initiative.

  7. Dispatch (batch/schedule only). A batch or schedule workflow does not fire on enable alone. Send a one-off broadcast with workflows-run-batch, or attach a recurring schedule with workflows-schedule-create. A batch trigger fans out to a person audience, so scheduling it needs the workflows-blast-radius preview and its confirm token; a schedule trigger runs once per occurrence with no audience, so schedule it directly. Confirm with workflows-get that status=='active' and its read-only schedules field has an active entry.

  8. Monitor. Drill down: workflows-global-stats (which workflows are failing) to workflows-stats (one workflow's trend) to workflows-list-invocations (who it failed for) to workflows-get-invocation (the triggering payload) to workflows-logs (the failing step).

Full tool catalog, grouped by job: references/lifecycle-and-debugging.md.

Editing a draft

Patch, don't replace. Edit a draft with workflows-patch-graph: a small, ordered list of id-addressed operations (update_action, add_action, remove_action, add_edge, remove_edge, replace_action_edges). update_action deep-merges its patch, so changing one email subject is a few lines, not the whole graph. The ops apply atomically server-side (read, apply in order, validate, save only if valid), and the response echoes the full updated graph, so you never re-fetch before the next edit. This keeps each round-trip tiny instead of re-transmitting every action and edge.

workflows-update covers only what a graph patch can't express: top-level fields like name, description, exit_condition, conversion, trigger_masking, and variables. It rejects actions/edges outright - a partial list would silently drop every step it omits - so every graph change goes through workflows-patch-graph.

After any patch, re-test the path you changed (step 3). A patch that validates structurally can still route the wrong way.

Email content follows the same rule. The email inside a function_email step is edited with workflows-patch-action-email: the same id-addressed design ops as the template patch, plus an email_patch merge for subject/preheader/text/recipients, with the HTML re-rendered server-side so it always matches the design. Prefer it over workflows-patch-graph update_action for email content - an update_action that changes design leaves the stored html stale. Library templates are edited with workflows-patch-email-template, not workflows-update-email-template (which resends the entire design JSON). Compose and edit email designs with the designing-email-templates skill.

Changing a live workflow

Editing an active workflow stages a draft instead of changing what's running: nothing reaches real people until you publish. Work the cycle:

  1. Edit. workflows-patch-graph (or workflows-update for content fields) on the active workflow writes to its draft — the first edit copies the live graph into the draft, later edits compose onto it. workflows-get shows the staged draft in draft; the live config stays in actions/edges. Metadata (name, description) applies live immediately.

  2. Test the draft. workflows-test-run with use_draft=true executes the staged draft instead of the live config. Re-test every path you changed.

  3. Publish deliberately. workflows-publish without confirm returns in_flight_runs, a confirm_token, and an impact summary: per deleted step, about how many people are parked there and whether they move to a surviving step (moves_to) or exit; empty_variables that may render empty for people already past their new producer when they reach a reference (a structural warning — it can fire even when everyone in-flight is still upstream of the producer); schedule_conflicts where a schedule overrides a variable the draft removes. Echo the impact to the user and get their go-ahead, then call again with confirm=true and that confirm_token. A 409 means the draft changed since the preview and a 400 means the token expired (15 minutes) — preview again and re-confirm either way. Publish revalidates everything, so an invalid draft is rejected and live config stays untouched.

  4. Or bail. workflows-discard-draft throws the staged draft away.

In-flight runs follow the live config: once published, people mid-flow continue from their current step on the new version. Steps they already passed don't re-run; people parked on a step the publish deletes skip forward to its next surviving step (or exit at a dead end), exactly as the impact preview reported.

Timing edits apply to parked runs gradually, not instantly. Publishing a shortened delay (or a moved wait window) reschedules the runs parked on it via a rate-limited sweep. Runs already due to wake soon keep their original earlier wake untouched; only wakes that the sweep moves earlier are affected, and those land spread out, no sooner than a few minutes after publish (and never later than their original wake). Runs still parked shortly after publishing are expected - tell the user this rather than re-publishing or treating it as a failure.

Rolling back

Every live-content change appends a snapshot to the workflow's revision history. workflows-list-revisions lists versions (newest first); workflows-get-revision returns one version's full content. To roll back (or forward), workflows-restore-revision copies that version's content into the draft — it never touches the live config — then the normal publish cycle applies: test with use_draft=true, preview, confirm. The preview shows exactly what the rollback does to people in-flight, same as any publish.

A restore returns 409 when a draft is already open; publish or discard it, or pass overwrite=true to replace it. Two things a rollback cannot undo: runs that already moved or exited while the newer version was live keep their positions (their side effects happened), and a publish that shortened a delay may have pulled parked wake times earlier — rolling back doesn't push them later again.

What the server owns, never send it

The server compiles and manages these. Authoring them by hand is the fastest way to a broken workflow:

  • bytecode on any filter, trigger, condition, conversion, or masking. Compiled server-side from the human-readable properties/hash. Omit it; send filters: {...}, not bytecode.

  • trigger (top-level). Derived from the trigger action in actions. Read-only. Set the trigger by adding the trigger node, not by setting this field.

  • billable_action_types, version, id, created_*. Computed/managed.

Minimal worked example

Event trigger, wait 1 day, send email, exit. Note: exactly one trigger, every non-exit node has an outgoing edge, ids are referenced consistently by edges, and no bytecode is sent.

{
  "name": "Nudge after signup",
  "description": "One day after signup, send a reminder.",
  "exit_condition": "exit_only_at_end",
  "actions": [
    {
      "id": "trigger_node",
      "name": "Signed up",
      "type": "trigger",
      "config": {
        "type": "event",
        "filters": { "events": [{ "id": "user signed up", "name": "user signed up", "type": "events", "order": 0 }] }
      }
    },
    {
      "id": "delay_1",
      "name": "Wait 1 day",
      "type": "delay",
      "config": { "delay_duration": "1d" }
    },
    {
      "id": "email_1",
      "name": "Reminder email",
      "type": "function_email",
      "config": {
        "template_id": "template-email",
        "template_uuid": "<uuid returned by workflows-create-email-template>",
        "message_category_type": "marketing",
        "inputs": {
          "email": {
            "value": {
              "to": { "email": "{person.properties.email}", "name": "" },
              "from": { "email": "[email protected]", "name": "Example" }
            }
          }
        }
      }
    },
    {
      "id": "exit_node",
      "name": "Exit",
      "type": "exit",
      "config": { "reason": "Done" }
    }
  ],
  "edges": [
    { "from": "trigger_node", "to": "delay_1", "type": "continue" },
    { "from": "delay_1", "to": "email_1", "type": "continue" },
    { "from": "email_1", "to": "exit_node", "type": "continue" }
  ]
}

Email bodies come from the template library, not hand-written html:

  1. Reuse first. List the library with workflows-list-email-templates and pick a template that fits. A drip campaign typically references one base template (say, a branded announcement) from every email step.

  2. Create only if nothing fits. Author a new template design-first with the designing-email-templates skill: compose the design, omit html (the server renders html from the design).

  3. Reference it by putting the template's UUID in each step's config.template_uuid, as above. The save snapshots the template's subject, text, html, and design into the step.

  4. Differentiate per step with workflows-patch-action-email design operations - each step's snapshot is edited independently, so five steps from one base template can each carry their own content.

The snapshot is one-way: editing the library template later does not change steps that already referenced it, and patching a step never touches the library template. If a user expects a template edit to flow into their workflows, correct that - the steps keep their copies, and each one is updated with workflows-patch-action-email.

Always give templates a real plain-text text alongside the design: clients that block rich content show only text, so filler like "placeholder" reaches real inboxes.

Hard rules to surface to the user, not work around

  • Behavioral targeting is unsupported. "Did event X at least N times over the last M days" can't be expressed as a trigger or a batch/schedule audience. If asked, reject it and explain; don't approximate it with a broken filter. (The backend rejects behavioral cohorts in batch audiences outright.)

  • Batch audiences target who a person is, not what they did. Person properties and/or static/property-based cohorts only. Event/action filters in a batch audience are silently dropped, so they're rejected.

  • Prefer re-evaluating audiences. For batch, inline person-property conditions or a dynamic (filter-based) cohort re-evaluate as people qualify; a static cohort is a frozen list, use only for an explicit given set.

Files

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
PostHog
Publisher
PostHog (Official)
Source
PostHog/skills · skills/omnibus/building-workflows · commit a5d30e7
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by PostHog.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download building-workflows.zip