Workflow Automation
Lotics workflows are declarative step sequences that chain tools, control flow, and expressions to automate business processes end-to-end. Instead of writing code, you define what should happen when a specific event occurs, and the workflow engine handles execution, retries, and state management. A single workflow can receive an email, extract data with AI, query and update records, generate documents, and send notifications, all without leaving the platform.
What are workflows?
A workflow in Lotics is a sequence of steps that executes automatically when a trigger condition is met. Each workflow has exactly one trigger and one or more steps. Steps run in order, and each step can reference data from the trigger event or from previous steps using expressions. Workflows are designed to replace the manual, repetitive processes that operations teams perform daily: copying data between systems, sending follow-up emails, updating spreadsheets, generating reports.
Workflows are declarative: you specify the trigger, define the steps, and configure each step's inputs. The workflow engine manages execution order, handles errors, and maintains state between steps. If a step fails, the workflow logs the error with full context so you can diagnose and fix the issue. There are no silent failures.
The AI assistant can build workflows from natural language descriptions. Describe what you want automated ("when a customer submits a booking request form, create a shipment record, assign it to the operations team, and send a confirmation email") and the assistant will create the workflow with the correct trigger, steps, and expressions.
Trigger types
Every workflow starts with a trigger, the event that causes the workflow to run. Lotics supports 8 trigger types that cover the most common ways work enters an operations team: data changes, incoming communications, scheduled tasks, user actions, and external system events.
Triggers are configured per-workflow and cannot be combined. If you need the same workflow to run on both a schedule and a record change, create two workflows that call a shared sequence of steps. Each trigger provides context data that subsequent steps can reference via expressions.
record_updated
Fires when a record is created, updated, or deleted in a specified table. This is the most common trigger for keeping data consistent and reacting to changes as they happen.
Context provided: record_id, table_id, prev_data, next_data, changes (the specific fields that changed), and change_origin (who or what made the change). Display counterparts are available for all data fields, converting option keys to names, member IDs to names, and record IDs to display text.
Example uses: When a shipment status changes to "Delivered", update the related invoice status. When a new customer record is created, send a welcome email. When a record is deleted, archive the related documents.
recurring_schedule
Fires on a cron schedule (e.g., every weekday at 9am, first Monday of the month, every 6 hours). Use this for periodic tasks that don't depend on a specific event.
Context provided: trigger_id and the scheduled execution time.
Example uses: Generate a daily summary report of overdue invoices. Check for containers that have been in yard for more than 14 days. Send weekly KPI emails to management.
receive_gmail_email
Fires when a new email arrives in a connected Gmail account. The trigger monitors the inbox and processes each incoming message automatically.
Context provided: email_id, from (sender address), subject, body (full email content), and attachments[] (list of file attachments).
Example uses: Extract invoice data from vendor emails. Parse booking confirmations from shipping lines. Route customer inquiries to the right team based on subject line keywords.
receive_outlook_email
Fires when a new email arrives in a connected Outlook account. Works identically to the Gmail trigger but for Microsoft 365 accounts.
Context provided: email_id, from (sender address), subject, body (full email content), and attachments[] (list of file attachments).
Example uses: Same as Gmail trigger. Process purchase orders received via Outlook. Extract shipping instructions from carrier emails.
button_pressed
Fires when a user clicks a button field in a table row. This lets you attach on-demand actions to individual records, triggered explicitly by a team member.
Context provided: table_id, record_id, field_key (which button field was clicked), context.member_id (who clicked it), data (full record data), and display (human-readable display values for all fields).
Example uses: "Generate Invoice" button on an order record. "Send Reminder" button on an overdue payment. "Approve" button on a purchase request that routes to the next approver.
form_submitted
Fires when a public or internal form is submitted. Forms can be shared externally via link (for customer-facing intake) or used internally for structured data collection.
Context provided: form_id, submission_id, context.member_id (the submitter, if authenticated), data (all submitted field values), and display (human-readable versions of the submitted values).
Example uses: When a customer submits a booking request form, create a shipment record and assign it to the operations team. When an employee submits an expense report, route it to their manager for approval.
app_action
Fires when an action is triggered from within a Lotics app. Apps are custom interfaces built on top of your tables, and actions let you wire workflow logic to buttons and interactions within those interfaces.
Context provided: app_id, action_id, context.member_id (who triggered it), and inputs (the action parameters defined in the app configuration).
Example uses: An operations dashboard app with a "Dispatch" action that assigns a driver, updates the shipment status, and sends a notification. A customer portal app with a "Request Quote" action that creates a record and notifies the sales team.
receive_webhook
Fires when an HTTP POST request is received at the workflow's unique webhook URL. This allows external systems to trigger workflows without any Lotics-specific integration code.
Context provided: The full request body, headers, and query parameters from the incoming HTTP POST.
Example uses: Receive shipment tracking updates from a carrier's API. Process payment notifications from a payment gateway. Accept data from any external system that can send HTTP requests.
Step types
Steps are the building blocks of a workflow. Each step performs a single action: calling a tool, evaluating a condition, iterating over a list, validating inputs, or pausing execution. Steps execute in sequence, and each step's output becomes available to subsequent steps through the expression engine.
Lotics provides 8 step types that cover the full range of automation needs, from simple tool calls to complex branching logic, asynchronous waiting patterns, and input validation.
tool_call
Execute any registered tool from the Lotics tool registry. This is the workhorse step type, covering AI extraction, record operations, email sending, document generation, HTTP requests, and more.
Key fields: tool_name (which tool to execute) and input (the tool's parameters, which can contain expressions).
Example uses: Extract invoice data from an attachment using AI. Create or update a record. Send a Slack notification. Generate a PDF from a template. Query records with filters. Send an email.
if
Conditional branching based on an expression that evaluates to true or false. The workflow follows the "then" branch if the condition is true, and the optional "else" branch if false.
Key fields: condition (an expression that evaluates to a boolean), then (steps to execute when true), and else (optional steps to execute when false).
Example uses: If invoice total exceeds $10,000, route to manager approval; otherwise, auto-approve. If the ETA has changed from the previous value, send a notification; otherwise, skip. If a matching record is found, update it; otherwise, create a new one.
switch
Multi-way branching based on matching an expression value against multiple cases. More readable than nested if/else chains when routing on a single value.
Key fields: expression (the value to match against), cases (map of value to step sequences), and default (optional fallback steps if no case matches).
Example uses: Route a support ticket to different teams based on category: billing, technical, shipping. Handle different document types with different extraction logic. Apply different approval flows based on expense category.
foreach
Iterate over an array, executing a sequence of nested steps for each item. Inside the loop body, the current item and its index are available as expression variables.
Key fields: items (an expression that evaluates to an array) and steps (the step sequence to execute for each item). Inside the loop, {{item}} references the current array element and {{index}} references its position.
Example uses: For each line item in an invoice, create a separate record and validate against the purchase order. For each shipment in a batch, send a tracking update email. For each attachment in an incoming email, run AI extraction.
wait
Pause execution for a specified duration. The workflow engine saves the current state and schedules a resume job.
Key fields: duration_in_minutes (how long to pause).
Example uses: Wait 24 hours after sending a payment reminder before escalating to collections. Wait 30 minutes after creating a record before checking if it was updated (allowing time for manual review). Introduce a delay between API calls to respect rate limits.
wait_for_event
Pause execution until a specific external event occurs, with an optional timeout. The workflow engine saves state and resumes when the matching event is received.
Key fields: event_type (what kind of event to wait for), event_ref (identifier to match against), and timeout_in_minutes (optional maximum wait time before the workflow continues or fails).
Example uses: Wait until a manager approves a purchase order before generating the payment. Wait until a payment confirmation webhook is received. Wait for a shipment status to change to "Cleared" before generating the delivery order.
return
Exit the workflow early and return a result. This stops execution immediately, skipping any remaining steps.
Key fields: status (either "success" or "error") and message (a human-readable result description).
Example uses: If a duplicate record is detected, return an error message and stop processing. If a validation check fails, return early with an explanation. If the required data is not found, exit gracefully instead of continuing with empty values.
validate
Declarative input validation that checks multiple conditions and collects all failures. Unlike an if step that stops at the first condition, validate runs all checks and reports every issue at once.
Key fields: checks (a list of validation rules, each with a condition and error message). All checks are evaluated, and all failures are collected into a single error report.
Example uses: Before processing an invoice, validate that the vendor exists, the PO number is present, the total is positive, and the currency is supported, reporting all issues in one pass. Before creating a shipment, verify that all required fields are filled and the dates are logically consistent.
Expression engine
Expressions are the glue that connects steps together. Using the {{ }} syntax, any step can reference data from the trigger event or from the output of previous steps. Expressions let you build dynamic workflows where each step adapts based on what happened before it.
The expression engine uses jexpr, a lightweight JavaScript expression evaluator. It supports standard operators (arithmetic, comparison, logical), dot notation for nested objects, array indexing, and string concatenation.
Two evaluation modes
Pure expression mode: When the entire value is a single expression like {{trigger.record_updated.next_data.total}}, the result preserves its original type (number, boolean, object, array). This is important when passing structured data between steps.
Template string mode: When expressions are mixed with text like "Invoice {{trigger.gmail_receive_email.subject}} received from {{trigger.gmail_receive_email.from}}", the result is always a string. Use this for building messages, descriptions, and labels.
Available context
| Context path | Available when | Description |
|---|---|---|
| {{trigger.{namespace}.*}} | Always | Data from the trigger event. The namespace matches the trigger type (e.g., trigger.record_updated, trigger.gmail_receive_email, trigger.button_pressed). |
| {{trigger.{namespace}.display.*}} | Always | Human-readable display values for trigger data. Option keys become option names, member IDs become member names, record IDs become display text. |
| {{{step_id}.output.*}} | After a step completes | The output of a previous step, referenced by its step ID. |
| {{item}} | Inside foreach | The current item in the array being iterated. |
| {{index}} | Inside foreach | The zero-based index of the current item. |
Expression examples
| Expression | Result |
|---|---|
| {{trigger.record_updated.next_data.status}} | The status field value from the updated record |
| {{trigger.gmail_receive_email.from}} | Sender email address |
| {{trigger.gmail_receive_email.attachments[0]}} | First email attachment |
| {{extract_step.output.invoice_number}} | Invoice number extracted by a previous AI extraction step |
| {{trigger.button_pressed.display.status}} | Human-readable status name (e.g., "Active" instead of "active") |
| {{trigger.record_updated.changes}} | Object containing only the fields that changed |
| {{trigger.form_submitted.data.customer_name}} | A specific field from the form submission |
| {{item.amount * item.quantity}} | Computed value inside a foreach loop |
Limitations
The expression engine intentionally keeps a small surface area for reliability:
- No optional chaining (
?.) -- use explicit checks or the if step for null handling - No nullish coalescing (
??) -- use the if step or ternary operator instead - No template literals (backtick strings with
${}) -- use the template string mode with {{ }} instead
Example: freight forwarder booking confirmation
A freight forwarding company receives booking confirmations from shipping lines via email. Before Lotics, a coordinator would manually read each email, find the booking number, check the vessel name and ETA, open the tracking spreadsheet, update the record, and forward the information to the customer. This process took 10-15 minutes per booking and was error-prone.
With a Lotics workflow, the entire process is automated:
Trigger: receive_gmail_email fires when a booking confirmation arrives in the operations inbox.
Step 1 -- AI extraction (tool_call): Uses AI to extract structured data from the email body: booking number, vessel name, container number, ETA, port of loading, and port of discharge. The AI handles varying email formats across different shipping lines.
Step 2 -- Find matching record (tool_call): Queries the Shipments table to find the record matching the extracted booking number using {{extract_step.output.booking_number}}.
Step 3 -- Update record (tool_call): Updates the matching shipment record with the extracted vessel name, container number, and ETA. Uses expressions to map each extracted field to the correct record field.
Step 4 -- Check ETA change (if): Compares the new ETA ({{extract_step.output.eta}}) against the previously recorded ETA ({{find_step.output.data.eta}}). If they differ, the workflow branches to the notification step.
Step 5 -- Notify team (tool_call, conditional): If the ETA changed, sends a notification to the operations team with the old and new ETAs, the booking number, and the vessel name. The team knows immediately about schedule changes without manually monitoring emails.
Step 6 -- Generate PDF (tool_call): Generates a PDF booking confirmation document from a company template, populated with the extracted data and the updated record fields.
Step 7 -- Email customer (tool_call): Sends the generated PDF to the customer's email address (pulled from the linked customer record), along with a summary of the booking details.
The entire process runs in under 30 seconds with no manual intervention. The coordinator's time is freed for exception handling and customer relationship management.
Example: invoice processing and reconciliation
A logistics company receives hundreds of vendor invoices monthly via email. Each invoice needs to be recorded, cross-checked against the corresponding purchase order, and routed to the finance team for payment. Manual processing took the accounts payable team 3-4 days per month.
Trigger: receive_gmail_email fires when an invoice email arrives.
Step 1 -- AI extraction (tool_call): Uses AI to extract structured data from the email attachment: vendor name, invoice number, invoice date, line items (each with description, quantity, unit price, and amount), total amount, and currency.
Step 2 -- Validate invoice data (validate): Checks that the required fields are present: vendor name is not empty, invoice number follows the expected format, total amount is positive, and at least one line item exists. All failures are reported in a single pass.
Step 3 -- Create line item records (foreach): Iterates over {{extract_step.output.line_items}}. For each item, a nested tool_call step creates a record in the Invoice Details table with the description, quantity, unit price, and amount. Uses {{item.description}}, {{item.quantity}}, and {{item.amount}} to map extracted data to record fields.
Step 4 -- Find matching PO (tool_call): Queries the Purchase Orders table to find the matching PO by vendor name and PO number from the extracted data.
Step 5 -- Compare totals (if): Compares the invoice total against the PO amount. If the difference exceeds 2%, the workflow branches to the discrepancy path.
Step 6a -- Flag discrepancy (tool_call, if difference > 2%): Creates a discrepancy flag on the invoice record with the expected PO amount, actual invoice amount, and percentage difference. Sends a notification to the finance manager with a link to the invoice and PO records for manual review.
Step 6b -- Auto-verify (tool_call, if amounts match): Sets the invoice status to "Verified" automatically. The invoice moves directly into the payment queue without manual intervention.
The finance team now processes the same volume of invoices in half a day. Discrepancies are caught immediately instead of being discovered during month-end reconciliation.
Execution and error handling
When a workflow runs, the engine creates an execution record that tracks the status of every step. You can view the full execution history for any workflow: which steps ran, what data they received, what they returned, and how long each step took.
If a step fails, the workflow stops at that step and logs the error with full context: the step type, the resolved input data (with expressions already evaluated), and the error message. Errors are never silently swallowed. You can view the execution, fix the issue (correct an expression, update a tool configuration, fix the source data), and understand exactly what went wrong.
Workflows that reach a wait or wait_for_event step save their full context and resume from where they left off when the condition is met. The execution state is persisted, so even if the server restarts, the workflow resumes correctly.
Frequently asked questions
How many workflows can I create?
There is no hard limit on the number of workflows. You can create as many as your operations require. Each workflow runs independently, and the engine handles concurrent executions across all active workflows.
What happens when a workflow step fails?
The workflow stops at the failed step and logs the error with full context: the step type, input data, and error message. You can view the execution history, identify the issue, and understand exactly what went wrong. Errors are never silently swallowed.
Can the AI assistant build workflows for me?
Yes. Describe the process you want to automate in natural language, and the AI assistant will create the workflow with the appropriate trigger, steps, and expressions. You can then review and adjust the configuration before activating it.
Can a workflow call another workflow?
Workflows do not directly call other workflows. If you need shared logic across multiple workflows, structure the steps within each workflow independently. This keeps each workflow self-contained and easier to debug.
Do workflows run in real time?
Yes. Event-based triggers (record changes, emails, webhooks, button clicks, form submissions) fire within seconds of the event occurring. Scheduled triggers run at the configured cron time. Each step executes as soon as the previous step completes.
What tools are available in tool_call steps?
The tool registry includes AI extraction, record CRUD (create, read, update, query), email sending, document generation (PDF, Excel, Word), HTTP requests, Slack notifications, and more. The AI assistant knows the full tool catalog and will select the right tool when building a workflow from your description.
How do display values work?
Before a workflow executes, the engine preprocesses raw field values into human-readable display values. Select field option keys become option names ("active" becomes "Active"). Member IDs become member names. Record IDs become display text. These display values are available at {{trigger.{namespace}.display.*}} so your workflow steps can use human-readable text in notifications, documents, and messages without extra lookup steps.
Can workflows handle files and attachments?
Yes. Email triggers provide attachments as part of their context. Tool_call steps can pass attachments to AI extraction tools, attach files to records, generate documents, and send emails with attachments. File data flows through the expression engine just like any other data.
What happens during a wait step?
The workflow engine saves the complete execution state (all step results, trigger context, current position) and schedules a resume. When the wait duration expires or the expected event occurs, the engine resumes execution from exactly where it left off. The state is persisted to the database, so it survives server restarts.
How do I debug a workflow that isn't working?
Open the workflow's execution history to see each run. For each execution, you can inspect every step: the resolved inputs (with expressions already evaluated to their actual values), the output, the duration, and any error. This lets you trace exactly where the data flows and where it breaks.