Workflows

Build visual automation workflows using a drag-and-drop DAG (directed acyclic graph) editor. Workflows connect trigger nodes to action nodes, enabling complex email operations without code.

Create a workflow

POST
/v1/workflows

Create a new workflow in draft status.

Request body

ParameterTypeDescription
namerequiredstringWorkflow name.
descriptionstringOptional description of what the workflow does.
Response — 201 Created
{
  "id": "wf_abc123",
  "name": "Bounce Handler",
  "description": "Suppress permanently bounced emails and notify Slack",
  "status": "draft",
  "nodes": [],
  "connections": [],
  "createdAt": "2026-03-21T00:00:00Z"
}

List workflows

GET
/v1/workflows

List all workflows for the current team.

Get a workflow

GET
/v1/workflows/{id}

Retrieve a workflow with its full node and connection graph.

Update a workflow

PATCH
/v1/workflows/{id}

Update workflow name, description, nodes, or connections.

Delete a workflow

DELETE
/v1/workflows/{id}

Delete a workflow. Active workflows must be deactivated first.

Activate or deactivate

POST
/v1/workflows/{id}/activate

Toggle a workflow between active and inactive. Active workflows respond to their trigger events.

Active workflows are read-only — editing (PATCH) is rejected while a workflow is active. Deactivate it first; draft and inactive workflows are editable.

Duplicate a workflow

POST
/v1/workflows/{id}/duplicate

Clone a workflow's node/connection graph into a new draft. Execution history is not copied, and the copy gets its own webhook trigger key/URL.

Manually trigger execution

POST
/v1/workflows/{id}/executions

Manually trigger a workflow execution. Optionally pass trigger data in the request body.

Send an optional Idempotency-Key header to make the trigger safe to retry: a repeat request with the same key on the same workflow returns the original execution instead of firing a second run. The same header is honoured on the public webhook trigger (POST /v1/workflows/triggers/{publicKey}).

List execution history

GET
/v1/workflows/{id}/executions

List executions for a workflow with cursor-based pagination, most recent first.

Query parameters

ParameterTypeDescription
statusstringFilter by execution status: running, completed, failed, cancelled, or waiting.
limitnumberResults per page (default: 20, max: 100).
cursorstringCursor for pagination (the cursor value from the previous response).

Example response

Each list item carries a truncated error preview (~200 chars) so failures can be spotted without opening every run; the full error and per-node detail live on the detail endpoint below.

{
  "data": [
    {
      "id": "8f1c0c2e-9a3b-4f7d-8e21-7b2d9c4a1e55",
      "workflowId": "2b9a7d10-4c3e-4a1f-9d52-1e6f3b8c0a44",
      "status": "failed",
      "nodeCount": 3,
      "startedAt": "2026-06-02T10:30:00Z",
      "completedAt": "2026-06-02T10:30:02Z",
      "error": "send_email failed: smtp 550 rejected"
    }
  ],
  "cursor": "2026-06-02T10:30:00.0000000Z",
  "hasMore": true
}

Get execution detail

GET
/v1/workflows/{id}/executions/{exec_id}

Get detailed execution results including the trigger payload and per-node status, input/output data, and errors.

Example response

{
  "id": "8f1c0c2e-9a3b-4f7d-8e21-7b2d9c4a1e55",
  "workflowId": "2b9a7d10-4c3e-4a1f-9d52-1e6f3b8c0a44",
  "status": "failed",
  "triggerData": { "email_id": "d290f1ee-6c54-4b01-90e6-d701748f0851" },
  "error": "send_email failed: smtp 550 rejected",
  "nodes": [
    {
      "id": "c4a1e556-7b2d-9c4a-8e21-9a3b4f7d8f1c",
      "nodeId": "b1f2...",
      "nodeType": "send_email",
      "status": "failed",
      "inputData": { "to": "user@example.com" },
      "outputData": null,
      "error": "smtp 550 rejected",
      "startedAt": "2026-06-02T10:30:01Z",
      "completedAt": "2026-06-02T10:30:02Z"
    }
  ],
  "startedAt": "2026-06-02T10:30:00Z",
  "completedAt": "2026-06-02T10:30:02Z"
}

Rerun an execution

POST
/v1/workflows/{id}/executions/{exec_id}/rerun

Create a new execution from a finished one. The original run is left unchanged as an audit record.

Request body

ParameterTypeDescription
fromFailedNodebooleanDefault false replays the whole graph. true preserves the original run's completed-prefix node outputs and re-runs from the failed node onward (only valid when the source execution failed).

Returns 201 with the new execution (same shape as the detail endpoint). Only finished executions can be rerun — a run that is still running or waiting returns 400.

Execution status

ParameterTypeDescription
runningstatusExecution is in progress.
completedstatusAll nodes finished successfully.
failedstatusA node failed after exhausting its retries; see the error field.
cancelledstatusExecution was cancelled before completing.
waitingstatusParked on a long delay node; a scheduler resumes it when the delay elapses.

Node status

ParameterTypeDescription
pendingstatusNode has not started yet.
runningstatusNode is executing.
completedstatusNode finished successfully.
failedstatusNode failed after exhausting its retries.
skippedstatusNode was skipped — the un-taken branch of a condition node.
waitingstatusDelay node sleeping on a long timer; flips to completed when it fires.

Workflow status

ParameterTypeDescription
draftstatusWorkflow is being designed. Does not respond to triggers.
activestatusWorkflow is live and will execute when triggered.
inactivestatusWorkflow is paused. Will not respond to triggers until reactivated.

Trigger nodes

Every workflow starts with a trigger node that defines when the workflow runs:

ParameterTypeDescription
email_eventtriggerFires on email events: delivered, bounced, opened, clicked, complained.
webhook_receivedtriggerFires when an external HTTP POST hits the workflow unique URL.
scheduletriggerFires on a recurring cron schedule (e.g. daily digest, weekly report).
manualtriggerTriggered manually from the dashboard or via the API.
contact_eventtriggerFires when a contact is created, updated, unsubscribed, or added to a segment.
broadcast_completetriggerFires when a broadcast finishes sending.

Action nodes

Action nodes define the steps in your workflow:

ParameterTypeDescription
send_emailactionSend a transactional email via the Txtly API.
send_broadcastactionTrigger a broadcast to a segment.
http_requestactionMake an external HTTP call (any method, headers, body).
delayactionWait for a specified duration before continuing.
conditionactionBranch based on a condition. Outputs: true and false paths.
filteractionContinue only if a condition is met, otherwise stop this branch.
add_segmentactionAdd a contact to a segment.
remove_segmentactionRemove a contact from a segment.
update_contactactionModify contact properties.
suppress_emailactionAdd an email address to the suppression list.
slack_notificationactionSend a message to a Slack channel via webhook.
discord_notificationactionSend a message to a Discord webhook.
transform_dataactionMap and transform data between nodes using JSONPath or templates.
codeactionRun custom C# code for advanced logic.

HTTP Request node

The http_request action calls any external HTTP API as part of a workflow. Non-2xx responses do not fail the node — the runner returns the full response so a downstream condition node can branch on node_{id}.status_code. The orchestrator only retries on network failure or unresolvable credential.

Config

ParameterTypeDescription
urlrequiredstringAbsolute http:// or https:// URL. file://, ftp:// etc. are rejected.
methodrequiredstringOne of GET, POST, PUT, PATCH, DELETE.
headersobjectFree-form string→string map of request headers. Applied before auth so a credential-backed Authorization wins on conflict.
bodystringRequest body. Ignored for GET and DELETE. Set Content-Type via headers.
auth_typestringOne of none (default), bearer, basic, header.
auth_header_namestringRequired when auth_type=header — the header to inject the credential's value under.
credential_idstringUUID of a Credential the team owns. Required for any auth_type other than none.

Output

The runner sets node_{id} in the execution context to:

{
  "status_code": 200,
  "ok": true,
  "headers": { "Content-Type": "application/json", "X-Trace-Id": "..." },
  "body": "{\"result\":\"ok\"}"
}

Execution engine

Workflows are executed as a DAG using topological sort to determine execution order. Independent branches run in parallel, while nodes within a branch run sequentially. Each node's output is available to downstream nodes as input data. Execution state is persisted at each step for observability and resume capability.

Example: Bounce Handler

[Email Bounced] --> [Condition: bounce_type == 'permanent'?]
  --> true:  [Suppress Email] --> [Slack: "Permanent bounce: {{email}}"]
  --> false: [Delay: 1 hour]  --> [Send Email: retry original]