Skip to main contentSkip to Content
Automations & Jobs

Automations and background jobs

An automation is a campaign of type AUTOMATION or TRANSACTIONAL with a workflow graph: a trigger, then steps joined by edges. This page explains how a contact moves through it and how the work is scheduled. For the create/update endpoints see Campaigns.

Triggers

TriggerStarts whenNotes
EVENTAn event of the configured eventType is logged for a contactFires every time the event arrives. Optional filter on an event field.
SEGMENT_ENTRYA contact enters the configured segmentA contact already in the segment does not enter it again. includeExisting: true on the trigger enrols current members once, when the automation is activated.
SEGMENT_EXITA contact leaves a dynamic segment
SCHEDULEDA cron expression firesEnrols the members of the configured segment at that moment.
MANUALPOST …/campaigns/{id}/enroll or the Enroll panel

Static segments change through the API, the ingest endpoint, forms and bulk actions. Dynamic segments are re-evaluated for a contact whenever one of their events arrives, and recomputed in full every hour; a contact that newly matches “enters” the segment either way.

Steps

StepWhat it does
SEND_EMAIL, SEND_PUSH, SEND_TELEGRAM, SEND_SMS, SEND_WHATSAPPSends a template through that channel. The template’s language fallback and A/B variants apply.
WAITPauses for a fixed time. There is no upper limit: the wait is stored on the enrollment and resumed by the scheduler.
CONDITIONBranches on rules over the contact (conditionMode: "rules", the same rule language as segments, including daysSince.<date field>) or on membership of a segment (conditionMode: "segment"). Edges labelled true/false (shown as Yes/No) pick the branch; a branch with no edge ends the path.
DECISIONWaits for something to happen, up to a time limit: an event (eventType, optional filter) or a previous step’s outcome such as “opened” or “clicked” (stepOutcome). Takes the true edge as soon as it happens and the false edge when timeoutMs passes.
AB_SPLITAssigns the contact to one of several branches by weight; see A/B tests.
ADD_TAG, REMOVE_TAG, SET_FIELD, UPDATE_SCORE, ADD_TO_SEGMENT, REMOVE_FROM_SEGMENT, ADD_DNC, REMOVE_DNC, CHANGE_STAGE, UPDATE_COMPANYChange the contact.
SEND_WEBHOOKCalls your URL with the contact.
SHOW_WIDGETShows an on-site widget on the next visit.
NOTIFY_INBOX, CREATE_TASK, GOTOCreate follow-up work, or jump to another step.

A common shape for a “did they do it in time?” automation:

EVENT PAYMENT_OPEN → DECISION (wait for PAYMENT_COMPLETED, timeoutMs 1 800 000) ├─ true → (end) └─ false → SEND_EMAIL "Payment reminder"

Enrollment rules

  • A contact has at most one enrollment per automation. With reEntryAllowed: false (the default) a contact who completed it never enters again; with true they can re-enter after completing, no sooner than reEntryCooldownMs after the previous completion.
  • A contact whose automation is paused (automationPaused on the contact) is skipped by every automation.
  • startAt / endAt and entryLimit on the campaign gate new enrollments.
  • Only an active automation enrols contacts or resumes waiting ones. Pausing it freezes every enrollment where it is; activating it again resumes them.

Pausing and resetting the queue

Pausing a campaign parks every contact where they are: waits do not resume and no new contacts enter. Resume picks them up again from the same step.

While a campaign is paused, Reset queue (next to Resume) removes every contact still in the flow instead. Their enrollment ends as EXITED with the reason Removed by a queue reset, and enrollment work still queued for the campaign is dropped. After you resume, only contacts that hit the trigger from then on enter; removed contacts are not re-enrolled from the trigger’s segment on resume, and come back only if they fire the trigger again and the campaign allows re-entry. The API is GET (counts) and DELETE (reset) on /api/workspaces/{workspaceId}/campaigns/{campaignId}/queue; the reset is refused while the campaign is active.

Marketing policies and transactional sends

Before a message is sent, the sender checks the contact’s consent for the channel, the campaign’s frequency cap and quiet hours, and the workspace frequency cap. Transactional sends (a password reset, a receipt) bypass these checks: they go to anyone with an address, at any hour, regardless of opt-outs. Suppression lists apply to both.

For an email sent from a template, the template’s email type decides: a marketing template is also sent at most once to each contact, while a transactional one can be sent again every time the step runs. The Send Email step shows this type: with a template you change it on the template (it applies to every step that sends it); without a template (inline subject and body) you set it on the step, or leave it on Same as the campaign type. On other channels, a campaign of type TRANSACTIONAL makes the send transactional.

When a marketing email is not sent because of consent, the step is logged as Skipped with the reason under the contact in the campaign’s execution logs, for example Not sent: contact opted out of marketing emails.

How work is scheduled

Nothing in CampaignOS depends on an external scheduler. Work that cannot finish inside a request is written to the Job table and drained by a cron endpoint:

  • GET /api/cron/tick is called every minute by the platform cron (vercel.json), authenticated with Authorization: Bearer <CRON_SECRET>. Each tick runs for up to 12 minutes within a function limit of 800 seconds; ticks overlap safely because every job is claimed atomically and holds a 15-minute lease.
  • Each tick first resumes enrollments whose wait has ended, then takes jobs in order of creation. A job that fails is retried up to 5 times with backoff; a job that outlives its lease is reclaimed.
  • Bulk work is split into chunks of 200 contacts: enrolling a segment, a broadcast, a scheduled trigger.
Job typeScheduled byDoes
campaign.enrollBroadcasts, activation with includeExistingEnrols a chunk of contacts
campaign.triggerSegment entry of more than ten contactsStarts the automation for a chunk
campaign.scheduled-runA SCHEDULED trigger firingEnrols the segment’s members
campaign.broadcast-summaryThe last enroll chunk of a broadcastNotifies that the broadcast is out
segment.recomputeHourly, and after a segment is savedRecomputes a dynamic segment and starts entry/exit automations for contacts that changed
score.decayDailyApplies score decay rules
report.run-schedulesHourlyRuns due report schedules
task.notify-dueHourlyNotifies assignees of due tasks
periodic.hourly, periodic.dailyThe tick itselfEnqueue the jobs above

GET /api/workspaces/{workspaceId}/jobs lists the workspace’s recent jobs with their status, attempts and errors; Settings → Background jobs shows the same, with the time of the last tick. A tick that has not run for several minutes means the cron is not reaching the endpoint or CRON_SECRET is wrong.

Testing an automation without sending

  • POST /api/workspaces/{workspaceId}/campaigns/{campaignId}/dry-run with { "contactId": "…" } walks the graph for that contact and reports the branch every condition would take and every message that would be sent, without sending or enrolling.
  • POST …/templates/{templateId}/send-test sends one template to an address of your choice.
Last updated on