Handling Event-Triggered Automations in Agent-Native: A Technical Deep Dive

Agent-Native implements event-triggered automations through a debounced execution pipeline that fetches Gmail messages incrementally, evaluates them against AI-powered rules, and executes actions like labeling or archiving while maintaining synchronization state via watermarks and processed-ID caches.

The BuilderIO/agent-native repository provides a robust framework for building AI-native applications, including a sophisticated automation engine for email management within the Mail template. This system handles event-triggered automations by combining debounced UI triggers with cron-based scheduling, ensuring efficient processing of Gmail inbox events without duplicate handling.

Automation Pipeline Architecture

The automation engine operates as a self-contained pipeline with multiple entry points and strict isolation of concerns.

Trigger Mechanisms

The system supports two distinct invocation patterns:

  • Scheduled Execution – A periodic job calls processAutomations at the end of the engine file, iterating over all Google-OAuth connected accounts to process pending messages.
  • Event-Triggered Execution – UI components invoke triggerAutomationsDebounced(ownerEmail) (lines 30-38 of automation-engine.ts), a debounced wrapper that prevents rapid re-invocations within a short time window.

Both entry points execute within runWithRequestContext, ensuring per-user settings and LLM credentials remain isolated throughout the request lifecycle.

Token Management and Gmail API Context

Before touching the Gmail API, the engine ensures valid authentication:

  • getAccessToken (lines 83-122) checks token expiry and refreshes Google OAuth tokens when they are near expiration, persisting the refreshed credentials via saveOAuthTokens.
  • All subsequent Gmail calls use this validated accessToken, preventing mid-stream authentication failures during batch operations.

Message Fetching and Deduplication Strategy

The engine implements a robust two-layer deduplication system to handle incremental sync correctly.

Watermark-Based Incremental Sync

Agent-Native uses Gmail's historyId mechanism to fetch only new messages since the last run:

  • getWatermark / setWatermark (lines 27-38) retrieve and store the last seen historyId and timestamp from user settings.
  • fetchNewInboxMessages first attempts a Gmail History request (lines 6-33). On failure, it falls back to gmailListMessages and updates the watermark with the latest historyId from the user profile (lines 41-57).

Processed-ID Caching

To prevent action duplication within the current run window:

  • getProcessedIds / saveProcessedIds (lines 40-55) maintain a short-lived cache of already-processed message IDs, automatically pruning entries older than seven days.
  • The system limits batch size to MAX_EMAILS_PER_RUN (50 messages), returning only IDs absent from the processed cache.

AI-Powered Rule Evaluation

Rule matching leverages LLM capabilities rather than simple pattern matching, enabling complex natural language conditions.

Model Configuration and Validation

  • getAutomationModelSettings (lines 53-63) reads the selected engine and model from user settings.
  • canUseAutomationModel (lines 30-65) validates that the configured LLM provider is reachable before attempting evaluation, preventing wasted API calls on misconfigured accounts.

Batch Evaluation Strategy

  • loadActiveRules (lines 71-81) queries the automationRules table for enabled rules specific to the owner and domain.
  • evaluateRules (lines 78-119) constructs a concise prompt containing all active rules and a batch of up to 10 emails, then invokes callModel to perform the evaluation.
  • The model returns a JSON array mapping each email to its matched rule IDs, which the engine parses to determine which actions to execute.

Action Execution Flow

Once rules match, the system delegates to specialized action handlers.

Supported Automation Actions

For every matched rule, the engine parses the stored actions JSON into an array of AutomationAction objects. executeActions iterates over these, delegating each to executeAction (defined in automation-actions.ts). Supported actions include:

  • label – Applies a Gmail label, creating it if necessary via buildLabelCache (lines 21-36)
  • archive – Removes the message from the inbox
  • mark_read – Marks the message as read
  • star – Stars the message
  • trash – Moves the message to trash

Result Aggregation and Housekeeping

After action execution:

  • The watermark updates to the latest historyId
  • Processed IDs are persisted to the cache
  • processAutomations aggregates results across all accounts, returning a ProcessResult containing total messages processed and actions executed (lines 73-89)

All usage is recorded under the "automation" label for downstream cost-tracking and observability.

Practical Code Examples

Defining an Automation Rule

Store this JSON schema in the automationRules table to create a new automation:

{
  "ownerEmail": "alice@example.com",
  "domain": "mail",
  "name": "Label newsletters",
  "condition": "subject contains \"Newsletter\" OR from contains \"news@\"",
  "actions": [
    { "type": "label", "labelName": "Newsletter" },
    { "type": "archive" }
  ],
  "enabled": 1
}

Triggering Automations from the UI

Use the triggerAutomationsDebounced hook to allow users to manually run automations:

import { triggerAutomationsDebounced } from "@/templates/mail/actions/trigger-automations";

function useTrigger() {
  const owner = useUserEmail(); // assumes a hook returning the logged-in email
  const trigger = async () => {
    const { triggered, reason } = await triggerAutomationsDebounced(owner);
    if (!triggered) console.log("Automation run debounced:", reason);
  };
  return trigger;
}

Extending Automation Actions

Add new action types by extending the AutomationAction union type:

type AutomationAction =
  | { type: "label"; labelName: string }
  | { type: "archive" }
  | { type: "mark_read" }
  | { type: "star" }
  | { type: "trash" }
  // future: { type: "forward"; address: string };

Summary

  • Dual trigger system supports both cron schedules and debounced UI events via processAutomations and triggerAutomationsDebounced.
  • State management uses Gmail historyId watermarks and 7-day processed-ID caches to ensure incremental sync without duplicates.
  • AI evaluation batches up to 10 emails against active rules using configurable LLM providers, returning structured JSON match results.
  • Action execution handles Gmail mutations (label, archive, star, trash) with automatic label creation and caches the results.
  • Request isolation ensures per-user OAuth tokens and model settings apply correctly through runWithRequestContext.

Frequently Asked Questions

How does Agent-Native prevent duplicate automation runs?

The system implements debouncing through triggerAutomationsDebounced (lines 30-38 of automation-engine.ts), which rejects rapid successive triggers within a short window. Additionally, the getProcessedIds cache tracks message IDs already handled in recent runs, automatically pruning entries older than seven days to prevent indefinite storage growth while blocking re-processing.

What happens when Gmail OAuth tokens expire during automation execution?

The getAccessToken function (lines 83-122) proactively checks token expiry before any Gmail API calls. If a token is near expiration, it refreshes the credentials using Google's OAuth endpoint and persists the updated tokens via saveOAuthTokens, ensuring the automation pipeline completes without authentication interruptions.

How many emails can the automation engine process in a single run?

The engine respects a hard limit of MAX_EMAILS_PER_RUN (50 messages) per invocation. This batching prevents API rate limit exhaustion and ensures the AI evaluation prompt stays within context window constraints when processing up to 10 emails per batch in evaluateRules.

Can I create custom automation actions beyond the built-in types?

Yes. The AutomationAction type in automation-actions.ts uses a discriminated union pattern that you can extend with new action variants. You must add the type definition and implement the corresponding logic in executeAction to handle the new action's Gmail API interactions, following the existing pattern for label caching and error handling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →