Scheduled Automation

This tutorial walks you through creating automated workflows in Maitento using Automated Actions. You will learn how to:

  1. Create an automated action that runs an app
  2. Add cron schedules for time-based execution
  3. Add message triggers for event-driven execution
  4. Configure actions to start apps or interactions
  5. 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:

  1. Runs automatically every day at 9:00 AM
  2. Also triggers whenever a data import app completes successfully
  3. Executes a report generation app with configurable parameters
  4. 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
FieldValuesDescription
minute0-59Minute of the hour
hour0-23Hour of the day (24-hour)
dayOfMonth1-31Day of the month
month1-12Month of the year
dayOfWeek0-6Day 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 TypeDescription
AppProcessUpdatedAn app process status changed
InteractionProcessUpdatedAn interaction process status changed
CodeGenerationProcessUpdatedA code generation process status changed
CapsuleProcessUpdatedA 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

  1. Verify the schedule is enabled:

    action-get --name my-action --namespace production
    
  2. Check the nextScheduledRunAt field is set correctly

  3. Ensure the cron expression is valid

Message Trigger Not Firing

  1. Verify the trigger type matches the event source
  2. Check the JPath filter syntax
  3. Ensure the trigger is enabled
  4. Verify the event payload matches your filter

Execution History Shows Failures

  1. Check the errorMessage field in history entries
  2. Verify the target app/interaction exists and is not disabled
  3. Check input parameter values are correct
  4. Review the process logs for detailed error information