From 99ce8dd22361f2992d5ade682fd0c3af3cbf6430 Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Mon, 8 Jun 2026 12:03:58 +0200 Subject: [PATCH] feat: launch automations from campaigns --- .../app/campaigns/sequences/CampaignFlow.tsx | 115 +++++++++++++++++- .../models/app/campaigns/sequences/Action.ts | 14 ++- 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/web/src/components/app/campaigns/sequences/CampaignFlow.tsx b/web/src/components/app/campaigns/sequences/CampaignFlow.tsx index 0df36229..0ac2e3d3 100644 --- a/web/src/components/app/campaigns/sequences/CampaignFlow.tsx +++ b/web/src/components/app/campaigns/sequences/CampaignFlow.tsx @@ -75,8 +75,10 @@ import { SelectMenu, type SelectOption } from "@/components/ui/select-menu"; import type { AppError } from "@/lib/api/client/normalizeError"; import buildError from "@/lib/helper/buildError"; import SequenceView from "./SequenceView"; +import StepVariants from "./StepVariants"; import CategoryPicker from "@/components/app/contacts/CategoryPicker"; -import type { SequenceAction, SequenceActionType } from "@/lib/api/models/app/campaigns/sequences/Action"; +import type { ActionKV, SequenceAction, SequenceActionType } from "@/lib/api/models/app/campaigns/sequences/Action"; +import { useAutomations } from "@/lib/api/hooks/app/automations/useAutomations"; import TaskTypePicker from "@/components/app/crm/TaskTypePicker"; import DealStagePicker from "@/components/app/crm/DealStagePicker"; import useMembers from "@/lib/api/hooks/app/organizations/useMembers"; @@ -388,6 +390,7 @@ const ACTION_META: Record ) : ( - +
+ + +
)} @@ -1722,6 +1735,7 @@ const ADD_ACTION_OPTIONS: { type: SequenceActionType; label: string }[] = [ { type: "move_deal_stage", label: "Move deal stage" }, { type: "unsubscribe", label: "Unsubscribe" }, { type: "notify", label: "Notify (webhook)" }, + { type: "run_automation", label: "Run automation" }, ]; function AddNodeMenu({ @@ -1812,6 +1826,9 @@ function defaultActionFor(type: SequenceActionType): SequenceAction { if (type === "move_deal_stage") { return { type }; } + if (type === "run_automation") { + return { type, automation_values: [] }; + } return { type }; } @@ -2098,6 +2115,8 @@ function ActionEditor({ )} + {action.type === "run_automation" && } +
+
+ {values.length === 0 ? ( +

+ Add key/value pairs to pass into the automation. Values support {"{{.FirstName}}"} / {"{{.Company}}"}. +

+ ) : ( +
+ {values.map((row, i) => ( +
+ updateRow(i, { key: v })} + placeholder="key" + className="w-28 shrink-0" + /> + = + updateRow(i, { value: v })} + placeholder="{{.FirstName}}" + className="flex-1 min-w-0" + /> + updateRow(i, { value: (row.value ?? "") + token })} /> + +
+ ))} +
+ )} + + + ); +} + // AssigneePicker — choose the teammate a campaign-created task is assigned to. // `null` means "campaign owner" (the executor's fallback). Values are user ids. function AssigneePicker({ diff --git a/web/src/lib/api/models/app/campaigns/sequences/Action.ts b/web/src/lib/api/models/app/campaigns/sequences/Action.ts index ffb05cd6..bd612519 100644 --- a/web/src/lib/api/models/app/campaigns/sequences/Action.ts +++ b/web/src/lib/api/models/app/campaigns/sequences/Action.ts @@ -9,7 +9,15 @@ export type SequenceActionType = | "notify" | "create_task" | "create_deal" - | "move_deal_stage"; + | "move_deal_stage" + | "run_automation"; + +// One templated input passed to a launched automation (value supports the same +// {{.FirstName}}/{{.Company}} contact templating campaign copy uses). +export interface ActionKV { + key: string; + value: string; +} export interface SequenceAction { type: SequenceActionType; @@ -35,4 +43,8 @@ export interface SequenceAction { deal_name?: string; deal_value?: number; deal_currency?: string; // ISO code, defaults to "USD" + // run_automation — launch an automation when the lead reaches this step, + // passing templated key/value inputs as the automation's event data. + automation_id?: string | null; + automation_values?: ActionKV[]; }