Agent Triggers
Triggers define when an agent runs and what input it receives. Each trigger is attached to an agent and determines the execution context — including input data, metadata, and optional scheduling.
An agent can have multiple triggers, each with its own type, configuration, and schedule.
Trigger Types
Seclai supports these trigger types, each suited to different use cases:
| Trigger Type | Input Source | Runs When | Best For |
|---|---|---|---|
| Dynamic Input | Provided at runtime | On-demand (API call or UI) | Chat bots, interactive tools, API integrations |
| Template Input | Pre-defined template | On-demand or on schedule | Recurring reports, periodic tasks |
| Content Added | From new content | Automatically when content is added | Document processing, RSS monitoring |
| Content Updated | From updated content | Automatically when content changes | Change tracking, update alerts |
| Content Added or Updated | From new or changed content | Automatically on any content change | Comprehensive monitoring pipelines |
| Email Received | From an inbound email | Automatically when email arrives | Email-to-agent inboxes, forwarding workflows |
| Cloud File Added | The changed file | A file is added to a connected drive | Auto-processing new uploads (Dropbox, Google Drive) |
| Cloud File Updated | The changed file | A file changes on a connected drive | Reprocessing edited documents |
| Cloud File Added or Updated | The changed file | A file is added or changed on a drive | Comprehensive file-change automation |
Dynamic Input
The dynamic_input trigger runs an agent on demand with input provided at execution time. This is the most flexible trigger type — the caller supplies the input text, any files the trigger accepts, and optional metadata with each run.
How It Works
- A user or application sends a request to run the agent
- The request includes the input text, any uploaded files, and optional metadata
- Each file is assigned to one of the trigger's declared attachments; a run whose files don't fit is refused here
- The agent processes the input through its steps
- Results are returned to the caller
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A label for this trigger |
allow_empty_input | boolean | No | Whether to allow runs with empty input (default: false) |
default_input | string | No | Default input text used when no input is provided |
metadata | object | No | Default metadata key-value pairs |
accepted_attachments | array | No | The files a run may include, as named attachments; omitted gives one open files attachment, or, when importing an export whose trigger also takes files, keeps its declaration. See Declared attachments |
Use Cases & Examples
Building a Chat Bot
The most common use of dynamic input triggers is building conversational interfaces. Your application sends user messages as input and receives agent responses.
Agent setup:
- Trigger type:
dynamic_input - Steps: Retrieval → Prompt Call → Display Result
API call from your app:
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "What is the return policy for electronics?",
"metadata": {
"user_id": "customer-456",
"session_id": "chat-session-789",
"channel": "web"
}
}'
The agent receives the user's question as {{agent.input}}, searches the knowledge base for relevant documents, and generates a contextual response.
Using metadata in steps:
In the prompt call step, you can personalize the system prompt:
You are a helpful customer support assistant for {{organization.name}}.
The customer is contacting us via the {{metadata.channel}} channel.
Answer the following question using only the provided context:
Context: {{step.retrieval.output}}
Question: {{agent.input}}
Running an Agent from Your Application
Dynamic input triggers let any application invoke agents as an API. For example, a content management system could run an agent to analyze new articles:
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "<article><title>New Product Launch</title><body>We are excited to announce...</body></article>",
"metadata": {
"content_type": "application/xml",
"article_id": "art-2025-001",
"author": "marketing-team",
"category": "product-news"
}
}'
How metadata flows through steps:
The metadata is available to every step in the agent via {{metadata.field_name}}:
- Gate step: Check
{{metadata.category}}to route processing - Prompt call: Include
{{metadata.author}}in the system prompt for context - Webhook step: Forward
{{metadata.article_id}}to an external system - S3 step: Use
{{metadata.category}}/{{metadata.article_id}}.jsonas the object key
Form Processing
Process form submissions by passing form data as JSON input:
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "{\"name\": \"Jane\", \"question\": \"How do I upgrade?\", \"plan\": \"starter\"}",
"metadata": {
"content_type": "application/json",
"form_type": "support-request",
"priority": "normal"
}
}'
The agent can extract JSON fields, look up relevant help articles, generate a response, and send it via email — all in one workflow.
Processing Uploaded Documents
An expense agent takes one invoice PDF and up to ten receipt photos with each run, reads the invoice, describes each photo, and files everything in a cloud drive.
Trigger: dynamic_input, declaring the invoice and receipts attachments from Common declarations.
Steps: three branches, each running from the trigger.
Extract Data (id: invoice_fields, output_format: json_object):
prompt_template: |
Extract the vendor, invoice number, date and total.
Invoice: {{agent.attachments.invoice}}
For Each (id: receipt_loop, iterate_attachments: true,
attachments: ["{{agent.attachments.receipts}}"]):
body:
→ Prompt Call: "What was bought, and for how much?\nReceipt: {{step.receipt_loop.item.attachment}}"
Write Cloud Drive File (destination_path: "/expenses/{{date UTC}}/{{index}}-{{name}}",
attachments: ["{{agent.attachments}}"])
The Extract Data step reads only the invoice, because its template names that attachment. The loop iterates only the photos. The drive step writes every uploaded file, one per path, with {{index}} and {{name}} filled in for each.
API call: upload the files, then run with their IDs — see Sending files to a run. A run with no PDF is refused before any step runs.
Template Input
The template_input trigger generates the agent's input from a pre-defined template string. Templates can include substitution variables that are resolved at runtime.
This trigger type is ideal for recurring tasks where the input structure is known in advance but values change each run (e.g., today's date, metadata values).
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A label for this trigger |
input | string | Yes | The template string with {{placeholder}} substitutions |
input_content_type | string | No | MIME type for the rendered input (see below) |
allow_empty_input | boolean | No | Whether to allow runs with empty rendered input (default: false) |
metadata | object | No | Default metadata key-value pairs available as {{metadata.*}} |
Input Content Type
The input_content_type field tells Seclai how to interpret and store the rendered trigger input. Supported values:
text/plaintext/htmlapplication/jsonapplication/xml
You can also provide a templated value so the content type is determined dynamically:
{{metadata.content_type}}
Validation:
- At save-time, template expressions are accepted as strings
- At run-time, the resolved value must be one of the supported content types above
Examples
Daily summary report:
Generate a summary report for {{date America/New_York}}.
Focus on key metrics and notable events from the past 24 hours.
Weekly newsletter with metadata:
Template:
Create a {{metadata.format}} newsletter for the week of {{date UTC}}.
Topic: {{metadata.topic}}
Audience: {{metadata.audience}}
Tone: {{metadata.tone}}
Metadata:
{
"format": "HTML",
"topic": "AI Industry News",
"audience": "Technical Leaders",
"tone": "professional"
}
Schedules
Template input triggers can run on automated schedules. You can attach one or more schedules to a trigger, and each schedule defines a recurring pattern.
Schedule Frequencies
| Frequency | Required Fields | Description |
|---|---|---|
| Hourly | minute_of_hour | Runs every N hours at the specified minute |
| Daily | hour_of_day, minute_of_hour | Runs every N days at the specified time |
| Weekly | day_of_week, hour_of_day, minute_of_hour | Runs every N weeks on the specified day and time |
| Monthly | day_of_month or weekday_of_month, hour_of_day, minute_of_hour | Runs every N months on the specified day and time |
Schedule Fields
| Field | Type | Range | Description |
|---|---|---|---|
frequency | enum | — | hourly, daily, weekly, or monthly |
repeat_interval | integer | ≥ 1 | How often the schedule repeats (e.g., every 2 hours, every 3 days) |
minute_of_hour | integer | 0–59 | The minute within the hour to run |
hour_of_day | integer | 0–23 | The hour within the day to run (24-hour format) |
day_of_week | integer | 0–6 | Day of the week (0 = Monday, 6 = Sunday) |
day_of_month | integer | 1–31 or -1 to -31 | Day of the month. Negative values count from the end (e.g., -1 = last day) |
weekday_of_month | integer | 0–35 | Specific weekday occurrence within a month (e.g., 0 = first Monday, 7 = second Monday) |
Schedule Examples
Every hour at minute 30:
{
"frequency": "hourly",
"repeat_interval": 1,
"minute_of_hour": 30
}
Every day at 9:00 AM:
{
"frequency": "daily",
"repeat_interval": 1,
"hour_of_day": 9,
"minute_of_hour": 0
}
Every Monday at 8:00 AM:
{
"frequency": "weekly",
"repeat_interval": 1,
"day_of_week": 0,
"hour_of_day": 8,
"minute_of_hour": 0
}
Every 2 weeks on Friday at 5:00 PM:
{
"frequency": "weekly",
"repeat_interval": 2,
"day_of_week": 4,
"hour_of_day": 17,
"minute_of_hour": 0
}
First day of every month at midnight:
{
"frequency": "monthly",
"repeat_interval": 1,
"day_of_month": 1,
"hour_of_day": 0,
"minute_of_hour": 0
}
Last day of every month at 6:00 PM:
{
"frequency": "monthly",
"repeat_interval": 1,
"day_of_month": -1,
"hour_of_day": 18,
"minute_of_hour": 0
}
First Monday of every quarter (every 3 months):
{
"frequency": "monthly",
"repeat_interval": 3,
"weekday_of_month": 0,
"hour_of_day": 9,
"minute_of_hour": 0
}
Multiple Schedules
A single trigger can have multiple schedules. For example, to run both on weekday mornings and Sunday evenings:
- Schedule 1: Weekly, Monday at 8:00 AM (
day_of_week: 0) - Schedule 2: Weekly, Tuesday at 8:00 AM (
day_of_week: 1) - Schedule 3: Weekly, Wednesday at 8:00 AM (
day_of_week: 2) - Schedule 4: Weekly, Thursday at 8:00 AM (
day_of_week: 3) - Schedule 5: Weekly, Friday at 8:00 AM (
day_of_week: 4) - Schedule 6: Weekly, Sunday at 6:00 PM (
day_of_week: 6)
Seclai automatically detects redundant schedules (ones that would never produce new runs compared to existing schedules) and intersecting schedules (ones that would fire at the same time), warning you before saving.
Content Added
The content_added trigger runs the agent automatically whenever new content is added to a connected knowledge base.
How It Works
- A content source fetches new content (e.g., a new RSS article, a new web page)
- The content is indexed into the knowledge base
- All
content_addedtriggers linked to that knowledge base fire - The new content's text becomes the agent's input
- Content metadata (title, author, URL, publish date, etc.) is available via
{{metadata.*}}
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A label for this trigger |
knowledge_base_id | UUID | Yes | The knowledge base to monitor for new content |
metadata | object | No | Additional default metadata merged with content metadata |
Examples
RSS feed summarizer:
When a new article arrives in a knowledge base fed by RSS sources, automatically summarize it and send a notification:
- Trigger:
content_addedon the "Industry News" knowledge base - Step 1: Prompt Call — Summarize
{{agent.input}}using the article metadata - Step 2: Send Email — Email the summary to the team
The content metadata from the source is automatically available:
Summarize this article:
Title: {{metadata.title}}
Author: {{metadata.author}}
Published: {{metadata.published_date}}
Source: {{metadata.source_url}}
Content:
{{agent.input}}
New document classifier:
Automatically tag and categorize new documents:
- Trigger:
content_addedon a document knowledge base - Step 1: Prompt Call — Classify the document and output JSON
- Step 2: Extract JSON — Parse the classification result
- Step 3: Webhook Call — Send classification to your CMS
Content Updated
The content_updated trigger runs the agent automatically whenever existing content in a connected knowledge base is updated (e.g., a web page's content changes, an RSS article is revised).
How It Works
- A content source detects that previously indexed content has changed
- The updated content is re-indexed in the knowledge base
- All
content_updatedtriggers linked to that knowledge base fire - The updated content becomes the agent's input
- Content metadata is available via
{{metadata.*}}
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A label for this trigger |
knowledge_base_id | UUID | Yes | The knowledge base to monitor for content changes |
metadata | object | No | Additional default metadata merged with content metadata |
Examples
Change tracking:
Detect and report significant changes to monitored web pages:
- Trigger:
content_updatedon a competitor monitoring knowledge base - Step 1: Prompt Call — Analyze what changed and assess significance
- Step 2: Gate — Only continue if changes are significant
- Step 3: Send Email — Notify the team about the changes
Compliance monitoring:
Re-analyze documents when they change for regulatory compliance:
- Trigger:
content_updatedon a policy document knowledge base - Step 1: Prompt Call — Check updated content against compliance rules
- Step 2: Extract JSON — Parse compliance check results
- Step 3: Webhook Call — Update compliance dashboard
Content Added or Updated
The content_added_or_updated trigger combines both behaviors — it fires whenever content is added to or updated in a connected knowledge base. This is the most common content trigger type when you want to process all content changes uniformly.
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A label for this trigger |
knowledge_base_id | UUID | Yes | The knowledge base to monitor |
metadata | object | No | Additional default metadata merged with content metadata |
Example
Content processing pipeline:
Process all content changes through a standardized pipeline:
- Trigger:
content_added_or_updatedon a documentation knowledge base - Step 1: Prompt Call — Extract key topics and generate a summary
- Step 2: Extract JSON — Parse structured output
- Step 3: Write AWS S3 Object — Store the processed result
- Step 4: Webhook Call — Notify the search index to update
Email Received
The email_received trigger turns an agent into a virtual email inbox. The agent runs whenever email arrives at its address — the message body becomes the agent's input and any attachments become agent input attachments.
Account‑level email settings — branded sender identity, custom and vanity domains, DMARC monitoring, recipient opt‑outs, the blocked‑senders list, and the per‑plan rate limits — are documented on the Email page. This section covers the per‑agent trigger behavior.
Inbox Addresses
Every email trigger has a built-in address derived from the agent's ID (a short, base32-encoded form):
<agent-id>@agent.seclai.com
You can also set a friendly alias, which adds a second address scoped to your account:
<alias>.<account-id>@agent.seclai.com
An alias may contain letters, digits, +, ., and -, must be 1–32 characters, must not start or end with +, ., or -, must not look like a UUID, and must be unique in your account. Both addresses route to the same agent; you can see and copy them from the trigger's configuration. (For convenience, the long unencoded UUID form of the address — with or without dashes — also routes to the agent, so any address you've already shared keeps working.)
Branded Domains
On the Team and Pro plans you can send and receive agent email from your own
branded domain instead of the shared agent.seclai.com — a vanity subdomain
(<name>.seclai.com, no domain of your own required; Team and Pro) or a custom
domain you own (agent.mycompany.com; Pro). You configure, verify, and monitor
these on the Email page in the left sidebar, under Domains. The full walkthrough — the exact DNS
records for each option, ownership verification, DMARC monitoring, choosing a
primary domain, and safe removal — lives on the Email page.
Once a domain is verified and set as your primary domain, agent addresses
become clean — <agent-id>@yourdomain and <alias>@yourdomain (no account suffix
needed, since the domain is dedicated to your account) — and agent replies are
sent from that domain. Both addresses work on every verified domain you have,
and on the shared agent.seclai.com form, and are shown on your primary domain.
Changing your primary domain doesn't change an alias; if you remove a domain,
addresses on that domain stop working, but the alias keeps working on your other
domains and the shared form. Vanity and custom domains cover both
receiving and sending. See
Email → Sending domains for the details.
How It Works
- Someone sends an email to one of the agent's addresses
- The message is received, authenticated (SPF/DMARC), and screened for spam/viruses
- The body becomes the agent's input; attachments are assigned to the trigger's declared attachments. If the email has no body or usable attachments, the subject line is used as the input instead
- The sender, subject, and received time are captured as run metadata
- The agent processes the email through its steps
Use Cases & Examples
Email-to-Agent Support Inbox
Turn an agent into a self-serve support inbox. Mail sent to the agent's address is authenticated and screened, scanned, answered against a knowledge base, and a reply is sent straight back to the sender.
Agent setup:
- Trigger type:
email_received(optionally with anallowed_sendersallowlist) - Steps: Retrieval → Prompt Call → Send Email (to
{{metadata.email_from}})
The reply step targets the sender via the captured {{metadata.email_from}} field, and the system prompt can use {{metadata.email_subject}} for context:
You are a support assistant. A customer emailed with the subject
"{{metadata.email_subject}}".
Answer their message using only the provided context:
Context: {{step.retrieval.output}}
Message: {{agent.input}}
Triage & Forwarding
Use an inbox to classify incoming mail and route it onward — for example, extracting the request type and priority, then opening a ticket in an external system via a webhook.
Agent setup:
- Trigger type:
email_received - Steps: Extract Data (classify) → Gate (route by priority) → Webhook Call (create ticket)
Invoice Inbox
Suppliers email invoices to an accounts inbox. The agent reads each invoice, saves the PDF to the finance drive, and replies to the sender with what it recorded — or, when the email's files don't fit (no PDF, two PDFs, or an extra file such as a cover letter), tells the sender what was wrong and records nothing.
Trigger: email_received with an allowed_senders allowlist, declaring one attachment and running anyway when the files don't fit. These are two trigger settings, set on the trigger editor's Attachments step (through the API, PUT …/accepted-attachments and PUT …/email-config respectively):
{
"accepted_attachments": [
{
"name": "invoice",
"label": "Invoice",
"content_types": ["application/pdf"],
"min": 1,
"max": 1
}
],
"run_on_attachment_mismatch": true
}
Steps:
if_else (conditions: [{target: "metadata.email_attachment_issues", operator: "$not_empty"}])
then → Send Email (reply_to_sender: true, subject: "Re: {{metadata.email_subject}}",
text_body: "We couldn't process your email:\n{{metadata.email_attachment_issues}}\nPlease send the invoice as one PDF, with no other files.",
attachments: [])
else → Extract Data (id: fields, output_format: json_object):
prompt_template: |
Extract the invoice number, due date and total.
Invoice: {{agent.attachments.invoice}}
→ Write Cloud Drive File (destination_path: "/invoices/{{date UTC}}/{{name}}",
attachments: ["{{agent.attachments.invoice}}"])
→ Send Email (reply_to_sender: true, subject: "Re: {{metadata.email_subject}}",
text_body: "Recorded: {{step.fields.output}}", attachments: [])
A new Send Email step starts with every file on its input, so both replies set attachments: [] to avoid sending the sender's files back. Strict mode (run_on_attachment_mismatch: false) would instead fail the run and say nothing to the sender; see Attachments: strict or run anyway.
Replying to the Sender
An agent can email a response back to the person who wrote in. Add a Send Email step and turn on Reply to original sender (reply_to_sender) — the recipient is resolved at run time from the message's authenticated sender, so you never set an address yourself. For safety this only sends when the inbound message passed SPF or DMARC (a spoofed sender gets no reply), and it only works on email_received agents. A good subject is Re: {{metadata.email_subject}}. Replies are threaded (with a Reply-To back to the agent's inbox), so when the sender replies it returns to the agent and can continue the conversation.
To include the message being answered, as a mail client does, also turn on Quote the original message (quote_original_message). The reply then ends with an attribution line ("On <date>, <sender> wrote:") and the original, quoted in both the HTML and plain-text bodies. The quote keeps the original's text, basic formatting and links; images, styles and scripts are removed and plain text is escaped, so the quote cannot change how the rest of your reply looks or behaves. The quote is added only when the reply goes to the address in the original's From line, so nobody receives another person's message. A Simulate Email run has no received message, so its reply carries no quote.
Agent emails are sent from a noreply@agent.seclai.com address (or, for a reply, the agent's own inbox address) rather than a Seclai address, so the From aligns with the agent and replies route back to it. A reply's inbox address — its Reply-To, and its From whenever the agent can send — is the address the sender wrote to (an alias stays an alias), as long as that address is on the domain replies are sent from (your primary domain, or agent.seclai.com if you have none); otherwise it comes from the agent's ID address on that domain.
If you instead send a notification to a registered user (a normal recipient_user_id send, not a reply) but still want their reply to come back to the agent, turn on Reply to agent inbox (reply_to_agent_inbox) on the Send Email step. It sets Reply-To to the agent's inbox and only takes effect when the agent has an email_received trigger — useful for a sustained conversation that starts from a notification.
Multi-Turn Conversations (Trusted Memory)
For a back-and-forth email assistant, do not rely on the quoted reply chain in the incoming email body — it's written by the sender and can be forged or trimmed. Instead, keep the conversation in a memory bank keyed by the authenticated sender, alone or together with the email thread: record the new email as a user turn (which also returns the history so far), answer in context, store the answer as an assistant turn, and reply. Because the sender is authenticated (keep Require sender authentication on), that history is trustworthy server-side state. The key decides whether each sender has one history or one per email thread — see Per sender or per thread.
Best practice: turn on Strip quoted reply chains on that memory bank (create page or Configuration tab). Each reply usually quotes the previous message (and the whole thread when Quote the original message is on), so without it every stored email repeats text the bank already holds — the bank grows faster, and so does the cost of every prompt that loads the history. See Memory Banks, and Email conversation agent for the whole build step by step.
Per Sender or Per Thread
The key on the memory steps decides which emails share a history. There are two choices:
| Key | One history for | Typical agents |
|---|---|---|
{{metadata.email_from}}:{{metadata.email_thread_id}} | Each sender, in each email thread | Support inbox, ticket intake, order or booking questions |
{{metadata.email_from}} | Each sender, across all of their threads | Personal assistant, account manager, coach, tutor, concierge |
Per thread: each email someone starts is its own conversation. Use this when people write in about separate, self-contained requests, and an answer to one should not be shaped by another. Say Dana emails a support inbox:
- Dana starts an email, "My invoice total looks wrong." The agent sees no history and answers.
- Dana replies, "It's invoice 1042." The agent sees the invoice conversation and answers in context.
- A week later Dana starts a new email, "How do I add a teammate?" The agent sees no history, so the invoice problem doesn't leak into the answer.
- Dana replies to the old invoice thread, "Still wrong." The agent picks the invoice conversation back up.
This keeps each conversation short and on topic. The trade-offs:
- No memory across threads. The agent knows nothing about a person's earlier threads, so it can't say "as you mentioned last week". To remember facts about a person across threads while keeping conversations separate, see Both: per-thread conversations, per-sender facts.
- It depends on the sender's mail client. A client that doesn't send a
Referencesheader makes each reply look like a new thread, so the agent forgets the conversation (see More to know about the per-thread key).
Per sender: one continuous conversation with each person. Use this when the relationship matters more than any single request, and the agent should build up context about someone over time. Say Sam uses an assistant agent by email:
- Sam starts an email, "I'm vegetarian and I'm in Lisbon next week." The agent sees no history and answers.
- Days later Sam starts a new email, "Find me somewhere for dinner Friday." The agent sees the earlier conversation, so it suggests vegetarian places in Lisbon without being told again.
- Whether Sam replies or starts a fresh email, the agent sees the same history.
The trade-offs:
- Unrelated topics mix. Everything a person has sent is in one history. Tell the Prompt Call to focus on the latest email and use earlier turns only as background.
- Only recent turns reach the answer. Append Chat Turn passes the Prompt Call the most recent 100 turns, so older messages drop out of view.
- The history needs limits. A per-sender history keeps growing for as long as the person writes. Set compaction on the bank, with a compaction prompt and a turn limit of 100 or less, so old turns are summarized into the history instead of dropping out of view. Also turn on Strip quoted reply chains, and pick a retention setting that fits how long history stays useful.
Both: per-thread conversations, per-sender facts
A support inbox that keeps each request separate but still remembers who a customer is needs both. Use two memory banks:
- A conversation bank for the back-and-forth of each request, keyed per thread:
{{metadata.email_from}}:{{metadata.email_thread_id}}. - A general bank for lasting facts about the person, such as their plan, their preferences, or a device they use, keyed per sender:
{{metadata.email_from}}.
Build the steps in this order:
- Search Memory on the general bank, key
{{metadata.email_from}}, query{{agent.input}}. It looks up what's known about the sender. It has to come first, because the Prompt Call in step 3 must follow directly after an Append Chat Turn step. - Append Chat Turn on the conversation bank, per-thread key, speaker
user, content{{agent.input}}. Set the content explicitly; left blank, it would store the search result from step 1 instead of the email. - Prompt Call with Use step input as messages on, so it gets the thread's history. It doesn't receive the search result as input, so reference it in the system prompt with
{{step.<search-step-id>.output}}, for example "Known about this customer: …". - Append Chat Turn (speaker
assistant) and Send Email, both after the Prompt Call, as in the full build. - To record new facts, add a second Prompt Call after the answer that pulls out anything worth remembering from
{{agent.input}}, followed by Write to Memory on the general bank with key{{metadata.email_from}}. Write to Memory stores whatever its input is, so without the extraction step it would save every reply in full.
Set the key on both general-bank steps. A blank Search Memory key searches every sender's facts. See Memory steps for the step fields.
Not sure which to use? Ask whether the answer to someone's new email should depend on what they wrote in a different email:
- No: use per thread.
- Yes, the whole conversation matters: use per sender.
- Only a few facts about the person matter: use both.
The AI agent builder suggests per thread unless you describe an agent that should remember people across emails.
How to set it up. Use the same key on every memory step for that bank: the Append Chat Turn step that records the email, the one that records the answer, and any Load Chat History or Search Memory step. A step with a different key reads or writes a different history. Search Memory's key is optional, but a blank key searches every conversation in the bank, including other senders' conversations, so always set it. See Email conversation agent for the full build.
What counts as a thread. Replies to an email, and replies to those replies, share its email_thread_id, including replies to the agent's own answers. A new email that isn't a reply starts a new thread.
More to know about the per-thread key
- It depends on the sender's mail client. The thread is identified from the
Referencesheader, which most mail clients send. A client that sends onlyIn-Reply-Tomakes each reply look like a new thread, so the agent starts over every time. If people say the agent forgets what they said earlier in a thread, switch to the per-sender key. - Each participant has their own history. Both keys start with the sender, so when someone CC'd on a thread replies, their history is separate from the original sender's.
- Never key on
email_thread_idalone. It comes from headers the sender writes. Withoutemail_fromin front, anyone who knew the Message-ID of the email that started another thread could load that thread's history into their own reply.
Both keys need sender authentication. With Require sender authentication off, email_from isn't verified, so neither key is a trustworthy identity.
Changing the key on an existing agent. Memory already stored stays under the old key and isn't moved. After the change, conversations start fresh under the new key, and the old entries expire under the bank's retention setting (never, if retention is Indefinite).
Testing with Simulate Email. With the per-sender key, simulated emails from the same From address share a history. With the per-thread key, each simulated email starts a new thread, unless you fill in In-Reply-To, which then becomes the thread. To simulate a follow-up, open the advanced email fields and enter the same Thread ID on each run.
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
alias | string | No | Friendly local part, unique in your account, for <alias>.<account-id>@agent.seclai.com and <alias>@ on each verified domain. Leave empty to use only the agent-ID address. |
allowed_senders | string[] | No | Allowlist of sender addresses and/or bare domains. Leave empty to accept mail from any sender. |
ignore_auto_generated | boolean | No | When true (the default), automated mail — auto-replies, vacation responders, bulk/mailing-list mail, and bounces — is dropped so the agent can't ping-pong with an auto-responder. Turn it off for agents that should process automated mail (alerts, reports, newsletters). |
require_sender_auth | boolean | No | When true (the default), the sender must pass SPF or DMARC even on an open inbox (no allowlist), so the address can't be flooded with spoofed mail. Turn it off only to accept mail from senders that can't be authenticated. |
queue_on_quota | boolean | No | When true, an email that exceeds your plan's hourly inbound-email rate limit is queued (shown as a Queued run) instead of failed, and runs automatically as capacity frees over the following minutes. When false (the default), over-limit mail fails immediately. Queuing never bypasses the limit — it just catches up in order. (A message with insufficient account credits still fails immediately, since queuing wouldn't help.) |
accepted_attachments | array | No | The files an email may carry, as named attachments; omitted gives one open files attachment, or, when importing an export whose trigger also takes files, keeps its declaration. See Declared attachments. |
run_on_attachment_mismatch | boolean | No | When true, an email whose attachments don't fit the trigger's declared attachments still runs: files that don't fit are left out, and email_attachment_issues lists the problems. When false (the default), the run fails. See Attachments: strict or run anyway. |
The Send Email step also gains reply_to_sender, quote_original_message and reply_to_agent_inbox options for email_received agents (see Replying to the Sender).
Captured Metadata
Each run records the email context as metadata, available to steps via {{metadata.field}}:
| Metadata Key | Description |
|---|---|
email_from | The sender address (authenticated envelope sender) |
email_to | The address the email was sent to |
email_subject | The subject line |
email_cc | Carbon-copied recipients (when present) |
email_reply_to | Reply-To address (when present) |
email_date | The sender's claimed send time (Date header) |
email_in_reply_to | Message-ID this email replies to, for threading |
email_received_at | When the email was received |
email_message_id | The Message-ID header; replies thread on it |
email_thread_id | Message-ID of the email that started the thread |
email_auto_generated | true/false — whether the message is automated |
email_spf | SES SPF check result (when known) |
email_dkim | SES DKIM check result (when known) |
email_dmarc | SES DMARC check result (when known) |
email_attachment_issues | One line per attachment problem, and why (when there is any) |
Header-derived fields (email_cc, email_reply_to, email_date, email_in_reply_to, email_message_id, email_thread_id) are informational and not authenticated — only email_from is verified by SPF/DMARC. In particular, never reply to email_reply_to; a reply-to-sender step always uses the authenticated email_from.
Security & Limits
- Sender allowlist: Optional, but recommended. Each entry is either a full email address (e.g.
alice@example.com) or a bare domain (e.g.example.com, which also matches sub-domains likemail.example.com). Wildcard syntax (*@…) is not used — a bare domain already covers every address at that domain. When an allowlist is set, Seclai additionally requires the email to pass SPF or DMARC, so a spoofed sender cannot match it. (A DKIM signature alone is not enough — it can authenticate a domain different from the one being matched.) - Automated mail (loop prevention): By default, machine-generated mail — auto-replies / vacation responders, mailing-list and bulk mail, and bounces — is dropped before a run, so an auto-responder on the other end can't ping-pong with your agent. Turn off Ignore automated email on the trigger only if the agent is meant to process automated mail (alerts, reports, newsletters).
- Sender authentication on open inboxes: By default, even an open inbox (no allowlist) requires the sender to pass SPF or DMARC, so the guessable agent address can't be flooded with spoofed mail. Turn off Require sender authentication on the trigger only to accept mail from senders that can't be authenticated — with it off, anyone can spoof any sender address to the inbox.
- Open addresses can be abused: Leaving the allowlist empty accepts mail from any (authenticated) sender — this is your choice, but anyone who learns the address can trigger runs (consuming credits) and flood it up to the hourly rate limit. Set an allowlist if the address might become known publicly.
- Spam & viruses: Messages that fail the SES spam check are discarded. Messages that fail the virus check — or that SES could not virus-scan (e.g. very large attachments) — are also discarded, so an agent never runs on unscanned content.
- Rate limits: Inbound email triggers are rate-limited per plan (the hourly quota per plan), plus a hard per-trigger ceiling. Mail that exceeds the limit, or is sent to an open agent with insufficient credits, surfaces as a failed agent run so you can see it — unless you enable Queue over-limit email (
queue_on_quota), in which case over-limit mail is queued and caught up automatically as capacity frees (see the field above). - Sustained overload: If inbound mail keeps arriving faster than your plan can process it and the queued backlog grows beyond what can realistically catch up, Seclai sends a mandatory inbound email overload alert to the account owner and automatically pauses the worst-affected agent (and auto-blocks a single flooding sender). This safeguard is covered in full on the Email → Alerts page — re-enable a paused agent from its page once the volume subsides or your plan is upgraded.
- Blocked senders: A sender on your account's blocked-senders list is turned away before a run, shown as a Blocked sender rejection.
- Rejections: Mail discarded before a run (unknown alias, unauthorized sender, spam, flood, automated mail, blocked sender) appears in the agent's Traces tab as a trace with a Rejected status — open it to see why the email didn't run, or filter the Traces status to Rejected to see only these. The same records are also available via the API (
GET /api/agents/inbound-email-rejections) and thelist_inbound_email_rejectionsMCP tool. To be proactively notified when a sender is turned away by your allowlist (so you can add a legitimate correspondent you forgot), enable the Inbound Email Turned Away alert on the agent's Alerts tab. - Attachments: Attachments must fit the trigger's declared attachments, the same as the interactive run path, and at most 100 are processed per email. An image the message body displays, such as a signature logo, reaches the run when a declared attachment takes it, and is otherwise left out without failing the run. Whether a mismatch fails the run is up to you: see Attachments: strict or run anyway.
Attachments: strict or run anyway
An email's attachments don't fit when any of these is true:
- A file fits none of the trigger's declared attachments by type and filename pattern, or every one it fits already holds its
max. On a trigger that declares none, that is every file sent with the message. An image the message body displays never counts here. A file's type is the one its upload is stored as, so a PDF the sender's mail client labelsapplication/octet-streamis a PDF. - A declared attachment receives fewer files than its
min. - More than 100 files are attached. Images the message body displays don't count toward this; past the limit they are left out without failing the run.
- A file is refused on upload: an SVG that can't be made safe, or a file over the size limit.
A calendar invite or forwarded message sent without a file name is named for you (invite.ics, or the forwarded message's subject with .eml), and the files inside a forwarded message are attachments too, under their own names (prefixed with the forwarded message's name when a file of that name is already attached). Two parts are never attachments, so they never fail a run: any other part with no usable file name, and a part that can't be decoded. Both are listed in email_attachment_issues in either mode.
An email trigger created before invites and forwarded files were included keeps skipping them in strict mode. Turn on run anyway to include them.
Strict (the default, run_on_attachment_mismatch: false) fails the run. The reason shows on the failed run in Traces, and any run-failed alert you have set up fires, but the sender hears nothing.
Run anyway (run_on_attachment_mismatch: true) starts the run, and {{metadata.email_attachment_issues}} holds one line for each problem. Past about 1,000 characters, the remaining lines are replaced by a count, such as …and 12 more. Files that fit no declared attachment, fit only attachments already holding their max, are over the limit, or are refused on upload are left out; every other file reaches the run under the attachment it was assigned to. An attachment that received fewer than its min gets a line saying what was expected, and a step that references it runs without those files.
To tell the sender, branch on the issues. This suits a trigger that declares attachments. For one that declares none, don't branch: every email that carries a file would get the "couldn't use your attachments" answer.
if_else (conditions: [{target: "metadata.email_attachment_issues", operator: "$not_empty"}])
then_steps:
└─ send_email (reply_to_sender: true,
subject: "Re: {{metadata.email_subject}}",
text_body: "We couldn't use your attachments:\n{{metadata.email_attachment_issues}}")
else_steps:
└─ … the normal workflow …
Turn it on with Run even when attachments don't fit on the trigger editor's Attachments step, run_on_attachment_mismatch on PUT /api/agents/{agent_id}/triggers/{trigger_id}/email-config, or the set_email_trigger_config MCP tool. You can also ask the AI agent builder, for example "reply asking for the right file when one is missing". It sets the option when your request decides it; if your request mentions wrong or missing files without saying what should happen, it builds the workflow and asks which you want.
Examples
1. A required file, private inbox. An accounts agent's trigger declares an attachment invoice (label Invoice, content_types: ["application/pdf"], min: 1), and its steps read {{agent.attachments.invoice}}. A colleague attaches brief.docx instead.
- Strict: the run fails, and Traces shows why:
'Invoice' needs at least 1 file (application/pdf); 0 receivedand'brief.docx' (application/vnd…) is not an attachment this agent accepts. - Run anyway: the run starts without
brief.docx, the issues hold the same two lines, and the branch above replies asking for the invoice.
2. A filename pattern, public inbox. A quotes agent's trigger declares an attachment quote with name_pattern: "quote-*.pdf", and a customer attaches scan.pdf.
- Strict: the run fails, and the customer never learns why.
- Run anyway: the issues read
'scan.pdf' (application/pdf) is not an attachment this agent accepts, and the branch above replies asking for the quote.
3. A number of files. A comparison agent's trigger declares an attachment contracts with min: 2, and the sender attaches one contract.
- Strict: the run fails with
'contracts' needs at least 2 files; 1 received. - Run anyway: the run starts with the one file, and the agent can reply asking for the second.
4. A conversational agent receiving a signed reply. An agent that answers the email body declares no attachments, and a reply arrives with a signature logo.
- Strict or run anyway: the run answers the body. The logo is shown in the body, not sent with it, so it is left out without an issue.
5. Too many files, or a refused one. An email carries 120 files, or an SVG that can't be made safe among PDFs.
- Strict: the run fails, on
too many attachments (120 > 100)or on the refused file. - Run anyway: the first 100 files, or the PDFs, reach the run. Each file left out gets a line with its reason, such as
'file-101.pdf': over the 100-file limit, until the count takes over.
Which should I choose?
| Your inbox | Choose |
|---|---|
| Written to by your own systems with fixed file names | Strict |
| Written to by people, especially outside your team | Run anyway |
| Conversational, where replies carry signatures | Run anyway |
| A pipeline where a wrong file should page someone | Strict, with a run-failed alert |
Blocking senders
Your account keeps a blocked-senders list (Email → Blocked senders in the left sidebar) that applies to every EMAIL_RECEIVED agent on the account. Mail from a blocked sender is quietly rejected before any agent runs — no run, no credits, no notification — and appears only as a Blocked sender rejection in the agent's Traces. You can block a single address or a whole domain, manually or automatically (via governance policies or the overload safeguard). Only SPF/DMARC-authenticated senders are matched, since an unauthenticated From is forgeable.
The full reference — match types, manual vs. automatic blocking, one-click blocking from a rejection, and owner/admin gating — is on the Email → Blocked senders page.
Testing
Use Simulate Email on the trigger to test the agent without sending real mail — fill in From, Subject, body, metadata, and attachments, and it runs the agent exactly as an inbound email would (the simulation bypasses the sender allowlist and spam checks, since you're the authenticated owner). Exporting an agent carries its sender allowlist over; the custom alias is not exported (it's account-scoped, and the imported agent gets a new default address).
Accepting Files
Two trigger types take files: Dynamic Input (files uploaded with a run, from the app, the API, MCP or a parent agent's Call Agent step) and Email Received (an email's attachments). Every other trigger type takes none; cloud file triggers pass the changed file as the run's input instead.
A file-taking trigger declares the files it accepts as named attachments — for example an invoice that must be one PDF, and up to ten optional photos. The declaration does three jobs:
- It is the contract with whoever sends files. Before any step runs, each file is assigned to one declared attachment. A run whose files don't fit is refused, with a message naming each problem, so a wrong file never reaches a step or spends credits.
- It is what steps choose from. A step reads the files of one attachment with
{{agent.attachments.invoice}}, or every file with{{agent.attachments}}. - It tells callers what to send. The Run Agent modal, the API and MCP list the declared attachments, so a caller can stage the right files first time.
The field-by-field reference is Declared attachments in the Attachments guide.
Declaring attachments in the editor
The trigger editor walks through four steps: Trigger type, Trigger settings, Attachments and Metadata. On Attachments, each declared attachment is a card; Add attachment adds another. Each card has:
| Field | What to enter |
|---|---|
| Name | How steps refer to it. The card shows the reference to use, such as {{agent.attachments.invoice}} |
| Label | Optional. Shown to people: in the Run Agent modal and in the message when a run is refused |
| Content types | Pick or type MIME types, or a type/* wildcard such as image/*. Leave empty to accept any type |
| Filename pattern | Optional glob the file's name must also match, ignoring case, such as invoice-*.pdf |
| Minimum files | A run with fewer is refused, unless an email trigger is set to run anyway |
| Maximum files | Empty is no limit |
Switching a trigger from a type that takes no files to Dynamic Input or Email Received fills in one open attachment, files, which takes any number of files of any type. Remove every card and the trigger accepts no files. On an email trigger this step also holds Run even when attachments don't fit; see Attachments: strict or run anyway.
Outside the editor, set the declaration with accepted_attachments when creating an agent through the API, with PUT /api/agents/{agent_id}/triggers/{trigger_id}/accepted-attachments (GET /api/agents/{agent_id} returns the trigger_id), or with the set_trigger_accepted_attachments MCP tool. The AI agent builder declares attachments for the workflows it generates.
Common declarations
Any files, none required. What a new file-taking trigger starts with. Steps read the files with {{agent.attachments.files}} or {{agent.attachments}}.
[{ "name": "files", "content_types": [], "min": 0, "max": null }]
Exactly one PDF. A contract reviewer that cannot do anything without its contract.
[
{
"name": "contract",
"label": "Contract",
"content_types": ["application/pdf"],
"min": 1,
"max": 1
}
]
A required document plus optional extras. An expense agent that needs the invoice and can use photos of the receipts. Steps can treat the two differently: the invoice goes to an Extract Data step, the photos to a For Each loop.
[
{
"name": "invoice",
"label": "Invoice",
"content_types": ["application/pdf"],
"min": 1,
"max": 1
},
{
"name": "receipts",
"label": "Receipt photos",
"content_types": ["image/*"],
"min": 0,
"max": 10
}
]
A filename convention. A quotes inbox that only takes files named the way your sales tool exports them.
[
{
"name": "quote",
"content_types": ["application/pdf"],
"name_pattern": "quote-*.pdf",
"min": 1,
"max": 1
}
]
A range. A comparison agent that needs between two and five photos.
[
{
"name": "photos",
"content_types": ["image/*"],
"min": 2,
"max": 5
}
]
No files. A text-only chatbot or an email agent that answers the message body. An empty declaration refuses any run that carries a file, except that an email arriving at a trigger set to run anyway runs without its files; for how an email's signature images are treated, see example 4 below the email trigger.
[]
How files are assigned
Before the first step runs, each file is assigned to exactly one declared attachment that accepts it: its type must match one of the attachment's content types (or the list is empty), and its name must match the filename pattern when one is set. When a file fits more than one attachment, the files are placed so that every attachment receives its minimum whenever that is possible — so a narrow invoice (one PDF, required) declared next to a broad other (any type) works whichever order the files arrive in.
The run is refused when a file fits no attachment that still has room, or an attachment receives fewer than its minimum. With the expense declaration above:
| Files sent | Result |
|---|---|
invoice.pdf, front.jpg, back.jpg | Runs: invoice holds the PDF, receipts the two photos |
invoice.pdf | Runs: receipts is optional, and a step reading it receives no files |
front.jpg | Refused: 'Invoice' needs at least 1 file (application/pdf); 0 received |
march.pdf, april.pdf | Refused: 'april.pdf' (application/pdf) is one file too many: 'Invoice' takes at most 1 file |
invoice.pdf, notes.docx | Refused: 'notes.docx' (application/vnd…) is not an attachment this agent accepts |
Where the refusal lands depends on the trigger:
- Dynamic Input — the run is never created. The API answers
400with the problems listed, the Run Agent modal shows them, and an upload that no declared attachment accepts, by type and filename pattern, is refused when it is uploaded. - Email Received — the run fails visibly in Traces, or, with Run even when attachments don't fit, runs without the files that don't fit and lists the problems in
{{metadata.email_attachment_issues}}. See Attachments: strict or run anyway. - A Call Agent step — the parent's step fails, naming the problems.
Reading the files in steps
Steps reach the declared files through the attachment reference grammar. The toolbar's Agent Input menu and each step's Attachments field offer the declared attachments by name.
In a prompt. Put the reference where the file belongs; the model receives the file (or, when it can't read that type, a conversion) and the prompt shows a labelled marker in its place. Naming the file on its own line keeps the prompt easy to read:
Extract the invoice number, date and total.
Invoice: {{agent.attachments.invoice}}
In a step's Attachments list. Steps that send, store or loop over files, such as Send Email, Write Cloud Drive File or For Each, take a list of references (see which steps):
["{{agent.attachments.receipts}}"]
Every file. {{agent.attachments}} selects every file the run received, whatever attachment it was assigned to.
Implicitly. A Prompt Call (with a plain-text prompt, not a JSON template or step-input messages), Extract Data or Evaluate step whose template uses {{agent.input}} and names no agent attachment receives every file the run received. Once the template names one, such as {{agent.attachments.invoice}}, the step receives only the attachments it names. {{input}} is a separate source, so naming an agent attachment does not narrow what {{input}} brings. To be exact about what a model sees, set the step's Attachments list: the step then receives exactly those files.
The Attachments guide has complete worked examples.
Changing a declaration
Saving a trigger is refused when it would stop declaring an attachment a step references, and so is changing it to a trigger type that takes no files. The message names what is missing, such as The dynamic input trigger does not declare the attachments the agent references: invoice. To rename an attachment, add the new one, point the steps at it, then remove the old one.
Saving a step is refused in the same way when it references an attachment the triggers don't declare (undeclared_attachment). An index, glob or filename reference to the agent's files, such as {{agent.attachments[0]}}, is saved as {{agent.attachments}} — every agent input file — with a warning; declare an attachment to choose one file.
Sending files to a run
-
App — the Run Agent modal's Attach files tab lists the attachments the trigger accepts. Drop files in and add a message if you want; text and files go together in one run.
-
API (Dynamic Input agents only; uploads to any other agent are refused) — upload each file, then start the run with the upload IDs, at most 20:
curl -X POST https://api.seclai.com/agents/{agent_id}/upload-input \ -H "X-API-Key: YOUR_API_KEY" -F "file=@invoice.pdf" # → {"id": "<invoice-upload-id>", …} curl -X POST https://api.seclai.com/agents/{agent_id}/upload-input \ -H "X-API-Key: YOUR_API_KEY" -F "file=@front.jpg" # → {"id": "<photo-upload-id>", …} curl -X POST https://api.seclai.com/agents/{agent_id}/runs \ -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"input": "March expenses", "input_upload_ids": ["<invoice-upload-id>", "<photo-upload-id>"]}'You don't say which attachment a file is for: it is assigned by its type and name.
GET /agents/{agent_id}/attachment-referencesreturns the declaration, so a client can check its files before uploading. -
MCP —
get_agent_attachment_references, thenupload_agent_inputfor each file, thenrun_agentwithinput_upload_ids(Dynamic Input agents only, at most 20 files). -
Email — attach the files to the message.
-
Another agent — a Call Agent step's Attachments list chooses the files it passes, and they are assigned to the called agent's declaration the same way.
The upload flow, size limits and scanning are covered under File Attachments.
Trigger Metadata
All trigger types support metadata — a set of key-value pairs that are available to every step in the agent via {{metadata.field_name}} substitutions.
Where Metadata Comes From
| Trigger Type | Metadata Sources |
|---|---|
dynamic_input | Provided in the API request body + default metadata from trigger config |
template_input | Defined in the trigger configuration |
content_added / content_updated / content_added_or_updated | Content source metadata (title, URL, author, etc.) + default metadata from trigger config |
For content-based triggers, metadata from the content source is automatically included. This typically contains fields like title, author, source_url, published_date, content_type, and any custom metadata defined on the source.
Content-based triggers also provide source_connection_content_version_id in the run metadata. This ID is required by the Write Metadata, Write Content Attachment, and Load Content Attachment step types to identify which content record to read from or write to. You do not need to configure this manually — it is set automatically when the agent is triggered by a content event.
Using Metadata in Steps
Metadata is accessed via {{metadata.field_name}} in any step field that supports string substitutions:
In a prompt call system prompt:
You are analyzing content from {{metadata.source_url}}.
Author: {{metadata.author}}
Category: {{metadata.category}}
In a gate condition:
- Target:
metadata.category - Operator:
$eq - Value:
technology
In an S3 object key:
reports/{{metadata.category}}/{{metadata.article_id}}/{{date UTC}}.json
In a webhook payload:
{
"source": "{{metadata.source_url}}",
"title": "{{metadata.title}}",
"summary": "{{step.summarize.output}}",
"processed_at": "{{datetime UTC}}"
}
In an email subject:
New {{metadata.category}} article: {{metadata.title}}
Running Triggers
Manual Execution
Both dynamic_input and template_input triggers can be run manually:
Via the UI:
- Navigate to your agent
- Click Run on the trigger
- For dynamic input: enter your input text and optional metadata
- For template input: the template is rendered automatically
Via the API:
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "Your input text here",
"metadata": {
"key": "value"
}
}'
Cloud File Triggers
Cloud file triggers run an agent automatically whenever a file is added or updated on a connected cloud drive. The changed file is downloaded and passed to the agent as its input, so the agent can process it directly.
Setup
- Go to Integrations → Cloud Drives and connect a drive (Dropbox or Google Drive). You'll be redirected to authorize access — the consent screen lists exactly what is granted. Only files that change after you connect will trigger runs (your existing files are not reprocessed).
- On your agent, set the trigger type to a Cloud File trigger and pick the connected drive.
- Optionally narrow which files trigger the agent with the filters below.
Optional filters
- File path pattern — a case-insensitive glob matched against the file
path, e.g.
/reports/*or**/*.pdf. Leave blank to match all files. - File content types — an allowlist of formats, e.g.
application/pdforimage/*. Leave blank to allow any type. - Max file size (MB) — files larger than this are skipped.
File metadata
Each run captures the changed file's identity and the drive event as metadata,
available to every step via {{metadata.field}}:
| Metadata Key | Description |
|---|---|
file_name | The file name |
file_path | The file's path on the drive |
file_id | The provider's stable file identifier |
file_rev | The provider's file revision identifier |
file_event_type | file_added or file_updated |
file_size | The file size in bytes |
file_content_type | The file's MIME type |
file_modified_at | When the file was last modified on the drive |
file_client_modified_at | The client-reported modification time (when present) |
cloud_drive_provider | The drive provider (e.g. dropbox) |
cloud_drive_connection_id | The Seclai cloud-drive connection ID |
external_account_id | The provider account the file belongs to |
Reference them in steps with {{metadata.file_name}} etc.
Note: Files that are skipped (too large, download failed, or during a very large burst) are recorded on the connection's Skipped files tab. If a drive needs re-authorization, the connection is marked Error — reconnect it from the connection page.
Reading and writing drive files from a workflow
Triggers are only one way an agent touches a cloud drive. Inside a workflow you
can also add cloud-drive steps — List folder, Read file, and Write
file — that operate on a connected drive on demand (for example, list a folder,
read a specific file by path, or write a generated report back to the drive). A
prompt_call step can additionally enable a cloud-drive tool so the model
itself can browse, read, and (optionally) write files during a tool loop, scoped
to one connection and bounded by per-run call and byte limits. All of these use
the same connections you set up under Integrations → Cloud Drives.
Automatic Execution
- Scheduled triggers — Fire at the times defined by their schedule(s)
- Content triggers — Fire when the connected knowledge base detects content changes
- Cloud file triggers — Fire when a file is added or updated on a connected cloud drive
Automatic runs appear in the agent's traces alongside manual runs.
Next Steps
- Agent Steps — Learn about each step type in detail
- Agents Overview — Back to the agents overview
- Knowledge Bases — Connect data sources to triggers
- Content Sources — Understand how content flows into knowledge bases
- API Examples — Code samples for agent automation