This page documents all currently supported command types in the AIR Automation Engine.
Each step in a script ultimately produces a command. A command is placed on the command queue, claimed by a compatible worker, executed, and then marked as completed, failed, or retried depending on the outcome.
This document focuses on:
It does not explain script design, scheduling, or the overall orchestration lifecycle in detail. Those topics should be covered in separate introduction pages.
The following command types are currently supported:
noopsleepsql-executesql-importreport-renderreport-printprint-artifactsend-emailcleanup-artifactsAt a conceptual level, commands follow this structure:
{
"CommandType": "sql-execute",
"AllowedWorkers": ["EMAIL-WORKER-1"],
"PayloadJson": {
}
}
CommandType determines which executor handles the command.AllowedWorkers is optional.AllowedWorkers is omitted or null, any worker that supports that command type may claim it.AllowedWorkers contains one worker name, only that worker may claim the command.AllowedWorkers contains multiple worker names, any of those workers may claim it.Most payload values may make use of runtime placeholders:
{
"Value": "{{OrderId}}"
}
These placeholders are resolved before the command executes.
Typical examples:
{{OrderId}}{{RunId}}{{StepExecutionId}}{{ReportArtifactId}}The exact keys available depend on previous steps and what values they stored in the run context.
noopPerforms no action and always succeeds.
Use noop when you need:
noop does not require any payload fields.
{}
{
"CommandType": "noop",
"PayloadJson": {}
}
sleepIntroduces a deliberate delay in execution.
Use sleep when:
| Field | Type | Required | Description |
|---|---|---|---|
DurationSeconds |
integer | Yes | Number of seconds to pause |
ResultKey |
string | No | Optional key to store the sleep duration in context |
{
"DurationSeconds": 10
}
{
"DurationSeconds": 5,
"ResultKey": "LastSleepDuration"
}
DurationSeconds must be greater than zero.Sleep is sometimes useful, but it is still a blunt tool. Prefer real completion or status checks where possible.
sql-executeExecutes SQL against a target database.
Use sql-execute for:
NonQuery – for statements that do not return a valueScalar – for queries that return a single value| Field | Type | Required | Description |
|---|---|---|---|
ConnectionString |
string | No* | Target DB connection string |
SqlText |
string | Yes | SQL command text |
ExecutionMode |
string | Yes | NonQuery or Scalar |
Parameters |
array | No | SQL parameters |
ResultKey |
string | No | Context key for scalar result |
* Depending on your implementation, a default connection may be used.
| Field | Type | Required | Description |
|---|---|---|---|
Name |
string | Yes | SQL parameter name, e.g. @OrderId |
Value |
any | Yes | Parameter value |
DbType |
string | No | SQL type hint, e.g. Int, NVarChar, UniqueIdentifier |
{
"SqlText": "UPDATE Orders SET Status = @Status WHERE OrderId = @OrderId",
"ExecutionMode": "NonQuery",
"Parameters": [
{
"Name": "@Status",
"Value": "Processed",
"DbType": "NVarChar"
},
{
"Name": "@OrderId",
"Value": 12345,
"DbType": "Int"
}
]
}
{
"SqlText": "SELECT COUNT(*) FROM Orders WHERE Status = @Status",
"ExecutionMode": "Scalar",
"Parameters": [
{
"Name": "@Status",
"Value": "Pending",
"DbType": "NVarChar"
}
],
"ResultKey": "PendingOrderCount"
}
{
"SqlText": "SELECT * FROM Orders WHERE OrderGuid = @OrderGuid",
"ExecutionMode": "NonQuery",
"Parameters": [
{
"Name": "@OrderGuid",
"Value": "{{OrderGuid}}",
"DbType": "UniqueIdentifier"
}
]
}
{
"SqlText": "INSERT INTO AuditLog (RunId, StepExecutionId, Message, CreatedUtc) VALUES (@RunId, @StepExecutionId, @Message, @CreatedUtc)",
"ExecutionMode": "NonQuery",
"Parameters": [
{
"Name": "@RunId",
"Value": "{{RunId}}",
"DbType": "UniqueIdentifier"
},
{
"Name": "@StepExecutionId",
"Value": "{{StepExecutionId}}",
"DbType": "UniqueIdentifier"
},
{
"Name": "@Message",
"Value": "Order report completed",
"DbType": "NVarChar"
},
{
"Name": "@CreatedUtc",
"Value": "2026-04-05T09:00:00",
"DbType": "DateTime2"
}
]
}
DbType when you need predictable conversion, especially for GUIDs, dates, decimals, and booleans.Scalar output may be stored in context and reused later.ExecutionModeExecutionMode must be supplied and should be either NonQuery or Scalar.
DbTypeIf a GUID value is passed without the correct type handling, it may fail conversion. Prefer:
{
"Name": "@Id",
"Value": "{{SomeGuid}}",
"DbType": "UniqueIdentifier"
}
Always prefer parameters over building raw SQL strings with values embedded.
sql-importImports data from a source SQL database into a destination table.
Use sql-import when:
| Field | Type | Required | Description |
|---|---|---|---|
SourceConnectionString |
string | Yes | Connection string to source DB |
SourceSql |
string | Yes | SQL query to run on source DB |
TargetTable |
string | Yes | Destination table name |
RecreateTargetTable |
boolean | No | Drop and recreate the target table |
TruncateTargetTable |
boolean | No | Truncate the target table before import |
SourceCommandTimeoutSeconds |
integer | No | Timeout for source query |
BulkCopyTimeoutSeconds |
integer | No | Timeout for bulk insert |
ResultKeyRowCount |
string | No | Context key for imported row count |
ResultKeyTargetTable |
string | No | Context key for target table name |
{
"SourceConnectionString": "Server=REMOTE-SQL;Database=ERP;Trusted_Connection=True;TrustServerCertificate=True;",
"SourceSql": "SELECT ItemCode, Description, Price FROM dbo.Items",
"TargetTable": "dbo.Stage_Items",
"RecreateTargetTable": true
}
{
"SourceConnectionString": "Server=REMOTE-SQL;Database=ERP;Trusted_Connection=True;TrustServerCertificate=True;",
"SourceSql": "SELECT CustomerId, Name, CreditLimit FROM dbo.Customers",
"TargetTable": "dbo.Stage_Customers",
"RecreateTargetTable": false,
"TruncateTargetTable": true,
"SourceCommandTimeoutSeconds": 120,
"BulkCopyTimeoutSeconds": 180,
"ResultKeyRowCount": "ImportedCustomerCount",
"ResultKeyTargetTable": "ImportedTargetTable"
}
{
"SourceConnectionString": "Server=REMOTE-SQL;Database=ERP;Trusted_Connection=True;TrustServerCertificate=True;",
"SourceSql": "SELECT * FROM dbo.Sales WHERE SalesDate >= '{{StartDate}}' AND SalesDate < '{{EndDate}}'",
"TargetTable": "dbo.Stage_Sales",
"TruncateTargetTable": true,
"ResultKeyRowCount": "ImportedSalesRows"
}
RecreateTargetTable is useful when the target table is transient or its schema should match the incoming data.TruncateTargetTable is useful when the table structure should remain fixed.For large ERP queries or slow links, use explicit command and bulk copy timeouts.
Be careful when pointing at permanent tables. Prefer stage tables unless the import is intentionally destructive.
report-renderLoads a DevExpress report definition from the database, applies parameters, exports it to a file format, and stores the result as an artifact.
Use report-render when:
pdfxlsxdocx| Field | Type | Required | Description |
|---|---|---|---|
ReportId |
integer | Yes | Report definition ID |
OutputFormat |
string | No | pdf, xlsx, or docx |
OutputFileName |
string | No | Base file name without extension |
ResultKeyArtifactId |
string | No | Context key for artifact ID |
ResultKeyArtifactLocation |
string | No | Context key for artifact location |
ResultKeyFileName |
string | No | Context key for file name |
Parameters |
array | No | Report parameters |
| Field | Type | Required | Description |
|---|---|---|---|
Name |
string | Yes | Report parameter name |
Value |
any | Yes | Parameter value |
DbType |
string | No | Optional type hint |
{
"ReportId": 25,
"OutputFormat": "pdf",
"Parameters": [
{
"Name": "@OrderId",
"Value": 12345,
"DbType": "Int"
}
],
"ResultKeyArtifactId": "ReportArtifactId",
"ResultKeyArtifactLocation": "ReportArtifactLocation",
"ResultKeyFileName": "ReportFileName"
}
{
"ReportId": 12,
"OutputFormat": "xlsx",
"OutputFileName": "SalesExport_April2026",
"Parameters": [
{
"Name": "@StartDate",
"Value": "2026-04-01",
"DbType": "Date"
},
{
"Name": "@EndDate",
"Value": "2026-04-30",
"DbType": "Date"
}
],
"ResultKeyArtifactId": "SalesExportArtifactId",
"ResultKeyFileName": "SalesExportFileName"
}
{
"ReportId": 42,
"OutputFormat": "docx",
"OutputFileName": "CustomerLetter",
"Parameters": [
{
"Name": "@CustomerId",
"Value": "{{CustomerId}}",
"DbType": "Int"
}
],
"ResultKeyArtifactId": "LetterArtifactId"
}
{
"ReportId": 30,
"OutputFormat": "pdf",
"OutputFileName": "MonthlyStatement",
"Parameters": [
{
"Name": "@CustomerId",
"Value": "{{CustomerId}}",
"DbType": "Int"
},
{
"Name": "@StatementDate",
"Value": "2026-04-05",
"DbType": "Date"
}
],
"ResultKeyArtifactId": "StatementArtifactId",
"ResultKeyArtifactLocation": "StatementArtifactLocation",
"ResultKeyFileName": "StatementFileName"
}
report-render is ideal when you need a reusable output.send-email, print-artifact, or cleanup-artifacts.report-print when the output must be retained or distributed.report-render just to print immediatelyIf the goal is fast direct printing, especially for labels, report-print is usually the better command.
Your implementation may set content type simply or explicitly; make sure downstream steps handle the artifact correctly.
report-printLoads a DevExpress report definition from the database, applies parameters, and prints it directly using the DevExpress print engine.
Use report-print when:
| Field | Type | Required | Description |
|---|---|---|---|
ReportId |
integer | Yes | Report definition ID |
PrinterName |
string | Yes | Installed printer name |
Copies |
integer | No | Number of copies |
PrinterSettings |
object | No | Optional print settings |
ResultKeyPrinterName |
string | No | Context key for printer name |
ResultKeyCopies |
string | No | Context key for copies |
ResultKeyReportId |
string | No | Context key for report ID |
ResultKeyPaperSource |
string | No | Context key for paper source |
Parameters |
array | No | Report parameters |
| Field | Type | Required | Description |
|---|---|---|---|
PaperSourceName |
string | No | Tray/source name |
Collate |
boolean | No | Collate copies |
Duplex |
string | No | Simplex, Horizontal, Vertical, or Default |
IgnoreInvalidPaperSource |
boolean | No | Ignore missing tray/source |
CustomPaperName |
string | No | Custom paper name |
WidthMm |
number | No | Custom page width in millimeters |
HeightMm |
number | No | Custom page height in millimeters |
{
"ReportId": 25,
"PrinterName": "Zebra ZD421",
"Copies": 1,
"Parameters": [
{
"Name": "@OrderId",
"Value": 12345,
"DbType": "Int"
}
]
}
{
"ReportId": 25,
"PrinterName": "Zebra ZD421",
"Copies": 1,
"PrinterSettings": {
"CustomPaperName": "Shipping Label 100x150",
"WidthMm": 100,
"HeightMm": 150,
"PaperSourceName": "Default",
"Collate": false,
"Duplex": "Simplex",
"IgnoreInvalidPaperSource": true
},
"Parameters": [
{
"Name": "@OrderId",
"Value": "{{OrderId}}",
"DbType": "Int"
}
],
"ResultKeyPrinterName": "LastPrinterUsed",
"ResultKeyCopies": "LastPrintedCopies",
"ResultKeyReportId": "LastPrintedReportId",
"ResultKeyPaperSource": "LastPaperSource"
}
{
"ReportId": 12,
"PrinterName": "HP LaserJet in Accounts",
"Copies": 2,
"PrinterSettings": {
"PaperSourceName": "Tray 2",
"Collate": true,
"Duplex": "Vertical",
"IgnoreInvalidPaperSource": false
},
"Parameters": [
{
"Name": "@InvoiceId",
"Value": 99871,
"DbType": "Int"
}
],
"ResultKeyPrinterName": "LastPrinterUsed",
"ResultKeyCopies": "LastPrintedCopies"
}
{
"CommandType": "report-print",
"AllowedWorkers": ["PRINT-SERVER-01"],
"PayloadJson": {
"ReportId": 25,
"PrinterName": "Zebra ZD421",
"Copies": 1
}
}
report-print is usually the right choice for labels and direct print workflows.WidthMm and HeightMm are especially useful for label printers.This command should only be available on Windows print workers.
The printer name must match exactly what Windows exposes on that machine.
The printer driver may still impose its own limitations.
print-artifactPrints an existing artifact, such as a previously rendered PDF or exported file.
Use print-artifact when:
| Field | Type | Required | Description |
|---|---|---|---|
ArtifactId |
guid | Yes | Artifact to print |
PrinterName |
string | Yes | Installed printer name |
Copies |
integer | No | Number of copies |
PrintProvider |
string | No | Shell or SumatraPdf |
SumatraPdfPath |
string | No | Required when using SumatraPdf |
DeleteArtifactsAfterSuccess |
boolean | No | Delete artifact after successful print |
DeleteArtifactsAfterFailure |
boolean | No | Delete artifact after failed print |
ResultKeyPrinterName |
string | No | Context key for printer name |
ResultKeyPrintedArtifactId |
string | No | Context key for artifact ID |
ResultKeyCopies |
string | No | Context key for copies |
ResultKeyPrintProvider |
string | No | Context key for print provider |
ResultKeyDeletedArtifact |
string | No | Context key for delete-after status |
{
"ArtifactId": "{{ReportArtifactId}}",
"PrinterName": "HP LaserJet in Accounts",
"Copies": 1,
"PrintProvider": "Shell"
}
{
"ArtifactId": "{{ReportArtifactId}}",
"PrinterName": "HP LaserJet in Accounts",
"Copies": 1,
"PrintProvider": "SumatraPdf",
"SumatraPdfPath": "C:\\Tools\\SumatraPDF\\SumatraPDF.exe"
}
{
"ArtifactId": "{{ReportArtifactId}}",
"PrinterName": "Zebra ZD421",
"PrintProvider": "SumatraPdf",
"SumatraPdfPath": "C:\\Tools\\SumatraPDF\\SumatraPDF.exe",
"DeleteArtifactsAfterSuccess": true,
"DeleteArtifactsAfterFailure": false,
"ResultKeyDeletedArtifact": "PrintedArtifactDeleted"
}
{
"ArtifactId": "{{InvoiceArtifactId}}",
"PrinterName": "HP LaserJet in Accounts",
"Copies": 2,
"PrintProvider": "Shell",
"ResultKeyCopies": "PrintedCopies"
}
print-artifact is useful when you need a stored file and later printing.report-render.report-print is usually better for that case.Make sure the artifact-producing step runs first and succeeds.
If using SumatraPdf, the executable path must exist on the worker machine.
send-emailSends email using the configured SMTP settings, with optional attachments.
Use send-email when:
| Field | Type | Required | Description |
|---|---|---|---|
From |
string | No | Override sender address |
To |
array | No | Recipient list |
Cc |
array | No | CC recipient list |
Bcc |
array | No | BCC recipient list |
Subject |
string | Yes | Email subject |
BodyText |
string | No | Plain text body |
BodyHtml |
string | No | HTML body |
Attachments |
array | No | File-path attachments |
AttachmentArtifactIds |
array | No | Artifact-based attachments |
DeleteArtifactsAfterSuccess |
boolean | No | Delete attached artifacts after success |
DeleteArtifactsAfterFailure |
boolean | No | Delete attached artifacts after failure |
ResultKeyMessageId |
string | No | Context key for provider message ID |
ResultKeyAttachmentCount |
string | No | Context key for attachment count |
ResultKeyDeletedArtifactCount |
string | No | Context key for deleted artifact count |
| Field | Type | Required | Description |
|---|---|---|---|
Path |
string | Yes | File path |
FileName |
string | No | Optional file name override |
ContentType |
string | No | MIME type |
{
"To": ["user@example.com"],
"Subject": "Test Email",
"BodyText": "Hello world"
}
{
"To": ["user@example.com"],
"Subject": "Report Ready",
"BodyHtml": "<p>Please see attached report.</p>",
"AttachmentArtifactIds": [
"{{ReportArtifactId}}"
],
"ResultKeyMessageId": "LastEmailMessageId",
"ResultKeyAttachmentCount": "LastEmailAttachmentCount"
}
{
"From": "reports@company.com",
"To": ["user@example.com"],
"Cc": ["manager@example.com"],
"Bcc": ["audit@example.com"],
"Subject": "Monthly Report",
"BodyText": "Please find the monthly report attached.",
"BodyHtml": "<h1>Monthly Report</h1><p>Please find the report attached.</p>",
"AttachmentArtifactIds": [
"{{ReportArtifactId}}",
"{{SummaryArtifactId}}"
],
"DeleteArtifactsAfterSuccess": true,
"DeleteArtifactsAfterFailure": false,
"ResultKeyMessageId": "EmailProviderId",
"ResultKeyAttachmentCount": "AttachmentCount",
"ResultKeyDeletedArtifactCount": "DeletedArtifactCount"
}
{
"To": ["ops@example.com"],
"Subject": "Daily Log Export",
"BodyText": "Please see attached log file.",
"Attachments": [
{
"Path": "C:\\Exports\\daily-log.txt",
"FileName": "daily-log.txt",
"ContentType": "text/plain"
}
]
}
report-render.Make sure the SMTP account, password/app password, and tenant/mailbox settings are correct.
At least one of To, Cc, or Bcc should contain a recipient.
Only use delete-after-success when the current email step is the final use of the artifact.
cleanup-artifactsDeletes one or more artifacts by ID.
Use cleanup-artifacts when:
| Field | Type | Required | Description |
|---|---|---|---|
ArtifactId |
string | No | Single artifact ID |
ArtifactIds |
array | No | Multiple artifact IDs |
IgnoreMissing |
boolean | No | Ignore artifacts not found |
IgnoreErrors |
boolean | No | Continue on delete failures |
ResultKeyDeletedCount |
string | No | Context key for deleted count |
ResultKeyMissingCount |
string | No | Context key for missing count |
ResultKeyFailedCount |
string | No | Context key for failed count |
At least one of ArtifactId or ArtifactIds must be supplied.
{
"ArtifactId": "{{ReportArtifactId}}",
"IgnoreMissing": true,
"ResultKeyDeletedCount": "DeletedArtifactCount"
}
{
"ArtifactIds": [
"{{ReportArtifactId}}",
"{{InvoiceArtifactId}}",
"{{SummaryArtifactId}}"
],
"IgnoreMissing": true,
"IgnoreErrors": false,
"ResultKeyDeletedCount": "DeletedArtifactCount",
"ResultKeyMissingCount": "MissingArtifactCount",
"ResultKeyFailedCount": "FailedArtifactDeleteCount"
}
{
"ArtifactIds": [
"{{StatementArtifactId}}",
"{{ExportArtifactId}}"
],
"IgnoreMissing": true,
"IgnoreErrors": true,
"ResultKeyDeletedCount": "CleanupDeletedCount"
}
Make sure the artifact is no longer needed by any later step.
IgnoreMissingFor cleanup-heavy workflows, IgnoreMissing is often a good idea.
Any compatible worker may claim the command.
{
"CommandType": "send-email",
"PayloadJson": {
"To": ["user@example.com"],
"Subject": "Hello"
}
}
Only one named worker may claim the command.
{
"CommandType": "send-email",
"AllowedWorkers": ["EMAIL-WORKER-1"],
"PayloadJson": {
"To": ["user@example.com"],
"Subject": "Hello"
}
}
Any worker in the list may claim the command.
{
"CommandType": "report-print",
"AllowedWorkers": ["PRINT-SERVER-01", "PRINT-SERVER-02"],
"PayloadJson": {
"ReportId": 25,
"PrinterName": "Zebra ZD421"
}
}
Use when the output must be sent as an attachment and then removed.
report-rendersend-emailcleanup-artifactsUse when speed matters and no reusable file is needed.
report-printUse when output must be retained or reused later.
report-renderprint-artifactUse when data must first be imported or prepared.
sql-importsql-executereport-render or report-printEach command should be designed to do one job well.
The strength of the AIR Automation Engine comes from combining these commands into clear, reusable workflows.
As a general rule:
report-render when you need a filereport-print when you need direct printingprint-artifact when printing an existing filesend-email for deliverycleanup-artifacts for explicit cleanup