---
title: Automations
description: "A visual flow builder: a trigger event connected to action steps across your integrations."
---

An automation is a flow on a canvas: one **trigger** (a reply arrives, a meeting is booked) runs one or more **actions** (post to Slack, push to your CRM, tag a contact), with **IF conditions** in between to branch. No code, though an advanced mode accepts a free-form condition.

<Mermaid
  chart={`
flowchart LR
  T["Trigger"] --> C{"IF condition"}
  C -->|yes| A1["Action"]
  C -->|no| A2["Action"]
  A1 -.->|on error| E["Error path"]
`}
/>

## The builder

Open the **Automations** tab. Each automation has a name, an on/off toggle, and a canvas. **Off** never fires, even from a campaign step.

- The **Trigger** node sits at the top and cannot be deleted.
- Drag a node's dot onto another node to connect, or onto empty canvas to pick what comes next (AI step, AI switch, condition, action, or **Stop**) already connected.
- Click a node to edit it. Click a connection and press `Delete` to remove it.
- **Add** places a node; **Tidy up** auto-arranges.
- Card positions save on their own, and the canvas is collaborative: teammates' cursors, live card movement, their selections outlined in their color. Press `/` to chat. See [Collaboration](/guides/collaboration/).

<Callout type="info" title="Save before it runs">
Triggers, conditions, and actions only take effect after **Save** (card positions are the exception). **Test** saves first, then dry-runs.
</Callout>

## Triggers

| Trigger | Fires when |
| --- | --- |
| Reply received | A contact replies to a campaign email |
| Meeting booked / rescheduled / canceled | A Calendly or Cal.com meeting changes |
| Email bounced | A campaign email bounces |
| Unsubscribed | A contact unsubscribes |
| Warmup health changed | A mailbox's warmup health state changes |
| Deliverability complaint | A spam complaint is recorded |
| Inbound webhook | An external system POSTs to this automation's unique URL |
| Campaign action | A campaign sequence reaches a "Run automation" step |

Each trigger carries its own event data (a reply carries contact email, reply intent, classifier confidence). Those are the values your conditions test and your action text inserts. Full per-trigger variable list: [Personalization & expressions](/guides/expressions/).

**Campaign action** never fires by itself; it runs only from a campaign step, still evaluating your conditions.

### Inbound webhook

Pick the trigger and **save**, and the editor shows a unique URL. Anything that can send HTTP `POST`s JSON to it.

- The JSON body becomes the payload: `{"email":"a@b.com"}` gives you `{{.email}}`. A non-object body is exposed as `{{.body}}`.
- **The URL is the credential.** It carries a high-entropy token, so treat it as a secret, send it over HTTPS, and rotate it (switch the trigger away and back) if it leaks.
- The request returns immediately and the flow runs in the background, so a slow action never blocks the caller.
- Keys starting with an underscore are stripped; the body is capped at 1 MB.

## IF conditions

An IF node splits into a **yes** path (right dot) and a **no** path (bottom dot). Only the matching branch runs; an unconnected branch just ends, or drag it to a **Stop** node.

Pick a **field**, an **operator**, and a **value**. Fields depend on the trigger, so they are always meaningful.

| Field type | Operators |
| --- | --- |
| Text | `is`, `is not`, `contains`, `is present` |
| Number | `≥`, `≤`, `is`, `is present` |
| Choice (enum) | `is`, `is not`, `is present` |

Three special conditions work with any trigger:

- **Ask AI (yes/no)**: a plain-language question about the event ("Is this reply asking about pricing?") picks the path. Templated, so you can drop in `{{.field}}`. One credit per evaluation; if the model can't answer or you are out of credits, the no path runs and History shows why. See [AI steps in automations](/guides/ai-steps-in-automations/).
- **Random split**: a set percentage (`1%` to `99%`) down the yes path, deterministic per event so re-deliveries never flip it.
- **Advanced expression**: a free-form condition, taken when truthy.

```
and (gtf .confidence 0.8) (eq .intent "positive")
```

Comparisons (`eq`, `gt`, `lt`), numeric coercion (`gtf`, `ltf`, `add`, `sub`, `mul`, `div`), logic (`and`, `or`, `not`), and text helpers (`contains`, `lower`) are available. A broken or empty expression is **false**, so a malformed condition never silently passes, and it is validated on save.

## Actions

Each action node picks a **Run** target (an integration, or Warmbly built-in) and an **Action**.

| Integration action | What it does |
| --- | --- |
| Send a Slack / Discord message | Posts to a channel (Slack needs `#channel`) |
| Send a webhook | HTTP request to a URL you provide |
| Create / update HubSpot contact, Pipedrive person, Salesforce contact, Close lead | Upserts the record |

Slack, Discord, and webhook actions take an optional message template. Slack and Discord arrive as a branded card in Warmbly's accent color with contact and subject fields, not a plain line.

| Built-in action | What it does |
| --- | --- |
| Add / remove a tag | Adds or removes a contact category |
| Label the email | Applies inbox labels to the replied-on conversation |
| Create a task | Assigned to the workspace owner |
| Create a deal | In a chosen pipeline and stage |
| Move the deal stage | Moves the contact's most recent open deal in that pipeline |
| Unsubscribe the contact | Only when the event carries a campaign |
| Set variables | Computes named values from templates for later steps to reuse as `{{.name}}` |
| Fire event | Publishes a `CUSTOM_EVENT` to the realtime gateway |
| AI step / AI switch | An agent or single-shot AI node, and AI-decided routing. See [AI steps](/guides/ai-steps-in-automations/) |

Three constraints worth knowing: **Move the deal stage** does nothing if the contact has no open deal in that pipeline, **Unsubscribe** needs an event carrying a campaign, and **Label the email** works only on a Reply received automation, since it needs a thread to label.

**Fire event** is the inverse of the inbound webhook: your app subscribes over the websocket with an API key holding `REALTIME_SUBSCRIBE` and receives `{ name, payload }`, so you get events without hosting a public URL. See [Realtime events](/api/realtime/).

### The on-error branch

Every action has a red dot on its right edge. Drag from it to handle failures (a rejected Slack message, a CRM timeout).

- **With** an on-error branch connected, a failure follows it and the run is **not** marked failed, like a try/catch. The normal path is skipped.
- **Without** one, the failure is recorded in History and the run is marked errored, but the flow continues down the normal path best-effort, so one bad step never blocks the rest.
- A dry run never fails an action, so **Test** always shows the normal path.

### Templating

Text fields (messages, webhook URLs, deal names, task titles) accept trigger variables as `{{.key}}`, with clickable chips in the builder to insert them:

```
New reply from {{.contact_email}} on {{.campaign_name}}
```

Unknown tokens render empty rather than failing.

## Testing and history

**Test** saves the canvas and dry-runs it against sample data: no messages sent, no records changed. It shows the exact path taken and a preview of each action. AI steps are the exception, running for real so you see actual output, which spends one credit per AI step.

**History** shows recent real runs: success or error, start time, and per-step results (error text on failure, a short summary on success). It updates live as the automation fires.

<Callout type="info" title="Best-effort by design">
Runs are best-effort and bounded against loops, so one failing step won't block the rest. The run is still recorded as errored so you can see what went wrong.
</Callout>

## Related guides

<Cards>
  <Card title="Personalization & expressions" href="/guides/expressions/">
    The condition and templating language shared by automations and campaigns.
  </Card>
  <Card title="Deliverability" href="/guides/deliverability/">
    Warmup health and complaint signals that several triggers fire on.
  </Card>
</Cards>
