Automations
Create and manage event-driven transactional automations with a Bearer stx_… API key (or dashboard session).
Base URL: https://api.supersendtx.com
You can also build flows visually in the dashboard canvas under Automations.
Authentication
Authorization: Bearer stx_…Create an automation
POST /automations
Creates a draft. Activate it before events will match.
{
"name": "Welcome email",
"trigger": {
"type": "event",
"event": "user.created"
},
"steps": [
{
"type": "send_email",
"email": {
"from": "[email protected]",
"to": "{{event.email}}",
"subject": "Welcome",
"html": "<p>Hi {{event.data.name}}</p>"
}
}
]
}Examples below are generated from src/lib/dx/snippets.ts (same source as the dashboard).
curl
curl -X POST https://api.supersendtx.com/automations \
-H "Authorization: Bearer stx_…" \
-H "Content-Type: application/json" \
-d '{"name":"Welcome email","trigger":{"type":"event","event":"user.created"},"steps":[{"type":"send_email","email":{"from":"[email protected]","to":"{{event.email}}","subject":"Welcome","html":"<p>Hi {{event.data.name}}</p>"}}]}'npm
npm install supersendtx
import { SuperSendTX } from 'supersendtx'
const client = new SuperSendTX('stx_…')
const { automation } = await client.automations.create({
name: 'Welcome email',
trigger: { type: 'event', event: 'user.created' },
steps: [
{
type: 'send_email',
email: {
from: '[email protected]',
to: '{{event.email}}',
subject: 'Welcome',
html: '<p>Hi {{event.data.name}}</p>',
},
},
],
})
await client.automations.activate(automation.id)CLI
SUPERSENDTX_API_KEY=stx_… npx -y --package=supersendtx-cli -- supersendtx automations create --file ./welcome.json
SUPERSENDTX_API_KEY=stx_… npx -y --package=supersendtx-cli -- supersendtx automations activate --id "$AUTOMATION_ID"welcome.json should match the request body above.
Endpoints
| Method | Path | Notes |
|---|---|---|
GET |
/automations |
List |
POST |
/automations |
Create draft |
GET |
/automations/{id} |
Get |
PATCH |
/automations/{id} |
Update name/trigger/steps |
DELETE |
/automations/{id} |
Delete |
POST |
/automations/{id} |
{ "action": "activate" | "pause" } |
GET |
/automations/runs |
Optional ?automation_id= |
GET |
/automations/runs/{id} |
Run detail |
POST |
/automations/runs/{id} |
{ "action": "cancel" | "retry" } |
Step types
| Type | Purpose |
|---|---|
send_email |
Send via the same transactional send path as POST /emails — inline subject/html or a published email.template |
send_sms |
Send SMS via Twilio — requires a connected integration and sms.to + sms.body |
delay |
Wait seconds before the next step |
condition |
Continue only if true; false stops the run (not if/else branching) |
wait_for_event |
Pause until another event arrives (optional timeout_seconds; on_timeout continue or fail, default fail) |
Trigger options:
| Key | Meaning |
|---|---|
cancel_on_events |
Array of event names that cancel queued, running, or waiting runs for the same recipient when fired. |
once_per_contact |
true to enrol each person at most once, ever (matched on the event's email or user_id). A second matching event is skipped and reported in the POST /events response as enrollment_skips with reason: "once_per_contact". |
cooldown_days |
Integer 1–3650. Skip enrolment when the same person already had a run on this automation within the last N days (reason: "cooldown"). |
Dashboard dry-runs and test sends are never deduplicated, and a retry of a failed run bypasses both rules. The check and the enrolment run inside one transaction under an advisory lock keyed on the automation and the person, so two copies of the same event arriving at once (a retried webhook, two workers) still enrol once.
Placeholders in email and SMS steps, in any string:
| Placeholder | Resolves to |
|---|---|
{{event.email}}, {{event.user_id}}, {{event.data.foo}} |
The triggering event. |
{{contact.first_name}}, {{contact.last_name}}, {{contact.email}}, {{contact.status}}, {{contact.attributes.plan}} |
The contact matched by the event's email or user_id (→ external_id), loaded when the step runs. |
{{contact_at_enrollment.attributes.plan}} |
The same snapshot, frozen when the run was created. |
{{unsubscribe_url}} |
The managed unsubscribe link (product / newsletter category). |
A Liquid-style default is accepted: {{contact.first_name | default:"there"}}. An unresolved placeholder renders as empty text, never as literal braces.
Conditions on the contact: a condition step whose path starts with contact. reads the contact fresh at step time, so a 14-day delay followed by { "type": "condition", "path": "contact.attributes.subscribed", "operator": "not_equals", "value": true } means "still not subscribed when the step runs". On contact. and contact_at_enrollment. paths, equals / not_equals compare as text, the same way segments do: booleans and numbers are stringified, so "value": true and "value": "true" both match an attribute stored as true or "true" (attributes are JSON written by whichever integration last touched the contact, and the stored type is not visible to the author). A missing contact or attribute equals only null. Event paths (event.data.*) stay strict, because the payload is typed by your own code. contact_at_enrollment. paths read the frozen snapshot.
Category and unsubscribe: inline send_email steps default to "category": "product" — the send honours product opt-outs, carries List-Unsubscribe headers, and replaces {{unsubscribe_url}} in html and text with the managed link. The placeholder is replaced, never appended, so include it; the API returns a warnings array when a product/newsletter step has none. Set "category": "transactional" only for genuinely transactional mail (receipts, OTPs). Template-backed steps inherit the template's category unless category is set on the step. A recipient who opted out of the category completes the run with a category_opt_out step output instead of failing it.
{
"name": "Signup → still free after 14 days",
"trigger": { "type": "event", "event": "signup", "cancel_on_events": ["subscription_checkout"], "once_per_contact": true },
"steps": [
{ "type": "delay", "seconds": 1209600 },
{ "type": "condition", "path": "contact.attributes.subscribed", "operator": "not_equals", "value": true },
{
"type": "send_email",
"email": {
"from": "[email protected]",
"to": "{{event.email}}",
"subject": "Two weeks in",
"html": "<p>Hey {{contact.first_name | default:\"there\"}},</p><p>…</p><p><a href=\"{{unsubscribe_url}}\">Unsubscribe</a></p>",
"text": "Hey {{contact.first_name | default:\"there\"}},\n\n…\n\nUnsubscribe: {{unsubscribe_url}}"
}
}
]
}Conversions: a conversion whose last touch inside the goal window was a send from an automation is credited to it (attributed_automation_id / attributed_run_id on the conversion). GET /automations/{id} returns conversions: { total, revenue_cents, mix }; GET /conversions?automation_id= lists them.
SMS example (send_sms)
Connect Twilio first (integrations twilio connect), then create an automation:
{
"name": "Auth OTP (SMS)",
"trigger": { "type": "event", "event": "auth.code_requested" },
"steps": [
{
"type": "send_sms",
"sms": {
"to": "{{event.data.phone}}",
"body": "Your code is {{event.data.code}}. Expires in 10 min."
}
}
]
}SUPERSENDTX_API_KEY=stx_… npx -y --package=supersendtx-cli -- supersendtx integrations twilio connect \
--account-sid AC… --auth-token … --from +15551234567
SUPERSENDTX_API_KEY=stx_… npx -y --package=supersendtx-cli -- supersendtx automations create --file ./otp-sms.json
SUPERSENDTX_API_KEY=stx_… npx -y --package=supersendtx-cli -- supersendtx automations activate --id "$AUTOMATION_ID"Template example (mutually exclusive with inline subject / html / text):
{
"type": "send_email",
"email": {
"from": "[email protected]",
"to": "{{event.email}}",
"template": {
"id": "welcome",
"variables": { "name": "{{event.data.name}}" }
}
}
}Trigger the flow
After activate, send events with POST /events or:
supersendtx events send --name user.created --email [email protected] --data '{"name":"Ada"}'