# How to Implement Automations in Agent-Native Based on Event Triggers

> Learn to implement automations in Agent-Native using event triggers. Explore the trigger, processing, and action layers to build LLM-powered workflows for Gmail and execute side-effects.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-07-02

---

**Agent-Native provides a built-in automation engine that processes event-driven workflows through a three-layer architecture—trigger, processing, and action—using LLM evaluation to match Gmail messages against user-defined rules and execute side-effects like labeling or archiving.**

The BuilderIO/agent-native repository ships with a complete automation framework that monitors Gmail inboxes and reacts to new messages in real time. This guide explains how to implement automations in Agent-Native based on event triggers, covering the debounced trigger logic, LLM-powered rule evaluation, and concrete action execution against the Gmail API.

## Automation Architecture Overview

The engine splits responsibilities across three distinct layers defined in [`templates/mail/server/lib/automation-engine.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/server/lib/automation-engine.ts):

- **Trigger layer** – Decides when processing starts via cron, manual requests, or custom events. Entry points include `triggerAutomationsDebounced` and `processAutomations`.
- **Processing layer** – Loads active rules, fetches new data, evaluates conditions with an LLM, and orchestrates actions via `processAutomationsForAccount`, `fetchNewInboxMessages`, `evaluateRules`, and `executeActions`.
- **Action layer** – Performs concrete side-effects (label, archive, star, trash) against Gmail through [`templates/mail/server/lib/automation-actions.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/server/lib/automation-actions.ts).

## Triggering Automations

Agent-Native supports three trigger mechanisms, all converging on the `processAutomations()` function in [`automation-engine.ts`](https://github.com/BuilderIO/agent-native/blob/main/automation-engine.ts).

### Cron-Based Polling

A server-side cron invokes `processAutomations()` once per minute. The [`scripts/qa-dispatch-automations-smoke.ts`](https://github.com/BuilderIO/agent-native/blob/main/scripts/qa-dispatch-automations-smoke.ts) file contains the test harness that validates this behavior.

### Manual Trigger

The `trigger-automations` action ([`templates/mail/actions/trigger-automations.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/actions/trigger-automations.ts)) exposes an imperative API:

```typescript
import { defineAction } from "@agent-native/core";
import { getRequestUserEmail } from "@agent-native/core/server";

export default defineAction({
  description: "Trigger automation processing now",
  schema: z.object({}),
  http: false,
  run: async () => {
    const ownerEmail = getRequestUserEmail();
    const { triggerAutomationsDebounced } = await import(
      "../server/lib/automation-engine.js"
    );
    const result = await triggerAutomationsDebounced(ownerEmail);
    return result.triggered
      ? "Automation processing triggered."
      : `Skipped: ${result.reason}`;
  },
});

```

### Debounce Logic

To prevent overlapping runs, a per-owner in-memory map (`_lastTriggerTimeByOwner`) guarantees the engine fires no more than once every 30 seconds.

## Loading and Storing Automation Rules

Rules persist in the `automationRules` table. The server loads active configurations via `loadActiveRules` in [`automation-engine.ts`](https://github.com/BuilderIO/agent-native/blob/main/automation-engine.ts):

```typescript
async function loadActiveRules(
  ownerEmail: string,
  domain: string,
): Promise<RuleRecord[]> {
  return db
    .select()
    .from(schema.automationRules)
    .where(and(
      eq(schema.automationRules.ownerEmail, ownerEmail),
      eq(schema.automationRules.domain, domain),
      eq(schema.automationRules.enabled, 1),
    ));
}

```

Each `RuleRecord` contains:

- `condition` – A textual description evaluated by the LLM.
- `actions` – A JSON-encoded array of `AutomationAction` objects defining side-effects.

## Fetching Event Data

For Gmail integrations, the engine watches inbox events using two persistence mechanisms:

1. **Watermark** – Stored per user (`automation-watermark`) tracking the last processed Gmail `historyId`.
2. **Processed IDs** – A cache (`automation-processed-ids`) prevents duplicate message handling.

The `fetchNewInboxMessages` function attempts the Gmail **history** endpoint first, falling back to `listMessages` when the history token expires. It returns message metadata required for rule evaluation.

## Evaluating Rules with LLM

The `evaluateRules` function constructs a prompt containing active rules and a batch of up to 10 email summaries, then invokes the configured LLM (Anthropic by default) via `callModel`:

```typescript
const prompt = `You are an email classification engine...
Rules:
${rulesText}

Emails:
${emailsText}
...`;
const text = await callModel(prompt, ownerEmail, modelSettings);
const parsed = JSON.parse(cleanedResponse) as Array<{ emailId: string; matches: RuleMatch[] }>;

```

The LLM returns a JSON mapping of email IDs to matched rule IDs, which the engine converts to a `Map<string, string[]>` for execution.

## Executing Actions

For every match, `executeActions` iterates over `rule.actions` (typed as `AutomationAction[]`) and dispatches them through `executeAction` in [`automation-actions.ts`](https://github.com/BuilderIO/agent-native/blob/main/automation-actions.ts):

```typescript
switch (action.type) {
  case "label":
    const labelId = await resolveLabelId(action.labelName, ctx);
    await gmailModifyMessage(ctx.accessToken, ctx.messageId, [labelId]);
    break;
  case "archive":
    await gmailModifyMessage(ctx.accessToken, ctx.messageId, undefined, ["INBOX"]);
    break;
  // … star, trash, etc.
}

```

Concrete Gmail API calls (`gmailModifyMessage`, `gmailTrashMessage`) reside in [`google-api.ts`](https://github.com/BuilderIO/agent-native/blob/main/google-api.ts), while label resolution logic lives in [`automation-actions.ts`](https://github.com/BuilderIO/agent-native/blob/main/automation-actions.ts).

## Managing Automations via the Dispatch API

Client-side code interacts with automations through the Dispatch API defined in [`packages/dispatch/src/lib/automations.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/dispatch/src/lib/automations.ts):

- **List** – `listDispatchAutomations()` performs a `GET` request to `/_agent-native/automations`.
- **Toggle** – `setDispatchAutomationEnabled(input)` sends a `PATCH` request to the same endpoint.

```typescript
export async function listDispatchAutomations(): Promise<DispatchAutomationItem[]> {
  const response = await fetch(agentNativePath("/_agent-native/automations"));
  return response.ok ? await response.json() : [];
}

```

The UI consumes these helpers through [`templates/mail/app/hooks/use-automations.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/app/hooks/use-automations.ts).

## Practical Implementation Examples

### Manually Trigger Automations from a React Component

```tsx
import { useActionMutation } from "@agent-native/core";
import { TriggerAutomations } from "@/actions/trigger-automations";

export function RunNowButton() {
  const { mutateAsync: runNow, isLoading } = useActionMutation(TriggerAutomations);

  return (
    <button
      onClick={() => runNow()}
      disabled={isLoading}
      className="btn-primary"
    >
      {isLoading ? "Running…" : "Run Automations Now"}
    </button>
  );
}

```

### List All Automations in a Custom Dashboard

```typescript
import { listDispatchAutomations } from "@agent-native/dispatch";

export async function showAutomations() {
  const automations = await listDispatchAutomations();
  console.table(automations.map(a => ({
    ID: a.id,
    Name: a.name,
    Trigger: a.triggerType,
    Enabled: a.enabled,
  })));
}

```

### Create a New Rule Server-Side

```typescript
import { db, schema } from "../db/index.js";
import { eq } from "drizzle-orm";

export async function createAutomationRule(
  owner: string, 
  domain: string, 
  name: string, 
  condition: string, 
  actions: AutomationAction[]
) {
  await db.insert(schema.automationRules).values({
    ownerEmail: owner,
    domain,
    name,
    condition,
    actions: JSON.stringify(actions),
    enabled: 1,
    createdAt: Date.now(),
    updatedAt: Date.now(),
  });
}

```

## Summary

- **Agent-Native automations** rely on a three-layer architecture (trigger, processing, action) implemented primarily in [`templates/mail/server/lib/automation-engine.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/server/lib/automation-engine.ts).
- **Event triggers** include a one-minute cron, manual invocation via [`trigger-automations.ts`](https://github.com/BuilderIO/agent-native/blob/main/trigger-automations.ts), and debounce logic that enforces a 30-second minimum between runs.
- **Rule evaluation** uses an LLM to compare Gmail message summaries against user-defined conditions, returning structured JSON that maps messages to rules.
- **Action execution** handles Gmail side-effects (label, archive, star, trash) through [`automation-actions.ts`](https://github.com/BuilderIO/agent-native/blob/main/automation-actions.ts) with deduplication via watermarks and processed-ID caches.
- **Client management** occurs through the Dispatch API ([`packages/dispatch/src/lib/automations.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/dispatch/src/lib/automations.ts)), enabling UI components to list and toggle automations.

## Frequently Asked Questions

### How does the debounce mechanism prevent duplicate automation runs?

The `triggerAutomationsDebounced` function in [`automation-engine.ts`](https://github.com/BuilderIO/agent-native/blob/main/automation-engine.ts) maintains an in-memory map called `_lastTriggerTimeByOwner` that tracks the last invocation timestamp per user. If a trigger request arrives within 30 seconds of the previous run, the function returns `{ triggered: false, reason: "Debounce active" }`, ensuring the engine never processes overlapping batches for the same account.

### What LLM does Agent-Native use for rule evaluation?

By default, the `evaluateRules` function calls Anthropic’s Claude model via the `callModel` utility. The prompt includes the full text of active rules and a batch of up to 10 email summaries, requesting a JSON response that maps each `emailId` to an array of matching rule IDs.

### How does the engine avoid processing the same Gmail message twice?

Agent-Native implements a two-tier deduplication strategy. First, it stores a **watermark** (`automation-watermark`) containing the last processed Gmail `historyId` or timestamp. Second, it maintains a cache of **processed IDs** (`automation-processed-ids`) that prevents re-evaluation of specific message IDs even if the watermark resets or the history token expires.

### Can I trigger automations manually instead of waiting for the cron?

Yes. Import and call the `trigger-automations` action from [`templates/mail/actions/trigger-automations.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/mail/actions/trigger-automations.ts) with `useActionMutation` in the frontend, or invoke `triggerAutomationsDebounced(ownerEmail)` directly in server code. This bypasses the one-minute cron interval, though the 30-second debounce still applies.