How automation behaves
Short articles for the less obvious rules. Inline hints elsewhere link here with “Learn more”.

When a workflow fires

A workflow starts a run when all of the following are true:

  1. The workflow is active.
  2. The event’s organisation matches the workflow (unscoped events skip unscoped workflows).
  3. The event type matches the workflow’s trigger, including any trigger settings (for example a form ID or pipeline).
  4. The workflow’s conditions pass. Malformed conditions fail closed — the workflow does not run.

Each committed write is its own occurrence. Updating a contact three times starts a CONTACT_UPDATED workflow three times. Re-emitting the same write (queue retry, relay crash) does not start a second run: event-sourced runs are unique per occurrence and workflow.

Operator test-fires and replays are not occurrences. They always create a new run.

Events that match no workflow appear on Automation → Health with a reason: no matching workflow, malformed conditions, or the loop depth cap.

Duplicate inbound deliveries

Inbound webhooks use the same occurrence idea as internal events: one run per delivery and workflow.

If the provider sends a delivery ID, that ID is the occurrence. Re-sending the same delivery ID does not start another run, even days later.

A custom endpoint without a delivery ID treats an identical body as a duplicate only inside the endpoint’s retry window:

  • Default is 15 minutes.
  • 0 means never treat a body as a duplicate.
  • “Forever” never expires body-hash duplicates.

A re-send of the same body after the window, or a new delivery ID, starts a new run.

Recipients by role

Send Email can address a contact-point role on the trigger record instead of a fixed address. The default role key is transactional.

Resolution stops at the first hit:

  1. An explicit address, if the action named one.
  2. An address that holds the requested role.
  3. An address that holds primary.
  4. The oldest remaining address (createdAt, then id).

Role scope limits the pool: contact-only, company-only, or both. When the role applies to both, company addresses are preferred, then contact addresses.

If no address is found, the send fails and the step is logged as failed.

Wait timing

A Wait step parks the run until amount of unit (minutes, hours, or days) has elapsed from the moment the wait starts. A one-hour wait that starts at 10:16 resumes at 11:16.

The run stays WAITING while every live position is parked. If another branch is still running, the run stays RUNNING.

The engine re-enqueues parked waits as their resume time approaches. If the queue delivers a wait early, the step parks again instead of continuing.

Time windows (“only resume on weekdays between 09:00 and 17:00”) are not available yet. Continue-now and skip-to-next-window operator commands are not available yet.

Inactive vs cancelled

Inactive is a workflow setting (isActive). Turning a workflow off stops it matching new events. In-flight runs keep going; they are not cancelled and their waits still resume.

If any runs are still in progress or waiting, switching off shows how many of each (for example "3 in progress, 2 waiting") and a link to that filtered run list. Switching off with no in-flight runs needs no extra step.

There is no cancelled run status yet. You cannot cancel a run from the hub. Leave or cancel for in-flight runs will arrive later. Deleting a workflow still removes it (and its runs) rather than archiving it.

Run statuses

The engine writes these run statuses:

  • RUNNING — at least one live position is not parked.
  • WAITING — every live position is parked on a wait.
  • SUCCESS — logged steps succeeded or were skipped (including dry-run skips).
  • FAILED — a step failed and the run did not continue past that failure. A linear run that succeeded at step 1 and failed at step 2 is FAILED.
  • PARTIAL — a step failed and a later step still succeeded (the run carried on past the failure).
  • SKIPPED_DRY_RUN — a dry-run test-fire that did not execute live actions.

There is no COMPLETED status. The hub run list, run detail and dashboard show these engine statuses: success counts use SUCCESS, and PARTIAL has its own badge.

CANCELLED is not a status yet.

Test-fire, replay and versions

Test-fire evaluates the matcher against a record you choose (scope, trigger settings, conditions) and can start a run labelled TEST_FIRE. It defaults to dry-run: actions are logged SKIPPED_DRY_RUN and nothing is sent. Run live opts in to real side effects: the engine claims the first step and the run executes to completion. Force starts a run even when conditions fail.

Replay starts a new run labelled REPLAY with the original trigger payload. The original run is never mutated. Replay also defaults to dry-run; run live executes the current workflow to completion.

Test-fires and replays are not occurrence-deduplicated. Each is a new run.

Workflows do not yet keep immutable definition versions. A replay follows the workflow as it is saved now, not the definition that was current when the original run started.

Dead letters

Malformed messages, and messages for a run or step that no longer exists, are recorded as dead letters and acknowledged immediately instead of being retried. Each automation dead-letter queue (events, actions, webhook processing) is consumed and writes the same record.

Open dead letters for your organisation appear on Automation → Health. You can Re-drive a recoverable message back onto its original queue, or Discard it. Both actions are audited.

Relay rows that stay FAILED on the automation outbox are not copied into this list; they remain on the outbox and show in the Failed relays count.

Transient send failures retry through the actions queue (max_retries is 3, so the fourth delivery is the last). After that the step is logged FAILED with the error. The run does not wait on a dead-letter queue.

Forge Tools