Skip to main content

Automations

Automations are workflow-driven business processes that execute automatically in response to triggers like entity events, HTTP webhooks, or scheduled cron jobs. They allow you to build complex business logic without writing backend code.

What are Automations?

Automations define sequences of actions that execute when triggered:

  • React to entity changes - When a customer is created, send a welcome email
  • Expose webhook endpoints - External systems can trigger workflows via HTTP
  • Schedule recurring tasks - Run nightly reports or monthly cleanups
  • Chain multiple actions - Query data → Transform → Send notification → Update records

When you define an automation, Stack9 automatically:

  • ✅ Registers the trigger (webhook, entity event, or schedule)
  • ✅ Executes actions in sequence
  • ✅ Handles errors and retries
  • ✅ Logs execution history
  • ✅ Manages async processing

Why Use Automations?

Traditional Approach

// Multiple files, scattered logic
app.post('/api/customer-created-webhook', async (req, res) => {
const customer = req.body;

await sendWelcomeEmail(customer);
await updateCRM(customer);
await notifySlack(customer);

res.json({ success: true });
});

Stack9 Automations

{
"key": "when_customer_created",
"triggerType": "afterCreate",
"entityKey": "customer",
"actions": [
{ "actionTypeKey": "send_welcome_email" },
{ "actionTypeKey": "update_crm" },
{ "actionTypeKey": "notify_slack" }
]
}

Benefits:

  • Centralized: All workflows in one place
  • Visual: See the entire workflow at a glance
  • Reusable: Use the same actions across automations
  • Testable: Easy to test each action
  • Maintainable: Change once, update everywhere

Automation File Structure

Automations are defined in src/automations/{automation_name}.json:

{
"key": "unique_automation_key",
"name": "Human Readable Name",
"entityKey": "entity_key",
"app": "app_key",
"triggerType": "webhook | afterCreate | afterUpdate | afterDelete | scheduled",
"triggerParams": {
// Trigger-specific configuration
},
"actions": [
{
"name": "Action Name",
"key": "action_key",
"actionTypeKey": "action_type_key",
"params": {
// Action parameters
}
}
],
"conditionalActions": [
// Optional conditional logic
]
}

Trigger Types

1. Webhook Triggers

Expose HTTP endpoints that external systems can call.

{
"key": "webhook_get_customer_dashboard",
"name": "Get Customer Dashboard Data",
"entityKey": "customer",
"app": "crm",
"triggerType": "webhook",
"triggerParams": {
"method": "get",
"path": "/get-customer-dashboard-data"
},
"actions": [
{
"name": "Fetch dashboard data",
"key": "fetch_data",
"actionTypeKey": "get_customer_dashboard_data",
"params": {
"token": "{{trigger.query.token}}"
}
}
]
}

Access the webhook:

GET /api/customer/webhook/get-customer-dashboard-data?token=abc123

HTTP Methods Supported:

  • get - Read-only operations
  • post - Create or trigger operations
  • put - Update operations
  • delete - Delete operations

2. Entity Lifecycle Triggers

Execute automatically when entities are created, updated, or deleted.

{
"key": "when_customer_created",
"name": "When Customer Created",
"app": "crm",
"entityKey": "customer",
"triggerType": "afterCreate",
"actions": [
{
"name": "Sync to search index",
"key": "sync_customer",
"actionTypeKey": "sync_customer_data",
"params": {
"customerId": "{{trigger.entity.id}}"
}
}
]
}

Available Lifecycle Triggers:

  • afterCreate - After entity is created
  • afterUpdate - After entity is updated
  • afterDelete - After entity is deleted (soft delete)

Trigger Context:

{
trigger: {
entity: { /* new entity data */ },
oldEntity: { /* previous entity data (for updates) */ },
operation: 'create | update | delete'
}
}

3. Workflow Triggers

Execute when an entity moves between workflow states.

{
"key": "after_order_approved",
"name": "After Order Approved",
"entityKey": "sales_order",
"triggerType": "afterWorkflowMove",
"triggerParams": {
"fromStep": ["pending_approval"],
"toStep": "approved",
"actionKey": "approve"
},
"actions": [
{
"name": "Send confirmation email",
"actionTypeKey": "send_order_confirmation"
},
{
"name": "Update inventory",
"actionTypeKey": "reserve_inventory"
}
]
}

4. Scheduled Triggers (Cron)

Run automations on a schedule.

{
"key": "daily_customer_sync",
"name": "Daily Customer Sync",
"triggerType": "scheduled",
"triggerParams": {
"cron": "0 2 * * *", // Every day at 2 AM
"timezone": "Australia/Brisbane"
},
"actions": [
{
"name": "Sync all customers",
"actionTypeKey": "sync_all_customers"
}
]
}

Cron Format: minute hour day month weekday

Common patterns:

  • 0 * * * * - Every hour
  • 0 0 * * * - Every day at midnight
  • 0 0 * * 0 - Every Sunday at midnight
  • */15 * * * * - Every 15 minutes
  • 0 9-17 * * 1-5 - Weekdays 9 AM to 5 PM

Actions

Actions are the individual steps in an automation workflow.

Action Structure

{
"name": "Human readable name",
"key": "unique_action_key",
"actionTypeKey": "predefined_action_type",
"params": {
"param1": "{{trigger.entity.field}}",
"param2": "static value"
}
}

Common Action Types

run_query - Execute a query

{
"name": "Fetch customer data",
"actionTypeKey": "run_query",
"params": {
"queryLibraryKey": "getcustomer",
"queryParams": {
"id": "{{trigger.entity.customer_id}}"
}
}
}

send_email - Send email notification

{
"name": "Send welcome email",
"actionTypeKey": "send_email",
"params": {
"to": "{{trigger.entity.email_address}}",
"template": "welcome_email",
"data": {
"name": "{{trigger.entity.name}}",
"crn": "{{trigger.entity.crn}}"
}
}
}

update_entity - Update a record

{
"name": "Mark as processed",
"actionTypeKey": "update_entity",
"params": {
"entityKey": "sales_order",
"entityId": "{{trigger.entity.id}}",
"data": {
"processed": true,
"processed_at": "{{now}}"
}
}
}

add_message_queue - Queue for async processing

{
"name": "Queue for processing",
"actionTypeKey": "add_message_queue",
"params": {
"queue": "process_large_file",
"body": {
"fileId": "{{trigger.entity.id}}"
}
}
}

Parameter Templates

Use double curly braces to inject dynamic values:

{
"params": {
"customerId": "{{trigger.entity.id}}",
"customerName": "{{trigger.entity.name}}",
"oldStatus": "{{trigger.oldEntity.status}}",
"queryParam": "{{trigger.query.token}}",
"currentTime": "{{now}}",
"userId": "{{user.id}}"
}
}

Available Template Variables:

  • {{trigger.entity.*}} - New entity data
  • {{trigger.oldEntity.*}} - Previous entity data
  • {{trigger.query.*}} - Query parameters (for webhooks)
  • {{trigger.body.*}} - Request body (for webhooks)
  • {{trigger.actionKey}} - Workflow action key
  • {{user.id}} - Current user ID
  • {{user.email}} - Current user email
  • {{now}} - Current timestamp

Conditional Actions

Execute actions only when conditions are met.

{
"conditionalActions": [
{
"condition": {
"rules": [
{
"field": "{{trigger.entity.total_amount}}",
"operator": "greaterThan",
"value": 1000
}
],
"combinator": "and"
},
"actions": [
{
"name": "Send high-value alert",
"actionTypeKey": "send_alert",
"params": {
"message": "High value order: ${{trigger.entity.total_amount}}"
}
}
]
},
{
"condition": {
"rules": [
{
"field": "{{trigger.actionKey}}",
"operator": "equals",
"value": "sendForValidation"
}
]
},
"actions": [
{
"name": "Execute validation",
"actionTypeKey": "handle_validation_step"
}
]
}
]
}

Available Operators:

  • equals - Exact match
  • notEquals - Not equal
  • greaterThan - Numeric greater than
  • lessThan - Numeric less than
  • contains - String contains
  • in - Value in array
  • notIn - Value not in array

Combinators:

  • and - All rules must match
  • or - At least one rule must match

Real-World Examples

Example 1: Sync Customer to Search Index

{
"key": "when_customer_created",
"name": "When Customer Created",
"app": "crm",
"entityKey": "customer",
"triggerType": "afterCreate",
"actions": [
{
"name": "Sync customer to OpenSearch",
"key": "sync_customer",
"actionTypeKey": "sync_customer_data",
"params": {
"customerId": "{{trigger.entity.id}}"
}
}
]
}

Example 2: Webhook for Dashboard Data

{
"key": "webhook_get_customer_dashboard",
"name": "Get Customer Dashboard Data",
"entityKey": "customer",
"app": "crm",
"triggerType": "webhook",
"triggerParams": {
"method": "get",
"path": "/get-customer-dashboard-data"
},
"actions": [
{
"name": "Fetch dashboard data",
"key": "fetch_data",
"actionTypeKey": "get_customer_dashboard_data",
"params": {
"token": "{{trigger.query.token}}"
}
}
]
}

Example 3: Multi-Step Order Processing

{
"key": "process_new_order",
"name": "Process New Order",
"entityKey": "sales_order",
"triggerType": "afterCreate",
"actions": [
{
"name": "Validate inventory",
"actionTypeKey": "check_inventory",
"params": {
"orderId": "{{trigger.entity.id}}"
}
},
{
"name": "Calculate taxes",
"actionTypeKey": "calculate_order_tax"
},
{
"name": "Reserve inventory",
"actionTypeKey": "reserve_stock"
},
{
"name": "Send confirmation email",
"actionTypeKey": "send_order_confirmation"
}
],
"conditionalActions": [
{
"condition": {
"rules": [
{
"field": "{{trigger.entity.total_amount}}",
"operator": "greaterThan",
"value": 500
}
]
},
"actions": [
{
"name": "Notify manager",
"actionTypeKey": "send_manager_notification"
}
]
}
]
}

Calling Webhooks

From TypeScript/JavaScript

import { ApiEntityService } from '@april9/stack9-sdk';

// POST webhook
await apiService.postWebhook('customer', 'process-payment', {
orderId: 12345,
amount: 100.50
});

// GET webhook
const result = await apiService.getWebhook('customer', 'get-dashboard-data', {
token: 'abc123'
});

From External Systems (cURL)

# POST webhook
curl -X POST https://your-instance.stack9.com/api/customer/webhook/process-payment \
-H "Content-Type: application/json" \
-d '{
"orderId": 12345,
"amount": 100.50
}'

# GET webhook
curl https://your-instance.stack9.com/api/customer/webhook/get-dashboard-data?token=abc123

Best Practices

1. Name Automations Descriptively

{
"key": "when_customer_created", // ✅ Clear purpose
"name": "When Customer Created"

"key": "automation_1", // ❌ Unclear
"name": "Automation 1"
}

2. Keep Actions Focused

// ✅ Good - Each action does one thing
{
"actions": [
{ "name": "Validate order", "actionTypeKey": "validate_order" },
{ "name": "Send email", "actionTypeKey": "send_email" },
{ "name": "Update inventory", "actionTypeKey": "update_inventory" }
]
}

// ❌ Bad - One action does everything
{
"actions": [
{ "name": "Do everything", "actionTypeKey": "process_entire_order" }
]
}

3. Use Conditional Logic Wisely

// ✅ Good - Simple, clear conditions
{
"condition": {
"rules": [
{ "field": "{{trigger.entity.status}}", "operator": "equals", "value": "approved" }
]
}
}

// ❌ Bad - Overly complex conditions
{
"condition": {
"rules": [
// 10 nested conditions...
]
}
}

4. Handle Errors Gracefully

Configure retries for critical operations:

{
"name": "Send payment",
"actionTypeKey": "process_payment",
"retry": {
"maxAttempts": 3,
"backoff": "exponential"
}
}

5. Log Important Events

{
"name": "Log processing start",
"actionTypeKey": "log_event",
"params": {
"level": "info",
"message": "Processing order {{trigger.entity.id}}"
}
}

Common Patterns

Email Notification on Status Change

{
"key": "notify_on_status_change",
"triggerType": "afterUpdate",
"entityKey": "sales_order",
"conditionalActions": [
{
"condition": {
"rules": [
{
"field": "{{trigger.entity.status}}",
"operator": "notEquals",
"value": "{{trigger.oldEntity.status}}"
}
]
},
"actions": [
{
"name": "Send status update email",
"actionTypeKey": "send_email",
"params": {
"to": "{{trigger.entity.customer.email}}",
"template": "status_changed",
"data": {
"oldStatus": "{{trigger.oldEntity.status}}",
"newStatus": "{{trigger.entity.status}}"
}
}
}
]
}
]
}

Nightly Batch Processing

{
"key": "nightly_report_generation",
"triggerType": "scheduled",
"triggerParams": {
"cron": "0 2 * * *"
},
"actions": [
{
"name": "Generate daily report",
"actionTypeKey": "generate_daily_sales_report"
},
{
"name": "Send to management",
"actionTypeKey": "email_report",
"params": {
"to": "management@company.com"
}
}
]
}

Async Heavy Processing

{
"key": "when_large_file_uploaded",
"triggerType": "afterCreate",
"entityKey": "file_import",
"actions": [
{
"name": "Queue for processing",
"actionTypeKey": "add_message_queue",
"params": {
"queue": "process_file_import",
"body": {
"fileId": "{{trigger.entity.id}}",
"fileName": "{{trigger.entity.file_name}}"
}
}
}
]
}

Troubleshooting

Automation Not Triggering

  1. Check entity key matches
  2. Verify trigger type is correct
  3. Check conditional logic
  4. Review application logs

Action Failing

  1. Check action parameters
  2. Verify action type exists
  3. Check user permissions
  4. Review error logs

Webhook Not Accessible

  1. Verify path is correct
  2. Check HTTP method
  3. Confirm entity key
  4. Test with cURL

Next Steps

Now that you understand Automations, learn about: