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 operationspost- Create or trigger operationsput- Update operationsdelete- 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 createdafterUpdate- After entity is updatedafterDelete- 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 hour0 0 * * *- Every day at midnight0 0 * * 0- Every Sunday at midnight*/15 * * * *- Every 15 minutes0 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 matchnotEquals- Not equalgreaterThan- Numeric greater thanlessThan- Numeric less thancontains- String containsin- Value in arraynotIn- Value not in array
Combinators:
and- All rules must matchor- 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
- Check entity key matches
- Verify trigger type is correct
- Check conditional logic
- Review application logs
Action Failing
- Check action parameters
- Verify action type exists
- Check user permissions
- Review error logs
Webhook Not Accessible
- Verify path is correct
- Check HTTP method
- Confirm entity key
- Test with cURL
Next Steps
Now that you understand Automations, learn about:
- Action Types - Create custom automation actions
- Entity Hooks - Add validation and business logic
- Connectors - Integrate external services
- How-To: Build Workflows