mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
fix: route email trigger path through standard info channel (#8996)
* docs(skill): document email triggers and S3 attachments
Add an "Email triggers" section to the triggers skill covering the
local-part config, the parsed_email/raw_email/email_extra_args payload,
the URL-style extras convention, where to find trigger_path (only with
a preprocessor, at event.trigger_path), and — most importantly — that
binary attachments are uploaded to the workspace S3 bucket and surface
as `{ s3: "windmill_emails/<job_id>/attachments/<filename>" }`. Scripts
must use wmill.loadS3File / wmill.load_s3_file to read them.
Also pulls EmailTrigger into the schema mappings so a real
`email_trigger.schema.yaml` is generated, and adds Email/Azure to the
trigger kinds list in the CLI agent guidance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref for email trigger path fix
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 26184ab7a4aadfc529dcedf038aa08d36c7ad381
This commit updates the EE repository reference after PR #553 was merged in windmill-ee-private.
Previous ee-repo-ref: 318a46897a605dc9be3817901f35ba5a99a0a525
New ee-repo-ref: 26184ab7a4aadfc529dcedf038aa08d36c7ad381
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM email_trigger\n WHERE\n ((workspaced_local_part IS TRUE AND workspace_id || '-' || local_part = $1)\n OR (workspaced_local_part IS FALSE AND local_part = $1))\n AND ($2::TEXT IS NULL OR path != $2)\n )\n ",
|
||||
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM email_trigger\n WHERE\n ((workspaced_local_part IS TRUE AND workspace_id || '-' || local_part = $1)\n OR (workspaced_local_part IS FALSE AND local_part = $1))\n AND ($2::TEXT IS NULL OR NOT (workspace_id = $3 AND path = $2))\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -11,6 +11,7 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
@@ -19,5 +20,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bb94e3105cb1bc5d90af2bc914b579a6e821c432eac38ad877d9ff362d8ab916"
|
||||
"hash": "4d73cefcc1b72238b7731ab52376b849a6f712b4109a48d9f77da8dd0060ab1a"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
1790e8df54bb00fb6800b8f3a6a2bb4c9787380e
|
||||
26184ab7a4aadfc529dcedf038aa08d36c7ad381
|
||||
|
||||
@@ -37,7 +37,7 @@ When a new app needs to be created, YOU run \`wmill app new\` yourself with \`--
|
||||
|
||||
## Triggers
|
||||
|
||||
You MUST use the \`triggers\` skill to configure HTTP routes, WebSocket, Kafka, NATS, SQS, MQTT, GCP, or Postgres CDC triggers.
|
||||
You MUST use the \`triggers\` skill to configure HTTP routes, WebSocket, Kafka, NATS, SQS, MQTT, GCP, Azure, Email, or Postgres CDC triggers.
|
||||
|
||||
## Schedules
|
||||
|
||||
|
||||
@@ -5563,6 +5563,49 @@ Examples:
|
||||
- \`u/user/webhook.http_trigger.yaml\`
|
||||
- \`f/data/kafka_consumer.kafka_trigger.yaml\`
|
||||
- \`f/sync/postgres_cdc.postgres_trigger.yaml\`
|
||||
- \`f/inbound/orders.email_trigger.yaml\`
|
||||
|
||||
## Email Triggers
|
||||
|
||||
An email trigger routes incoming emails to a script or flow. Each trigger reserves a local-part: emails sent to \`<local_part>@<windmill_email_domain>\` are delivered to the configured runnable. Set \`workspaced_local_part: true\` to namespace it per workspace (the actual recipient becomes \`<workspace_id>-<local_part>@…\`); on Windmill Cloud this is required.
|
||||
|
||||
Senders may append URL-style extras to the local-part with \`+\`: \`mytrigger+foo=bar+baz=qux@…\`. They flow through to the script as \`email_extra_args\` (see below).
|
||||
|
||||
### Payload
|
||||
|
||||
The runnable receives:
|
||||
|
||||
- \`parsed_email\` — \`{ headers, text_body, html_body, attachments[] }\`. Each \`attachment\` has \`{ headers, body }\`.
|
||||
- \`raw_email\` — the raw RFC 822 message as a string, **or** an S3 object (\`{ s3: "windmill_emails/<job_id>/raw.eml" }\`) if the message exceeds 1 MiB.
|
||||
- \`email_extra_args\` (optional, only when sender appended \`+key=value\` extras) — a flat object of the parsed extras.
|
||||
|
||||
With a preprocessor, all of the above are nested under \`event\` along with \`event.kind = "email"\` and \`event.trigger_path\` (the trigger's path). Without a preprocessor, \`trigger_path\` is **not** exposed — add a preprocessor if you need it.
|
||||
|
||||
### Attachments are S3 objects
|
||||
|
||||
Binary attachments are uploaded to the workspace S3 bucket and surface in \`parsed_email.attachments[i].body\` as:
|
||||
|
||||
\`\`\`json
|
||||
{ "s3": "windmill_emails/<job_id>/attachments/<filename>" }
|
||||
\`\`\`
|
||||
|
||||
To read the bytes inside a script, use the wmill SDK:
|
||||
|
||||
\`\`\`ts
|
||||
// TypeScript
|
||||
import * as wmill from "windmill-client"
|
||||
const file = await wmill.loadS3File(parsed_email.attachments[0].body)
|
||||
\`\`\`
|
||||
|
||||
\`\`\`python
|
||||
# Python
|
||||
import wmill
|
||||
data = wmill.load_s3_file(parsed_email["attachments"][0]["body"])
|
||||
\`\`\`
|
||||
|
||||
If the workspace has no S3 resource configured (Workspace Settings → Object storage), \`body\` falls back to the string \`"configure s3 in the workspace settings to handle attachments"\`. The same applies to large \`raw_email\` bodies. Email attachment storage requires the server to be built with the \`parquet\` feature.
|
||||
|
||||
Text/HTML/inline parts are placed inline in \`body\` as strings.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
@@ -7180,6 +7223,71 @@ required:
|
||||
- azure_mode
|
||||
- scope_resource_id
|
||||
- subscription_name
|
||||
`,
|
||||
"email_trigger": `type: object
|
||||
properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
local_part:
|
||||
type: string
|
||||
workspaced_local_part:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
type: object
|
||||
description: The arguments to pass to the script or flow
|
||||
retry:
|
||||
type: object
|
||||
properties:
|
||||
constant:
|
||||
type: object
|
||||
description: Retry with constant delay between attempts
|
||||
properties:
|
||||
attempts:
|
||||
type: integer
|
||||
description: Number of retry attempts
|
||||
seconds:
|
||||
type: integer
|
||||
description: Seconds to wait between retries
|
||||
exponential:
|
||||
type: object
|
||||
description: Retry with exponential backoff (delay doubles each time)
|
||||
properties:
|
||||
attempts:
|
||||
type: integer
|
||||
description: Number of retry attempts
|
||||
multiplier:
|
||||
type: integer
|
||||
description: Multiplier for exponential backoff
|
||||
seconds:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: Initial delay in seconds
|
||||
random_factor:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
description: Random jitter percentage (0-100) to avoid thundering herd
|
||||
retry_if:
|
||||
$ref: '#/components/schemas/RetryIf'
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- local_part
|
||||
`,
|
||||
"gcp_trigger": `type: object
|
||||
properties:
|
||||
@@ -8116,6 +8224,7 @@ export const SCHEMA_MAPPINGS: Record<string, SchemaMapping[]> = {
|
||||
{ name: "SqsTrigger", schemaKey: "sqs_trigger", filePattern: "*.sqs_trigger.yaml" },
|
||||
{ name: "GcpTrigger", schemaKey: "gcp_trigger", filePattern: "*.gcp_trigger.yaml" },
|
||||
{ name: "AzureTrigger", schemaKey: "azure_trigger", filePattern: "*.azure_trigger.yaml" },
|
||||
{ name: "EmailTrigger", schemaKey: "email_trigger", filePattern: "*.email_trigger.yaml" },
|
||||
],
|
||||
"schedules": [
|
||||
{ name: "Schedule", schemaKey: "schedule", filePattern: "*.schedule.yaml" },
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
type: object
|
||||
properties:
|
||||
script_path:
|
||||
type: string
|
||||
description: Path to the script or flow to execute when triggered
|
||||
permissioned_as:
|
||||
type: string
|
||||
description: The user or group this trigger runs as (permissioned_as)
|
||||
is_flow:
|
||||
type: boolean
|
||||
description: True if script_path points to a flow, false if it points to a script
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
local_part:
|
||||
type: string
|
||||
workspaced_local_part:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
type: object
|
||||
description: The arguments to pass to the script or flow
|
||||
retry:
|
||||
type: object
|
||||
properties:
|
||||
constant:
|
||||
type: object
|
||||
description: Retry with constant delay between attempts
|
||||
properties:
|
||||
attempts:
|
||||
type: integer
|
||||
description: Number of retry attempts
|
||||
seconds:
|
||||
type: integer
|
||||
description: Seconds to wait between retries
|
||||
exponential:
|
||||
type: object
|
||||
description: Retry with exponential backoff (delay doubles each time)
|
||||
properties:
|
||||
attempts:
|
||||
type: integer
|
||||
description: Number of retry attempts
|
||||
multiplier:
|
||||
type: integer
|
||||
description: Multiplier for exponential backoff
|
||||
seconds:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: Initial delay in seconds
|
||||
random_factor:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
description: Random jitter percentage (0-100) to avoid thundering herd
|
||||
retry_if:
|
||||
$ref: '#/components/schemas/RetryIf'
|
||||
description: Retry configuration for failed module executions
|
||||
required:
|
||||
- script_path
|
||||
- permissioned_as
|
||||
- is_flow
|
||||
- local_part
|
||||
@@ -15,6 +15,49 @@ Examples:
|
||||
- `u/user/webhook.http_trigger.yaml`
|
||||
- `f/data/kafka_consumer.kafka_trigger.yaml`
|
||||
- `f/sync/postgres_cdc.postgres_trigger.yaml`
|
||||
- `f/inbound/orders.email_trigger.yaml`
|
||||
|
||||
## Email Triggers
|
||||
|
||||
An email trigger routes incoming emails to a script or flow. Each trigger reserves a local-part: emails sent to `<local_part>@<windmill_email_domain>` are delivered to the configured runnable. Set `workspaced_local_part: true` to namespace it per workspace (the actual recipient becomes `<workspace_id>-<local_part>@…`); on Windmill Cloud this is required.
|
||||
|
||||
Senders may append URL-style extras to the local-part with `+`: `mytrigger+foo=bar+baz=qux@…`. They flow through to the script as `email_extra_args` (see below).
|
||||
|
||||
### Payload
|
||||
|
||||
The runnable receives:
|
||||
|
||||
- `parsed_email` — `{ headers, text_body, html_body, attachments[] }`. Each `attachment` has `{ headers, body }`.
|
||||
- `raw_email` — the raw RFC 822 message as a string, **or** an S3 object (`{ s3: "windmill_emails/<job_id>/raw.eml" }`) if the message exceeds 1 MiB.
|
||||
- `email_extra_args` (optional, only when sender appended `+key=value` extras) — a flat object of the parsed extras.
|
||||
|
||||
With a preprocessor, all of the above are nested under `event` along with `event.kind = "email"` and `event.trigger_path` (the trigger's path). Without a preprocessor, `trigger_path` is **not** exposed — add a preprocessor if you need it.
|
||||
|
||||
### Attachments are S3 objects
|
||||
|
||||
Binary attachments are uploaded to the workspace S3 bucket and surface in `parsed_email.attachments[i].body` as:
|
||||
|
||||
```json
|
||||
{ "s3": "windmill_emails/<job_id>/attachments/<filename>" }
|
||||
```
|
||||
|
||||
To read the bytes inside a script, use the wmill SDK:
|
||||
|
||||
```ts
|
||||
// TypeScript
|
||||
import * as wmill from "windmill-client"
|
||||
const file = await wmill.loadS3File(parsed_email.attachments[0].body)
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
import wmill
|
||||
data = wmill.load_s3_file(parsed_email["attachments"][0]["body"])
|
||||
```
|
||||
|
||||
If the workspace has no S3 resource configured (Workspace Settings → Object storage), `body` falls back to the string `"configure s3 in the workspace settings to handle attachments"`. The same applies to large `raw_email` bodies. Email attachment storage requires the server to be built with the `parquet` feature.
|
||||
|
||||
Text/HTML/inline parts are placed inline in `body` as strings.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
|
||||
@@ -10,6 +10,49 @@ Examples:
|
||||
- `u/user/webhook.http_trigger.yaml`
|
||||
- `f/data/kafka_consumer.kafka_trigger.yaml`
|
||||
- `f/sync/postgres_cdc.postgres_trigger.yaml`
|
||||
- `f/inbound/orders.email_trigger.yaml`
|
||||
|
||||
## Email Triggers
|
||||
|
||||
An email trigger routes incoming emails to a script or flow. Each trigger reserves a local-part: emails sent to `<local_part>@<windmill_email_domain>` are delivered to the configured runnable. Set `workspaced_local_part: true` to namespace it per workspace (the actual recipient becomes `<workspace_id>-<local_part>@…`); on Windmill Cloud this is required.
|
||||
|
||||
Senders may append URL-style extras to the local-part with `+`: `mytrigger+foo=bar+baz=qux@…`. They flow through to the script as `email_extra_args` (see below).
|
||||
|
||||
### Payload
|
||||
|
||||
The runnable receives:
|
||||
|
||||
- `parsed_email` — `{ headers, text_body, html_body, attachments[] }`. Each `attachment` has `{ headers, body }`.
|
||||
- `raw_email` — the raw RFC 822 message as a string, **or** an S3 object (`{ s3: "windmill_emails/<job_id>/raw.eml" }`) if the message exceeds 1 MiB.
|
||||
- `email_extra_args` (optional, only when sender appended `+key=value` extras) — a flat object of the parsed extras.
|
||||
|
||||
With a preprocessor, all of the above are nested under `event` along with `event.kind = "email"` and `event.trigger_path` (the trigger's path). Without a preprocessor, `trigger_path` is **not** exposed — add a preprocessor if you need it.
|
||||
|
||||
### Attachments are S3 objects
|
||||
|
||||
Binary attachments are uploaded to the workspace S3 bucket and surface in `parsed_email.attachments[i].body` as:
|
||||
|
||||
```json
|
||||
{ "s3": "windmill_emails/<job_id>/attachments/<filename>" }
|
||||
```
|
||||
|
||||
To read the bytes inside a script, use the wmill SDK:
|
||||
|
||||
```ts
|
||||
// TypeScript
|
||||
import * as wmill from "windmill-client"
|
||||
const file = await wmill.loadS3File(parsed_email.attachments[0].body)
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
import wmill
|
||||
data = wmill.load_s3_file(parsed_email["attachments"][0]["body"])
|
||||
```
|
||||
|
||||
If the workspace has no S3 resource configured (Workspace Settings → Object storage), `body` falls back to the string `"configure s3 in the workspace settings to handle attachments"`. The same applies to large `raw_email` bodies. Email attachment storage requires the server to be built with the `parquet` feature.
|
||||
|
||||
Text/HTML/inline parts are placed inline in `body` as strings.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
|
||||
@@ -1432,6 +1432,7 @@ SKILL_DEFINITIONS = [
|
||||
('SqsTrigger', 'sqs_trigger'),
|
||||
('GcpTrigger', 'gcp_trigger'),
|
||||
('AzureTrigger', 'azure_trigger'),
|
||||
('EmailTrigger', 'email_trigger'),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1893,6 +1894,7 @@ def main():
|
||||
'SqsTrigger', 'NewSqsTrigger',
|
||||
'GcpTrigger',
|
||||
'AzureTrigger',
|
||||
'EmailTrigger', 'NewEmailTrigger',
|
||||
]
|
||||
for schema_name in schema_names:
|
||||
if schema_name in backend_schemas:
|
||||
|
||||
@@ -49,6 +49,7 @@ SCHEMA_MAPPINGS = {
|
||||
('SqsTrigger', 'sqs_trigger'),
|
||||
('GcpTrigger', 'gcp_trigger'),
|
||||
('AzureTrigger', 'azure_trigger'),
|
||||
('EmailTrigger', 'email_trigger'),
|
||||
],
|
||||
'schedules': [
|
||||
('Schedule', 'schedule'),
|
||||
|
||||
Reference in New Issue
Block a user