Intro
Endpoint: https://api.omnisend.com/api/automations
Build event-driven workflows that send messages, apply tags, and branch logic based on contact behavior. Automations are created disabled and must be explicitly enabled to start processing trigger events.
Blocks
Workflows are composed of ordered blocks. Each block has a type discriminator and exactly one type-specific field populated.
| Block type | Description |
|---|---|
| delay | Pause the workflow before continuing to the next block |
| action | Execute an operation (send a message, manage tags) |
| split | Branch contacts based on event, contact, or message engagement conditions |
| abTesting | Randomly split contacts between two variant paths by percentage |
Action types
| Type | Description |
|---|---|
| sendEmail | Send an email using a template (Email Templates API) |
| sendSms | Send an SMS or MMS message |
| sendPush | Send a web push notification |
| sendWebhook | Send an HTTP POST to an external URL |
| addTag | Add a tag to the contact |
| removeTag | Remove a tag from the contact |
Email, SMS, and push blocks have an isSkipAllowed flag. When true, contacts who are ineligible to receive the channel's message skip the block and continue. When false, they exit the workflow. This flag does not apply to webhooks. Email and SMS default to true.
Push blocks: For
sendPush, omittingisSkipAllowedwhen creating an automation (POST) or replacing its blocks (PUT) defaults tofalse. Set"isSkipAllowed": trueexplicitly to let an ineligible contact skip the push block and continue. Omitting this field in a PATCH leaves its saved value unchanged.
Action configuration is nested under its matching type, such as action.sendPush for "type": "sendPush".
Delay modes
| Mode | Description |
|---|---|
| duration | Wait for a specified amount of time (duration.amount + duration.units: m/h/d/w/M) |
| immediate | No wait — proceed to the next block immediately |
| specificTime | Wait until a time of day in the brand's timezone (time in HH:MM 24h format) |
All delay modes support optional allowedWeekdays to restrict resolution to specific days.
A delay cannot be the last block in the workflow. A branch may end with a delay only when a shared downstream sequence follows the split or A/B test.
Split filters
A split evaluates a filterGroup of mixed-type filters:
| Filter type | field | operator examples | value |
|---|---|---|---|
| event | Event property path | eq, neq, gt, contains, exists, in, … | Property value |
| contact | Contact attribute (tag, country, segmentID, …) | Same set as audience contact filters | Attribute value |
| message | Always "blockID" | openedEmail, clickedEmail, clickedSms, openedPush, clickedPush | ID of a send block above this split |
Set filterGroup.logicalOperator to and or or. Event filters use the triggering event's properties; contact filters check current contact attributes.
Clicked message filters may include urlMatch with an operator of any, is, contains, startsWith, or endsWith. Supply a URL value unless the operator is any. Omit urlMatch for opened-message, event, and contact filters.
Note: Sibling blocks placed after a split run for contacts from both branches — the branches reconverge on that shared downstream sequence. Branches may contain different numbers of blocks, and a branch may end with a delay when such a shared continuation exists.
Triggers
trigger.condition.event specifies the event that enrolls contacts. Most events use standard Events API names (e.g. "placed order", "started checkout") and may require origin. Built-in events ("birthday", "product back in stock") do not use origin.
Use the event metadata endpoint to look up event names, origins, and property paths available for the brand. When an event has multiple origins, specify one; a single origin is selected automatically. Omit origin for "subscribed to marketing".
To enroll contacts when they enter a segment, use "event": "entered segment", "origin": "omnisend", and a trigger event filter on segment_id. A contact audience filter on segmentID only checks membership after a trigger fires.
Optional trigger.condition.filterGroups narrow enrollment by event properties:
- Most events accept at most one filter group.
"subscribed to marketing"accepts up to 10 groups, combined with AND logic."product back in stock"does not accept filter groups. This trigger is supported for Shopify, BigCommerce, and WooCommerce stores."birthday"has a special filter format; see the trigger condition examples.
Event filter values
Within an event filter group, set logicalOperator to and or or. Available comparison operators depend on the event property's type. Numeric comparisons (gt, gte, lt, lte) require numeric properties and JSON numbers: use "value": 50, not "value": "50". String properties require string values.
Omit value for exists and notExists. For string-list properties, in and notIn test membership using a single string value, not an array. These rules also apply to event filters in splits and exit conditions.
Audience filter group
trigger.audienceFilterGroup restricts which contacts enter the workflow when the trigger fires. It does not trigger the automation — it only gates enrollment. Set logicalOperator to and or or and supply at least one filter.
All contact filter values are non-empty strings. The same field and operator rules apply to contact filters in split blocks:
| Field | Operators | Value |
|---|---|---|
| segmentID | eq, neq | Segment ID |
| tag | eq, neq | Tag name |
| dateAdded | eq, neq, gt, lt | Date in YYYY-MM-DD format |
| firstName, lastName, state, city, postalCode | eq, neq, contains, notContains | Attribute value |
| country | eq, neq, contains, notContains | ISO-2 country code, such as US |
| gender | eq | m or f |
Inactivity settings
trigger.inactivitySettings.duration delays the workflow start until the contact has been inactive (no new triggering events) for the specified period. Each re-trigger resets the timer.
Use a positive amount and units m, h, d, or w.
Note: Use inactivity settings for abandoned-flow patterns — e.g. 30 min after
"added product to cart"with no follow-up cart event.
Exit Conditions
exitConditions remove contacts from the automation when a matching event occurs. Multiple exit conditions are evaluated with OR logic. Each condition specifies an event (and optional origin and filterGroup).
Note: Built-in trigger events (
"birthday","product back in stock") cannot be used as exit conditions.
Settings
Sending thresholds
Control which contacts receive messages at send blocks based on subscription status.
| Level | Description |
|---|---|
| subscribed | Only contacts who opted in to marketing (default) |
| nonSubscribed | Subscribed + non-subscribed, excluding those who explicitly unsubscribed |
| all | All contacts regardless of subscription status (transactional use) |
Set per channel (email, sms). Push is always restricted to subscribed contacts.
Note: US SMS is always restricted to subscribed contacts regardless of the
smsthreshold (CTIA regulation).
Frequency limiter
Restricts how often the same contact can re-enter the automation.
| Mode | Description |
|---|---|
| once | Contact enters the automation only once per lifetime |
| interval | Contact can re-enter after a specified duration since last entry |
For interval, supply a positive duration.amount with units h, d, or w (maximum 8760 hours, 365 days, or 52 weeks). Omit duration for once. Send null to clear an existing limiter.
Overlap limiter
Prevents a contact from entering when they are already in (or recently completed) other specified automations.
| Mode | Description |
|---|---|
| currentlyIn | Skip if the contact is currently active in any listed automation |
| recentlyIn | Skip if the contact completed any listed automation within withinDays days (1–7) |
Supply a non-empty automationIDs list. withinDays is required for recentlyIn and must be omitted for currentlyIn. Send null to clear the limiter.
Note: Self-referencing (listing the automation's own ID in
automationIDs) is rejected with400.
Lifecycle: Enable / Disable
Automations are always created disabled. The isEnabled field is read-only — use the dedicated enable/disable endpoints to change state.
Warning: Enabled automations cannot be patched or have blocks replaced — the API returns
409 Conflict. Disable the automation first, make changes, then re-enable.
Enable (POST /automations/{id}/enable): starts processing trigger events. enrollExisting optionally enrolls contacts who already qualify:
- Event-based triggers: contacts whose event occurred within the first delay block's time window are enrolled. The workflow must start with a delay block — otherwise
409 enroll-existing-not-applicable. "entered segment"trigger: all contacts currently in the trigger segments are enrolled. Only works on the first enable."birthday","product back in stock", and custom virtual events: not supported withenrollExisting=true; the API returns409 enroll-existing-not-applicable.
When enrollExisting is omitted or false, only future trigger events are processed. Re-enabling a segment-triggered workflow with enrollExisting=true returns 409 enroll-existing-not-applicable.
Disable (POST /automations/{id}/disable): stops accepting new trigger events. contactsInWorkflow controls in-flight contacts:
| Value | Description |
|---|---|
| keep | Contacts remain and continue through remaining blocks |
| exit | All contacts are immediately removed from the workflow |
Other Lifecycle Actions
| Action | Endpoint | Description |
|---|---|---|
| Copy | POST /automations/{id}/copy | Create a disabled copy. Optional name field; defaults to "Copy of: <original name>" |
Editing Automations
Use PATCH /automations/{id} to update a disabled automation. Omitted fields remain unchanged:
| Field | Update behavior |
|---|---|
| trigger | Update only the supplied sub-fields. Each supplied condition, audienceFilterGroup, or inactivitySettings replaces that sub-field in full. |
| exitConditions | Replace the entire list. Send null or [] to clear it. |
| blocks | Update existing blocks by id; do not add, remove, or reorder blocks. An empty array is rejected. |
| settings | Update only the supplied sub-fields. |
For a block PATCH, delay is replaced in full, including its mode-specific fields. split.filterGroup is also replaced in full; split branches cannot be changed through PATCH. For A/B tests, only aBlocksPercentage can be patched.
Use PUT /automations/{id}/blocks to replace the complete block tree, including branch structure:
- Identify existing blocks with
idand new blocks with a uniquetemporaryID. - Existing blocks omitted from the replacement tree are removed.
- Existing block and action types cannot be changed in place; use a new block to change types.
- For new email blocks,
templateIDis required. For existing email blocks, omittemplateID; it must not be provided.
Test Emails
Use POST /automations/{id}/blocks/{blockID}/test-email to send a test of an email block's saved content. Supply 1–5 valid email addresses:
{
"recipients": ["[email protected]"]
}A 204 No Content response means the request was accepted for asynchronous delivery. The block must be an email block with saved content. Sending can be rejected if all recipients are suppressed, email sending is disabled, the sender domain is unverified, or the billing plan does not allow sending.
UTM Tracking
UTM tags can be managed per email, SMS, or push block or read in aggregate. Webhook and tag actions do not support UTM tags.
| Endpoint | Description |
|---|---|
GET /automations/{id}/blocks/{blockID}/utm | Read UTM tags for a single block |
PUT /automations/{id}/blocks/{blockID}/utm | Set UTM tags for a single block |
GET /automations/{id}/utm | Read aggregated UTM tags for all email, SMS, and push blocks |
All three tag fields (source, medium, campaign) are replaced atomically on write. Send an empty string to clear a field — the next read returns its default value.
For a complete walkthrough, see How to configure UTM tags for automations.
Examples
For trigger condition and audience filter group examples, see the endpoint documentation:
- Create automation: Trigger condition examples — birthday, entered segment, event-based triggers with filter groups
- Create automation: Audience filter group examples — segment, tag, location, and date-based audience filters
- Patch automation: Audience filter group examples — replace or narrow audience filters on existing automations
Example: Create an automation with a welcome email
curl --request POST \
--url https://api.omnisend.com/api/automations \
--header 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
--header 'Omnisend-Version: 2026-03-15' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"name": "Welcome Series",
"trigger": {
"condition": {
"event": "subscribed to marketing"
}
},
"blocks": [
{
"temporaryID": "wait-1h",
"type": "delay",
"delay": {
"mode": "duration",
"duration": {
"amount": 1,
"units": "h"
}
}
},
{
"temporaryID": "welcome-email",
"type": "action",
"action": {
"type": "sendEmail",
"sendEmail": {
"subject": "Welcome to our store!",
"senderName": "My Store",
"preheader": "Thanks for subscribing",
"language": "en_US",
"templateID": "000000000000000000000001"
}
}
}
]
}'Creates a disabled automation that triggers on marketing subscription, waits 1 hour, then sends a welcome email.
Example: Enable an automation
curl --request POST \
--url https://api.omnisend.com/api/automations/000000000000000000000001/enable \
--header 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
--header 'Omnisend-Version: 2026-03-15' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"enrollExisting": false
}'Example: Disable an automation
curl --request POST \
--url https://api.omnisend.com/api/automations/000000000000000000000001/disable \
--header 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
--header 'Omnisend-Version: 2026-03-15' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"contactsInWorkflow": "keep"
}'Example: Replace automation blocks
curl --request PUT \
--url https://api.omnisend.com/api/automations/000000000000000000000001/blocks \
--header 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
--header 'Omnisend-Version: 2026-03-15' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"blocks": [
{
"temporaryID": "wait-30m",
"type": "delay",
"delay": {
"mode": "duration",
"duration": {
"amount": 30,
"units": "m"
}
}
},
{
"temporaryID": "tag-engaged",
"type": "action",
"action": {
"type": "addTag",
"addTag": {
"value": "engaged"
}
}
}
]
}'Replaces the entire block tree. Blocks with temporaryID are created as new; blocks with id update existing ones. Existing blocks not included are removed.
Note: The automation must be disabled before replacing blocks.
Example: Update UTM tags for a block
curl --request PUT \
--url https://api.omnisend.com/api/automations/000000000000000000000001/blocks/000000000000000000000002/utm \
--header 'Authorization: Omnisend-API-Key YOUR-API-KEY' \
--header 'Omnisend-Version: 2026-03-15' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"tags": {
"source": "omnisend",
"medium": "email",
"campaign": "welcome-series"
}
}'