Groundbase

Settings → Automations is where every "do this automatically" rule lives. There are two flavors under one roof:

The picker on the Automations page — "+ New automation" → Workflow vs Sequence — routes you to the right editor. Both surfaces share the same trigger / condition / action vocabulary; sequences just add the per-contact timeline.

Find them at Settings → Automations.

Starter templates (fastest way in)

The Automations page has two template buttons:

Each one opens the matching editor pre-populated — you tweak the bits that mention "CHANGE-ME" (or just review the steps), then save.

Workflow templates that ship today:

If a template doesn't fit, start with the closest one and edit it — that's usually faster than starting from scratch.

Anatomy of a workflow

Every workflow has three parts:

  1. Trigger — what fires it. Options:

    • When something happens (entity event) — see the full list of supported events below
    • On a schedule — once / hourly / daily / weekly in your local timezone
    • When an inbound webhook arrives — chained off Settings → Webhooks → Inbound
    • When I run it manually — a Run button on the workflow row
  2. Condition (optional) — gates execution. "Only run if all of these are true": a list of field/operator/value rules. Supported operators:

    • equals, not_equals
    • contains, not_contains, starts_with
    • is_set, is_empty
    • includes_tag (only on contact/company/deal triggers)

    Field paths reference the trigger context:

    • entity.first_name, entity.email, entity.source, etc. — direct columns on the triggered entity
    • entity.tags — the entity's tag list (used with includes_tag)
    • trigger.payload.<field> — anything in the event payload
  3. Actions — what to run, in order. Each action has a type + a config:

    • Apply tag / Remove tag{ tag_name }. Defaults to the trigger entity.
    • Create task{ title, description?, due_in_days?, kind?, location? }. kind is task / event / meeting (matches the manual-create kind picker). Auto-links to the contact when one's in the trigger. Title supports template variables like Follow up with {{first_name}}.
    • Create note{ body }. Drops on the trigger entity. Template variables supported.
    • Update field{ entity_type, field, value }. Safe-column allowlist enforced per entity (can't rewrite system columns like user_id).
    • Send SMS{ body, to?, contact_id? }. Sends through your default Twilio number. If you leave to blank, it uses the contact from the trigger. If you SET to to a different number, we look up which contact owns that number and thread the SMS under them instead. US/CA destinations only. Honors STOP opt-outs and account suspension.
    • Send email{ to, subject, body_html?, body_text?, from_name? }. Sends through your default email account (Settings → Email integration). Template variables work in every field.
    • Fire webhook{ url, method?, headers?, body? }. Custom POST to a URL of your choice. Default body is { trigger, entity }. 15-second timeout per call. The HTTP response status + body are captured in output for later steps to reference (see step outputs below).
    • Delay{ delay_seconds }. Pauses the workflow for that many seconds before continuing. Inline up to 20s; longer pauses park the run and resume from the cron (see delays below).
    • Branch{ condition, then: [steps], else: [steps] }. The condition shape is the same as the workflow-level condition. Run the then list if it passes, else if it doesn't. Delays inside a branch are not supported yet — put them at the top level of the workflow.

Branching

A Branch step lets one workflow handle two paths from the same trigger.

Trigger: contact.created
  → Branch: if entity.email is_set
       then:
         → Send email "Welcome…"
         → Apply tag "has-email"
       else:
         → Send SMS "Hey, can I grab your email?"
         → Create task "Follow up — need email"

The condition uses the same field paths and operators as the workflow-level condition (equals, contains, is_set, includes_tag, etc.). You can nest a Branch inside another Branch.

Step outputs (referencing what an earlier step did)

Every step records an output blob — usually the ids and fields of the entity it touched. Later steps can reference those values via:

{{steps.<step_id>.output.<key>}}

For example, a Send SMS step exposes output.message_id, output.to, output.status. So you can chain:

  1. Send SMS (id = s_sms_intro) — body "Hey {{first_name}}, …"
  2. Create note — body "Texted them at {{steps.s_sms_intro.output.to}} (msg {{steps.s_sms_intro.output.message_id}})"

The note records exactly which message was sent. Same pattern works for fire_webhook (output.status_code, output.response_body), create_task (output.task_id), apply_tag (output.tag_id), and so on.

Open the variable picker ({ } button next to any template-aware field in the editor) to browse what each earlier step exposed without having to remember the key names.

Scheduled triggers

Pick "On a schedule" and choose a recurrence:

The editor shows a "Next runs: …" preview as you set things up — you see the next 4 fire times before you save. Timezone defaults to your browser's IANA tz so "daily 9 AM" means 9 AM where you are, not UTC.

Scheduled workflows fire WITHOUT an entity in context — so steps like "apply tag" or "update field" will skip (no entity to apply to). The actions that work for scheduled are:

Template variables

Action configs that take text can reference fields from the trigger entity:

Unknown variables render as empty strings so a missing first_name doesn't break a bulk-run on 50 contacts.

Example: auto-tag new leads from Facebook

  1. Name: "Auto-tag FB leads"
  2. Trigger: When something happens → contact.created
  3. Condition: entity.source equals facebook-lead-ads (only when the inbound webhook tagged the source)
  4. Actions:
    • Apply tag → lead
    • Create task → "Call {{first_name}} within 24h" — due_in_days = 1
  5. Save → it's live. Next FB lead that comes in via your inbound webhook gets tagged AND becomes a follow-up task automatically.

Example: auto-note when a deal closes won

  1. Name: "Won deal logger"
  2. Trigger: deal.stage_changed
  3. Condition: trigger.payload.is_won equals true
  4. Action: Create note → "🎉 Closed-won on {{entity.title}}. {{trigger.payload.to_stage_name}}."
  5. Now every won deal auto-drops a note on the deal record. Audit trail forever.

Run history

Click any workflow row to expand its run history — every time it fired, what status (completed / skipped / failed), which step did what, and any error messages. Useful for debugging when a workflow doesn't seem to do what you expect.

Reliability

Currently supported triggers

These entity events fire workflows today:

For comms events (SMS / email / call / voicemail) the engine auto-resolves the related contact into the entity context — so you can write conditions like "if contact has tag 'VIP' AND it's an inbound SMS" without any extra wiring.

Delays — inline vs pause-and-resume

Delays under 20 seconds run inline — the workflow sleeps and continues in the same execution. Delays longer than 20 seconds park the run with status='paused' and a resume_at timestamp; a per-minute cron picks it back up when the time arrives and continues from the step after the delay.

So a workflow like:

Send SMS "Hey {{first_name}}!"
Delay 60 minutes
Send SMS "Following up — got time for a quick call?"

…runs the first SMS immediately, parks the run for an hour, then wakes up and sends the second SMS. The pause happens server-side; you don't have to keep anything open.

The shortest paused delay rounds up to ~1 minute (cron granularity). Multi-day delays work fine — "send a check-in two weeks after they sign up" is a single workflow with one Delay step.

Delays inside a Branch's then / else list are NOT supported yet — keep delays at the top level of the workflow.

Limits

Tips: editing + duplicating

Each workflow row has:

Inside the editor, the step list has up/down arrows next to each step so you can reorder without rebuilding.

Editing a running workflow

Edits take effect immediately for the next run. In-flight runs that triggered before you saved keep using the previous definition. Run history records the state at fire time, so you'll always be able to see what definition each run used.