Scheduled Automation
This tutorial walks you through creating automated workflows in Maitento using Automated Actions. You will learn how to:
- Create an automated action that runs an app
- Add cron schedules for time-based execution
- Add message triggers for event-driven execution
- Configure actions to start apps or interactions
- Monitor execution history
Prerequisites
Before starting this tutorial, ensure you have:
- A Maitento account with API access
- An app or interaction already deployed (we will create a sample app)
- The Maitento Shell CLI installed (for shell examples)
- The Maitento SDK installed (for C# examples)
Scenario Overview
We will build a Daily Report Generator automation that:
- Runs automatically every day at 9:00 AM
- Also triggers whenever a data import app completes successfully
- Executes a report generation app with configurable parameters
- Tracks execution history for monitoring
Step 1: Create the Automated Action
First, we create the automated action that defines what to execute. The action will run a report-generator app with specific inputs.
Using the Shell CLI
# Create the automated action
action-create daily-report \
--namespace production \
--description "Generates daily summary reports" \
--actions '[
{
"name": "Generate Report",
"config": {
"$type": "app",
"version": {
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"version": 1
},
"inputs": [
{ "name": "reportType", "value": "daily-summary" },
{ "name": "format", "value": "pdf" },
{ "name": "sendEmail", "value": "true" }
]
}
}
]'
Using the REST API
POST /namespaces/production/automated-actions
Content-Type: application/json
Authorization: Bearer <your-token>
{
"name": "daily-report",
"description": "Generates daily summary reports",
"actions": [
{
"name": "Generate Report",
"config": {
"$type": "app",
"version": {
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"version": 1
},
"inputs": [
{ "name": "reportType", "value": "daily-summary" },
{ "name": "format", "value": "pdf" },
{ "name": "sendEmail", "value": "true" }
]
}
}
]
}
Response:
{
"id": "f7a8b9c0-1234-56d7-8901-234567890123",
"tenantId": "...",
"namespaceId": "...",
"name": "daily-report",
"description": "Generates daily summary reports",
"actions": [...],
"schedules": [],
"messageTriggers": [],
"nextScheduledRunAt": null,
"totalExecutions": 0,
"consecutiveFailures": 0,
"dateCreated": "2026-01-05T10:00:00Z",
"dateUpdated": "2026-01-05T10:00:00Z"
}
Using the C# SDK
using Maitento.Sdk;
using Maitento.Entities.AutomatedActions;
// Create the client
var client = MaitentoClient.Create("your-api-key");
// Define the app to run
var appAction = new AutomatedActionItem
{
Name = "Generate Report",
Config = new AppProcessStartConfig
{
Version = new VersionReference
{
Id = Guid.Parse("a1b2c3d4-5678-90ab-cdef-1234567890ab"),
Version = 1
},
Inputs = new[]
{
new NameValuePair("reportType", "daily-summary"),
new NameValuePair("format", "pdf"),
new NameValuePair("sendEmail", "true")
}
}
};
// Create the automated action
var action = await client.AutomatedActions.CreateAsync(
namespaceName: "production",
name: "daily-report",
description: "Generates daily summary reports",
actions: new[] { appAction }
);
Console.WriteLine($"Created action: {action.Id}");
Console.WriteLine($"Name: {action.Name}");
Step 2: Add a Cron Schedule (Daily at 9 AM)
Now we add a schedule so the action runs automatically every day at 9:00 AM.
Understanding Cron Expressions
Maitento uses standard cron syntax:
minute hour dayOfMonth month dayOfWeek
| Field | Values | Description |
|---|---|---|
| minute | 0-59 | Minute of the hour |
| hour | 0-23 | Hour of the day (24-hour) |
| dayOfMonth | 1-31 | Day of the month |
| month | 1-12 | Month of the year |
| dayOfWeek | 0-6 | Day of week (0=Sunday) |
For daily at 9:00 AM: 0 9 * * *
Using the Shell CLI
# Add the daily schedule
action-schedule-add \
--action-name daily-report \
--namespace production \
--schedule-name "Daily 9am" \
--minute 0 \
--hour 9 \
--day-of-month "*" \
--month "*" \
--day-of-week "*"
Using the REST API
POST /automated-actions/by-id/f7a8b9c0-1234-56d7-8901-234567890123/schedules
Content-Type: application/json
Authorization: Bearer <your-token>
{
"name": "Daily 9am",
"isEnabled": true,
"minute": "0",
"hour": "9",
"dayOfMonth": "*",
"month": "*",
"dayOfWeek": "*"
}
Response (updated action with schedule):
{
"id": "f7a8b9c0-1234-56d7-8901-234567890123",
"name": "daily-report",
"schedules": [
{
"id": "s1234567-89ab-cdef-0123-456789abcdef",
"name": "Daily 9am",
"isEnabled": true,
"minute": "0",
"hour": "9",
"dayOfMonth": "*",
"month": "*",
"dayOfWeek": "*",
"nextOccurrence": "2026-01-06T09:00:00Z"
}
],
"nextScheduledRunAt": "2026-01-06T09:00:00Z"
}
Using the C# SDK
// Add a daily schedule at 9 AM
var schedule = new CronSchedule
{
Name = "Daily 9am",
IsEnabled = true,
Minute = "0",
Hour = "9",
DayOfMonth = "*",
Month = "*",
DayOfWeek = "*"
};
var updatedAction = await client.AutomatedActions.AddScheduleAsync(
action.Id,
schedule
);
Console.WriteLine($"Schedule added: {updatedAction.Schedules[0].Id}");
Console.WriteLine($"Next run: {updatedAction.NextScheduledRunAt}");
Additional Schedule Examples
# Every 15 minutes
action-schedule-add --action-name my-action --namespace production \
--minute "*/15" --schedule-name "Every 15 min"
# Weekdays at 6 PM
action-schedule-add --action-name my-action --namespace production \
--minute 0 --hour 18 --day-of-week 1-5 --schedule-name "Weekday evening"
# First day of each month at midnight
action-schedule-add --action-name my-action --namespace production \
--minute 0 --hour 0 --day-of-month 1 --schedule-name "Monthly"
# Every 30 seconds (6-field cron with seconds)
action-schedule-add --action-name my-action --namespace production \
--second "*/30" --schedule-name "Every 30 seconds"
Step 3: Add a Message Trigger (On Data Import Completion)
Message triggers allow your action to respond to system events. We will trigger the report when a data import app finishes successfully.
Available Trigger Types
| Trigger Type | Description |
|---|---|
AppProcessUpdated | An app process status changed |
InteractionProcessUpdated | An interaction process status changed |
CodeGenerationProcessUpdated | A code generation process status changed |
CapsuleProcessUpdated | A capsule process status changed |
JPath Filter Expressions
Filters use JPath syntax to match specific events:
$.item.status == 'Finished' // Only successful completions
$.item.status == 'Error' // Only failures
$.item.appId == 'specific-app-id' // Specific app only
$.item.status == 'Finished' && $.item.appId == 'abc123' // Combined
Using the Shell CLI
# Add trigger for when data-import app finishes successfully
action-msg-trigger-add \
--action-name daily-report \
--namespace production \
--trigger-name "On Data Import Complete" \
--type AppProcessUpdated \
--filter "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
Using the REST API
POST /automated-actions/by-id/f7a8b9c0-1234-56d7-8901-234567890123/message-triggers
Content-Type: application/json
Authorization: Bearer <your-token>
{
"name": "On Data Import Complete",
"isEnabled": true,
"triggerType": "AppProcessUpdated",
"eventFilter": "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
}
Response:
{
"id": "f7a8b9c0-1234-56d7-8901-234567890123",
"name": "daily-report",
"messageTriggers": [
{
"id": "t9876543-21fe-dcba-0987-654321fedcba",
"name": "On Data Import Complete",
"isEnabled": true,
"triggerType": "AppProcessUpdated",
"eventFilter": "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
}
]
}
Using the C# SDK
// Add a message trigger for when data import completes
var trigger = new MessageTrigger
{
Name = "On Data Import Complete",
IsEnabled = true,
TriggerType = MessageTriggerType.AppProcessUpdated,
EventFilter = "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
};
var updatedAction = await client.AutomatedActions.AddMessageTriggerAsync(
action.Id,
trigger
);
Console.WriteLine($"Trigger added: {updatedAction.MessageTriggers[0].Id}");
Additional Trigger Examples
# Trigger on any app failure
action-msg-trigger-add --action-name alert-action --namespace production \
--type AppProcessUpdated \
--filter "$.item.status == 'Error'" \
--trigger-name "On Any App Error"
# Trigger when interaction completes
action-msg-trigger-add --action-name followup-action --namespace production \
--type InteractionProcessUpdated \
--filter "$.item.status == 'Finished'" \
--trigger-name "On Interaction Complete"
# Trigger on capsule termination
action-msg-trigger-add --action-name cleanup-action --namespace production \
--type CapsuleProcessUpdated \
--filter "$.item.status == 'Finished' || $.item.status == 'Killed'" \
--trigger-name "On Capsule End"
Step 4: Configure Different Action Types
Automated actions can start different types of processes. Here are examples for each type.
Starting an App
{
"name": "Run App",
"config": {
"$type": "app",
"version": {
"id": "app-id-here",
"version": 1
},
"inputs": [
{ "name": "param1", "value": "value1" },
{ "name": "param2", "value": "value2" }
]
}
}
Starting an Interaction
{
"name": "Start Conversation",
"config": {
"$type": "interaction",
"version": {
"id": "interaction-id-here",
"version": 1
},
"initialPrompt": "Analyze the latest data and provide a summary.",
"inputs": [
{ "name": "context", "value": "daily-analysis" }
]
}
}
Sending a Message to an Existing Interaction
{
"name": "Send Follow-up",
"config": {
"$type": "interaction-message",
"interactionProcessIdFromEvent": "$.item.id",
"message": "Please provide the detailed breakdown.",
"senderType": "System"
}
}
Starting a Capsule
{
"name": "Run Container",
"config": {
"$type": "capsule",
"version": {
"id": "capsule-id-here",
"version": 1
},
"vfsMounts": [
{
"vfsId": "vfs-id",
"vfsNamespaceId": "namespace-id",
"mountName": "data"
}
],
"secretMounts": [
{
"secretName": "api-credentials",
"mountName": "secrets"
}
]
}
}
Complete SDK Example with Interaction
// Create an action that starts an AI interaction
var interactionAction = new AutomatedActionItem
{
Name = "AI Analysis",
Config = new InteractionProcessStartConfig
{
Version = new VersionReference
{
Id = Guid.Parse("b2c3d4e5-6789-01bc-def2-3456789012cd"),
Version = 1
},
InitialPrompt = "Analyze the imported data and create a summary report.",
Inputs = new[]
{
new NameValuePair("outputFormat", "markdown")
}
}
};
var action = await client.AutomatedActions.CreateAsync(
namespaceName: "production",
name: "ai-data-analysis",
description: "AI-powered data analysis on file ingestion",
actions: new[] { interactionAction }
);
Step 5: View Execution History
Monitor your automated actionβs execution history to track successes, failures, and performance.
Using the Shell CLI
# Get history (default: last 50 entries)
action-history --name daily-report --namespace production
# Get last 10 executions
action-history --name daily-report --namespace production --limit 10
# Get history by action ID
action-history --id f7a8b9c0-1234-56d7-8901-234567890123 --limit 20
Using the REST API
GET /automated-actions/by-id/f7a8b9c0-1234-56d7-8901-234567890123/history?limit=10
Authorization: Bearer <your-token>
Response:
[
{
"id": "h1234567-89ab-cdef-0123-456789abcdef",
"triggerType": "Schedule",
"triggerId": "s1234567-89ab-cdef-0123-456789abcdef",
"actionId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"processId": "p9876543-21fe-dcba-0987-654321fedcba",
"processType": "App",
"dateStarted": "2026-01-05T09:00:00Z",
"dateCompleted": "2026-01-05T09:02:15Z",
"outcome": "Success",
"errorMessage": null
},
{
"id": "h2345678-9abc-def0-1234-56789abcdef0",
"triggerType": "Message",
"triggerId": "t9876543-21fe-dcba-0987-654321fedcba",
"actionId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"processId": "p8765432-10fe-dcba-9876-543210fedcba",
"processType": "App",
"dateStarted": "2026-01-05T14:30:00Z",
"dateCompleted": "2026-01-05T14:32:45Z",
"outcome": "Success",
"errorMessage": null
},
{
"id": "h3456789-abcd-ef01-2345-6789abcdef01",
"triggerType": "Manual",
"triggerId": null,
"actionId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"processId": "p7654321-0fed-cba9-8765-43210fedcba9",
"processType": "App",
"dateStarted": "2026-01-04T16:00:00Z",
"dateCompleted": "2026-01-04T16:01:30Z",
"outcome": "Failed",
"errorMessage": "Input validation failed: reportType is required"
}
]
Using the C# SDK
// Get execution history
var history = await client.AutomatedActions.GetHistoryAsync(
action.Id,
limit: 20
);
Console.WriteLine($"Total executions: {history.Length}");
Console.WriteLine();
foreach (var entry in history)
{
Console.WriteLine($"Execution: {entry.Id}");
Console.WriteLine($" Trigger: {entry.TriggerType}");
Console.WriteLine($" Started: {entry.DateStarted}");
Console.WriteLine($" Completed: {entry.DateCompleted}");
Console.WriteLine($" Outcome: {entry.Outcome}");
if (entry.Outcome == ExecutionOutcome.Failed)
{
Console.WriteLine($" Error: {entry.ErrorMessage}");
}
Console.WriteLine();
}
// Check for recent failures
var recentFailures = history
.Where(h => h.Outcome == ExecutionOutcome.Failed)
.Take(5);
if (recentFailures.Any())
{
Console.WriteLine("Recent failures detected:");
foreach (var failure in recentFailures)
{
Console.WriteLine($" - {failure.DateStarted}: {failure.ErrorMessage}");
}
}
Step 6: Manual Trigger and Management
Manually Trigger an Action
Test your action by triggering it manually.
Shell CLI:
action-trigger --name daily-report --namespace production
REST API:
POST /automated-actions/by-id/f7a8b9c0-1234-56d7-8901-234567890123/trigger
Authorization: Bearer <your-token>
C# SDK:
var processId = await client.AutomatedActions.TriggerAsync(action.Id);
Console.WriteLine($"Manually triggered, process ID: {processId}");
Enable/Disable Schedules
Shell CLI:
# Disable a schedule
action-schedule-disable --action-name daily-report --namespace production \
--schedule-id s1234567-89ab-cdef-0123-456789abcdef
# Re-enable a schedule
action-schedule-enable --action-name daily-report --namespace production \
--schedule-id s1234567-89ab-cdef-0123-456789abcdef
C# SDK:
// Disable a schedule
await client.AutomatedActions.DisableScheduleAsync(
action.Id,
scheduleId: Guid.Parse("s1234567-89ab-cdef-0123-456789abcdef")
);
// Re-enable a schedule
await client.AutomatedActions.EnableScheduleAsync(
action.Id,
scheduleId: Guid.Parse("s1234567-89ab-cdef-0123-456789abcdef")
);
Enable/Disable Message Triggers
Shell CLI:
# Disable a trigger
action-msg-trigger-disable --action-name daily-report --namespace production \
--trigger-id t9876543-21fe-dcba-0987-654321fedcba
# Re-enable a trigger
action-msg-trigger-enable --action-name daily-report --namespace production \
--trigger-id t9876543-21fe-dcba-0987-654321fedcba
C# SDK:
// Disable a trigger
await client.AutomatedActions.DisableMessageTriggerAsync(
action.Id,
triggerId: Guid.Parse("t9876543-21fe-dcba-0987-654321fedcba")
);
// Re-enable a trigger
await client.AutomatedActions.EnableMessageTriggerAsync(
action.Id,
triggerId: Guid.Parse("t9876543-21fe-dcba-0987-654321fedcba")
);
Delete an Action
Shell CLI:
action-delete --name daily-report --namespace production
C# SDK:
await client.AutomatedActions.DeleteAsync(action.Id);
Complete Working Example
Here is a complete C# example that creates an automated action with both schedule and message trigger:
using Maitento.Sdk;
using Maitento.Entities.AutomatedActions;
public class ScheduledAutomationExample
{
public static async Task Main()
{
// Initialize the client
var client = MaitentoClient.Create("your-api-key");
// Step 1: Create the automated action
var appAction = new AutomatedActionItem
{
Name = "Generate Report",
Config = new AppProcessStartConfig
{
Version = new VersionReference
{
Id = Guid.Parse("a1b2c3d4-5678-90ab-cdef-1234567890ab"),
Version = 1
},
Inputs = new[]
{
new NameValuePair("reportType", "daily-summary"),
new NameValuePair("format", "pdf"),
new NameValuePair("sendEmail", "true")
}
}
};
var action = await client.AutomatedActions.CreateAsync(
namespaceName: "production",
name: "daily-report",
description: "Generates daily summary reports",
actions: new[] { appAction }
);
Console.WriteLine($"Created action: {action.Id}");
// Step 2: Add daily schedule at 9 AM
var schedule = new CronSchedule
{
Name = "Daily 9am",
IsEnabled = true,
Minute = "0",
Hour = "9",
DayOfMonth = "*",
Month = "*",
DayOfWeek = "*"
};
action = await client.AutomatedActions.AddScheduleAsync(action.Id, schedule);
Console.WriteLine($"Added schedule, next run: {action.NextScheduledRunAt}");
// Step 3: Add message trigger for data import completion
var trigger = new MessageTrigger
{
Name = "On Data Import Complete",
IsEnabled = true,
TriggerType = MessageTriggerType.AppProcessUpdated,
EventFilter = "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
};
action = await client.AutomatedActions.AddMessageTriggerAsync(action.Id, trigger);
Console.WriteLine($"Added message trigger: {action.MessageTriggers[0].Id}");
// Step 4: Manually trigger for testing
var processId = await client.AutomatedActions.TriggerAsync(action.Id);
Console.WriteLine($"Manual trigger started process: {processId}");
// Step 5: Check execution history
await Task.Delay(5000); // Wait for execution
var history = await client.AutomatedActions.GetHistoryAsync(action.Id, limit: 10);
Console.WriteLine($"\nExecution History ({history.Length} entries):");
foreach (var entry in history)
{
var status = entry.Outcome == ExecutionOutcome.Success ? "OK" : "FAILED";
Console.WriteLine($" [{status}] {entry.TriggerType} at {entry.DateStarted}");
}
// Summary
Console.WriteLine("\n=== Automation Setup Complete ===");
Console.WriteLine($"Action: {action.Name}");
Console.WriteLine($"Schedules: {action.Schedules.Length}");
Console.WriteLine($"Message Triggers: {action.MessageTriggers.Length}");
Console.WriteLine($"Next Scheduled Run: {action.NextScheduledRunAt}");
Console.WriteLine($"Total Executions: {action.TotalExecutions}");
}
}
Best Practices
Design for Idempotency
Your automated actions may run multiple times (retries, duplicate triggers). Design your apps to handle this gracefully:
// Check if report already exists for today
var existingReport = await GetReportForDate(DateTime.Today);
if (existingReport != null)
{
Console.WriteLine("Report already generated, skipping");
return;
}
Monitor Consecutive Failures
The consecutiveFailures counter tracks how many times an action has failed in a row. Use this for alerting:
var action = await client.AutomatedActions.GetByIdAsync(actionId);
if (action.ConsecutiveFailures >= 3)
{
// Send alert to operations team
await SendAlert($"Action '{action.Name}' has failed {action.ConsecutiveFailures} times");
}
Use Descriptive Names
Give your schedules and triggers meaningful names for easier debugging:
# Good
--schedule-name "Weekday Morning Report"
--trigger-name "On Customer Data Import Complete"
# Bad
--schedule-name "Schedule 1"
--trigger-name "Trigger"
Limit Schedule Count
Each action supports a maximum of 5 schedules. Use cron expressions efficiently:
# Instead of 4 separate schedules for 9am, 12pm, 3pm, 6pm:
# Use a single schedule with comma-separated hours
--hour "9,12,15,18"
Test with Manual Triggers
Always test your action manually before relying on schedules:
# Test the action
action-trigger --name daily-report --namespace production
# Check the result
action-history --name daily-report --namespace production --limit 1
Troubleshooting
Action Not Running on Schedule
-
Verify the schedule is enabled:
action-get --name my-action --namespace production -
Check the
nextScheduledRunAtfield is set correctly -
Ensure the cron expression is valid
Message Trigger Not Firing
- Verify the trigger type matches the event source
- Check the JPath filter syntax
- Ensure the trigger is enabled
- Verify the event payload matches your filter
Execution History Shows Failures
- Check the
errorMessagefield in history entries - Verify the target app/interaction exists and is not disabled
- Check input parameter values are correct
- Review the process logs for detailed error information
Related Documentation
- Apps and Capsules Guide - Learn about apps and capsules
- API Reference - Complete API documentation
- Shell Reference - Shell command reference
- SDK Reference - C# SDK documentation