Skip to content

Commit 107cc62

Browse files
michaelmcneesclaude
andcommitted
docs: fix output field names, filter values, config keys, and add email delivery semantics
- Fix email/move output: success → moved - Fix email/delete output: success → deleted - Fix email/flag output: success → updated, add action field - Fix email/receive and trigger filter values: remove seen, add recent, change default all → unseen - Remove fictional engine.email config keys; document compile-time default of 5 connections - Add email trigger type fields (mailbox, folder, filter, poll_interval) to workflow-reference/index.md - Remove _credential params from email/receive, email/move, email/delete, email/flag; add step-level credential notes - Fix Python browser/run example to use pre-created page object instead of calling browser.new_page() - Add at-least-once delivery section explaining crash-recovery re-trigger risk and idempotency guidance - Clarify steps.error is only practically useful when continue_on_error: true Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 37cf337 commit 107cc62

4 files changed

Lines changed: 42 additions & 22 deletions

File tree

site/src/content/docs/concepts/expressions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Every step exposes an `error` field:
5454
- **`null`** — The step succeeded or was skipped (its `if` condition was false).
5555
- **String message** — The step failed. The error message is populated from the connector.
5656

57-
The error field is available regardless of whether the step has `continue_on_error` enabled. Use it to implement conditional error handling:
57+
The `error` field is always present in the CEL context for any step. However, it is only practically useful when the referenced step has `continue_on_error: true`. Without that flag, a step failure halts the entire workflow before any downstream step can run — so there is no opportunity to check the error. Use `continue_on_error: true` on any step whose failure you want to handle in subsequent steps:
5858

5959
```yaml
6060
steps:

site/src/content/docs/server-guide/triggers.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ The `email` trigger type polls a mailbox for messages matching a filter and exec
201201
| `type` | string | Yes | Must be `email`. |
202202
| `mailbox` | string | Yes | Credential name for the email account (IMAP-compatible). |
203203
| `folder` | string | No | Folder to monitor (e.g., `INBOX`, `Archive`). Default: `INBOX`. |
204-
| `filter` | string | No | Filter messages: `all`, `unseen`, `seen`, `flagged`. Default: `unseen`. |
204+
| `filter` | string | No | Filter messages: `all`, `unseen`, `recent`, `flagged`. Default: `unseen`. |
205205
| `poll_interval` | string | No | How often to check for new messages (e.g., `30s`, `5m`). Default: `60s`. |
206206

207207
### Trigger Context Variables
@@ -242,20 +242,32 @@ steps:
242242
text: "New email from {{ trigger.from }}: {{ trigger.subject }}"
243243
```
244244
245-
### Connection Management
245+
### At-Least-Once Delivery
246246
247-
Email triggers maintain persistent IMAP connections to reduce authentication overhead. By default, Mantle pools up to 5 concurrent connections per mailbox credential.
247+
The email trigger marks messages as seen **after** firing the workflow for each message. If the Mantle process crashes or is restarted mid-poll, messages that were fetched but not yet marked may be re-triggered on the next poll cycle. This means email-triggered workflows may receive the same message more than once.
248248
249-
**Configuring connection limits in `mantle.yaml`:**
249+
Design email-triggered workflows to be idempotent. A reliable approach is to deduplicate on `trigger.message_id`:
250250

251251
```yaml
252-
engine:
253-
email:
254-
max_connections: 10 # Default: 5
255-
connection_timeout: 30s # Default: 30s
256-
idle_timeout: 5m # Default: 5m
252+
steps:
253+
- name: check-duplicate
254+
action: postgres/query
255+
credential: my-database
256+
params:
257+
query: "INSERT INTO processed_emails (message_id) VALUES ($1) ON CONFLICT DO NOTHING"
258+
args:
259+
- "{{ trigger.message_id }}"
260+
261+
- name: process-email
262+
action: ai/completion
263+
if: "steps['check-duplicate'].output.rows_affected > 0"
264+
# ... rest of workflow
257265
```
258266

267+
### Connection Management
268+
269+
Email triggers maintain persistent IMAP connections to reduce authentication overhead. By default, Mantle pools up to 5 concurrent connections per mailbox credential. This limit is a compile-time default in v0.3.0 and is not runtime-configurable via `mantle.yaml`.
270+
259271
### Provider Limits
260272

261273
Email providers have different concurrency limits. Plan your poll intervals accordingly:
@@ -293,4 +305,4 @@ triggers:
293305
poll_interval: 60s
294306
```
295307

296-
Each workflow gets its own connection, but they share the pool. If latency is a concern, stagger poll intervals or increase `max_connections` in `mantle.yaml`.
308+
Each workflow gets its own connection, but they share the pool. If latency is a concern, stagger poll intervals.

site/src/content/docs/workflow-reference/connectors.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,9 @@ Reads messages from an email mailbox. Supports filtering by folder and read stat
328328
| Param | Type | Required | Description |
329329
|---|---|---|---|
330330
| `folder` | string | No | Folder to read from (e.g., `INBOX`, `Archive`, `[Gmail]/Sent Mail`). Default: `INBOX`. |
331-
| `filter` | string | No | Filter messages by status: `all`, `unseen`, `seen`, `flagged`. Default: `all`. |
331+
| `filter` | string | No | Filter messages by status: `all`, `unseen`, `recent`, `flagged`. Default: `unseen`. |
332332
| `limit` | number | No | Maximum number of messages to return. Default: `10`. |
333333
| `mark_seen` | boolean | No | Mark retrieved messages as seen. Default: `false`. |
334-
| `_credential` | string | Yes | Email account credential (IMAP-compatible). |
335334

336335
**Output:**
337336

@@ -340,6 +339,8 @@ Reads messages from an email mailbox. Supports filtering by folder and read stat
340339
| `message_count` | number | Number of messages returned. |
341340
| `messages` | array | Array of message objects. Each message contains: `message_id` (string), `from` (string), `to` (string), `cc` (string), `subject` (string), `body` (string), `date` (RFC 3339 timestamp), `headers` (map), `flags` (array of strings), `uid` (number, IMAP UID). |
342341

342+
**Authentication:** Credentials are provided via the step-level `credential` field. The email connector reads `username`, `password`, `host`, and `port` from the credential (IMAP-compatible).
343+
343344
**Example:**
344345

345346
```yaml
@@ -364,16 +365,17 @@ Moves an email message to a different folder.
364365
| `uid` | number | Yes | IMAP UID of the message. |
365366
| `source_folder` | string | No | Source folder (for reference). Default: `INBOX`. |
366367
| `target_folder` | string | Yes | Destination folder path (e.g., `Archive`, `[Gmail]/All Mail`). |
367-
| `_credential` | string | Yes | Email account credential. |
368368

369369
**Output:**
370370

371371
| Field | Type | Description |
372372
|---|---|---|
373-
| `success` | boolean | `true` if the move was successful. |
373+
| `moved` | boolean | `true` if the move was successful. |
374374
| `uid` | number | The IMAP UID of the moved message. |
375375
| `target_folder` | string | The folder the message was moved to. |
376376

377+
**Authentication:** Credentials are provided via the step-level `credential` field.
378+
377379
**Note:** Gmail's "archive" action is implemented as a move to `[Gmail]/All Mail`.
378380

379381
**Example:**
@@ -398,15 +400,16 @@ Deletes an email message.
398400
|---|---|---|---|
399401
| `uid` | number | Yes | IMAP UID of the message. |
400402
| `folder` | string | No | Folder containing the message. Default: `INBOX`. |
401-
| `_credential` | string | Yes | Email account credential. |
402403

403404
**Output:**
404405

405406
| Field | Type | Description |
406407
|---|---|---|
407-
| `success` | boolean | `true` if the deletion was successful. |
408+
| `deleted` | boolean | `true` if the deletion was successful. |
408409
| `uid` | number | The IMAP UID of the deleted message. |
409410

411+
**Authentication:** Credentials are provided via the step-level `credential` field.
412+
410413
**Example:**
411414

412415
```yaml
@@ -430,7 +433,6 @@ Adds or removes flags (labels) on an email message.
430433
| `flags` | array | Yes | List of flag names to modify (e.g., `["flagged", "important"]`). |
431434
| `action` | string | Yes | `add` to set flags, `remove` to unset flags. |
432435
| `folder` | string | No | Folder containing the message. Default: `INBOX`. |
433-
| `_credential` | string | Yes | Email account credential. |
434436

435437
**Standard IMAP Flags:**
436438

@@ -448,10 +450,13 @@ Adds or removes flags (labels) on an email message.
448450

449451
| Field | Type | Description |
450452
|---|---|---|
451-
| `success` | boolean | `true` if the flag operation was successful. |
453+
| `updated` | boolean | `true` if the flag operation was successful. |
454+
| `action` | string | The operation performed: `add` or `remove`. |
452455
| `uid` | number | The IMAP UID of the message. |
453456
| `flags` | array | The flags that were modified. |
454457

458+
**Authentication:** Credentials are provided via the step-level `credential` field.
459+
455460
**Example:**
456461

457462
```yaml
@@ -739,7 +744,6 @@ Runs browser automation scripts (JavaScript, TypeScript, or Python) using Playwr
739744
import os
740745
import json
741746
742-
page = browser.new_page()
743747
page.goto('https://example.com/report')
744748
page.pdf(path='/mantle/artifacts/report.pdf')
745749

site/src/content/docs/workflow-reference/index.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ steps:
253253

254254
### Available Error Fields
255255

256-
- **`steps['name'].error`** — `null` for successful or skipped steps; a string error message for failed steps. Available for all steps regardless of `continue_on_error`.
256+
- **`steps['name'].error`** — `null` for successful or skipped steps; a string error message for failed steps. The field is always present in the CEL context, but is only practically reachable on a step that has `continue_on_error: true` — without that flag, a step failure halts the workflow before any downstream step can inspect the error.
257257
- **`steps['name'].output`** — Partial output available from the failed step if the connector provided it. Structure depends on the connector.
258258

259259
### Example: Fallback Pattern
@@ -355,9 +355,13 @@ Triggers are optional. Without them, the workflow can still be executed manually
355355

356356
| Field | Type | Required | Description |
357357
|---|---|---|---|
358-
| `type` | string | Yes | Trigger type. One of: `cron`, `webhook`. |
358+
| `type` | string | Yes | Trigger type. One of: `cron`, `webhook`, `email`. |
359359
| `schedule` | string | Cron only | Cron expression defining the schedule. Required when `type` is `cron`. |
360360
| `path` | string | Webhook only | URL path for the webhook endpoint. Required when `type` is `webhook`. |
361+
| `mailbox` | string | Email only | Credential name for the email account (IMAP-compatible). Required when `type` is `email`. |
362+
| `folder` | string | Email only | Folder to monitor (e.g., `INBOX`). Default: `INBOX`. |
363+
| `filter` | string | Email only | Filter messages: `all`, `unseen`, `recent`, `flagged`. Default: `unseen`. |
364+
| `poll_interval` | string | Email only | How often to check for new messages (e.g., `30s`, `5m`). Default: `60s`. |
361365

362366
### Trigger Lifecycle
363367

0 commit comments

Comments
 (0)