From 2a67dec9ecee7521f05d5b6992359cff979da52a Mon Sep 17 00:00:00 2001 From: Iaroslav Ciupin Date: Thu, 2 Apr 2026 19:11:43 +0300 Subject: [PATCH 1/2] Trigger notifications --- .../user-guide/task-configuration/triggers.md | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/content/user-guide/task-configuration/triggers.md b/content/user-guide/task-configuration/triggers.md index f7846fd67..1b0ce7e3d 100644 --- a/content/user-guide/task-configuration/triggers.md +++ b/content/user-guide/task-configuration/triggers.md @@ -279,6 +279,167 @@ You can attach multiple triggers to a single task by providing a list of trigger You can mix and match trigger types, combining predefined triggers with those that use `flyte.Cron`, and `flyte.FixedRate` automations (see below for explanations of these concepts). +## Notifications + +You can attach notifications to a trigger using the `notifications` parameter of `flyte.Trigger`. +Notifications fire when a triggered run reaches a terminal execution phase. + +```python +import flyte +from flyte import notify +from flyte.models import ActionPhase + +env = flyte.TaskEnvironment(name="my_task_env") + +trigger_with_notifications = flyte.Trigger( + name="daily_report", + automation=flyte.Cron("0 9 * * 1-5"), + notifications=( + notify.Slack( + on_phase=ActionPhase.FAILED, + webhook_url="https://hooks.slack.com/services/YOUR/WEBHOOK/URL", + message="Run {{.Run.Name}} failed with: {{.Error}}", + ), + notify.Email( + on_phase=ActionPhase.SUCCEEDED, + recipients=["oncall@example.com"], + subject="Run {{.Run.Name}} succeeded", + body="Run: {{.Run.Name}}", + ), + ), +) + +@env.task(triggers=trigger_with_notifications) +def process_data(date: str) -> str: + return f"Processed {date}" +``` + +### Execution phases + +The `on_phase` parameter accepts a single phase or a tuple of terminal phases from `flyte.models.ActionPhase`: + +| Phase | Description | +|-------|----------------------------| +| `ActionPhase.SUCCEEDED` | Run completed successfully | +| `ActionPhase.FAILED` | Run failed with an error | +| `ActionPhase.TIMED_OUT` | Run exceeded its timeout | +| `ActionPhase.ABORTED` | Run was manually aborted | + +To notify on multiple phases with the same notification: + +```python +notify.Email( + on_phase=(ActionPhase.FAILED, ActionPhase.ABORTED), + recipients=["oncall@example.com"], + subject="Alert: Run completed with phase {{.Phase}}", + body="Run: {{.Run.Name}}\nError: {{.Error}}", +) +``` + +### Template variables + +All message fields support template variables that are substituted at delivery time: + +| Variable | Description | +|--------------------|--------------------------------------------------------| +| `{{.Run.Project}}` | Project name | +| `{{.Run.Domain}}` | Domain name | +| `{{.Run.Name}}` | Run ID | +| `{{.Phase}}` | Execution phase | +| `{{.Error}}` | Error message when failed or abort reason whan aborted | + + +### Slack notifications + +`notify.Slack` sends a message to a Slack channel via an [incoming webhook](https://api.slack.com/messaging/webhooks). + +**Simple message:** + +```python +notify.Slack( + on_phase=ActionPhase.FAILED, + webhook_url="https://hooks.slack.com/services/YOUR/WEBHOOK/URL", + message="Run {{.Run.Name}} failed in {{.Run.Project}}/{{.Run.Domain}}: {{.Error}}", +) +``` + +**Rich formatting with [Block Kit](https://api.slack.com/block-kit):** + +Use `blocks` instead of `message` for structured layouts. When `blocks` is provided, `message` is ignored. + +```python +notify.Slack( + on_phase=ActionPhase.SUCCEEDED, + webhook_url="https://hooks.slack.com/services/YOUR/WEBHOOK/URL", + blocks=[ + { + "type": "header", + "text": {"type": "plain_text", "text": "Task Succeeded"}, + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Run:*\n{{.Run.Name}}"}, + {"type": "mrkdwn", "text": "*Phase:*\n{{.Phase}}"}, + ], + }, + {"type": "divider"}, + { + "type": "context", + "elements": [ + {"type": "mrkdwn", "text": "{{.Run.Project}}/{{.Run.Domain}}"}, + ], + }, + ], +) +``` + +### Email notifications + +`notify.Email` sends an email notification. You can provide a plain-text `body`, an `html_body`, or both (the email is sent as multipart when both are present). + +```python +notify.Email( + on_phase=ActionPhase.FAILED, + recipients=["oncall@example.com"], + cc=["team-lead@example.com"], + subject="ALERT: Run {{.Run.Name}} failed", + body="Run: {{.Run.Name}}\nError: {{.Error}}", + html_body="Error: {{.Error}}
", +) +``` + +### Microsoft Teams notifications + +`notify.Teams` sends a message to a Teams channel via an incoming webhook. Use `card` for [Adaptive Card](https://adaptivecards.io/designer/) formatting; when `card` is set, `title` and `message` are ignored. + +```python +notify.Teams( + on_phase=ActionPhase.FAILED, + webhook_url="https://outlook.office.com/webhook/YOUR_WEBHOOK_URL", + title="Task Failed", + message="Run {{.Run.Name}} failed: {{.Error}}\n", +) +``` + +### Custom webhook notifications + +`notify.Webhook` sends an HTTP request to any endpoint. All string values in `headers` and `body` support template variables. + +```python +notify.Webhook( + on_phase=ActionPhase.SUCCEEDED, + url="https://api.example.com/events", + method="POST", + headers={"Authorization": "Bearer my-token"}, + body={ + "event": "task_succeeded", + "run": "{{.Run.Name}}", + }, +) +``` + + ## Deploying a task with triggers We recommend that you define your triggers in code together with your tasks and deploy them together. From 5d496d920ccc189b06be86f029e357b27f193bde Mon Sep 17 00:00:00 2001 From: Iaroslav Ciupin Date: Thu, 2 Apr 2026 19:27:26 +0300 Subject: [PATCH 2/2] Run notifications --- .../task-deployment/run-with-notifications.md | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 content/user-guide/task-deployment/run-with-notifications.md diff --git a/content/user-guide/task-deployment/run-with-notifications.md b/content/user-guide/task-deployment/run-with-notifications.md new file mode 100644 index 000000000..03e5a8bb6 --- /dev/null +++ b/content/user-guide/task-deployment/run-with-notifications.md @@ -0,0 +1,144 @@ +--- +title: Run with notifications +weight: 11 +variants: +flyte +union +--- + +# Run with notifications + +You can attach notifications to a single run by passing them to `flyte.with_runcontext()`. +Notifications fire when the run reaches the terminal execution phase — no trigger or persistent deployment is required. + +```python +import os +import flyte +from flyte import notify +from flyte.models import ActionPhase + +env = flyte.TaskEnvironment(name="notify_example") + +SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"] +NOTIFICATION_EMAIL = os.environ["NOTIFICATION_EMAIL"] + + +@env.task +def compute(x: int, y: int) -> int: + return x + y + + +if __name__ == "__main__": + result = flyte.with_runcontext( + notifications=( + notify.Slack( + on_phase=ActionPhase.SUCCEEDED, + webhook_url=SLACK_WEBHOOK_URL, + message="Run {{.Run.Name}} succeeded.", + ), + notify.Email( + on_phase=ActionPhase.FAILED, + recipients=[NOTIFICATION_EMAIL], + subject="ALERT: Run {{.Run.Name}} failed", + body="Run: {{.Run.Name}}\nError: {{.Error}}", + ), + ), + ).run(compute, x=3, y=7) + print(f"Result: {result}") +``` + +Pass a single notification or a tuple of notifications. All notification types from `flyte.notify` are supported: `Slack`, `Email`, `Teams`, `Webhook`, and `NamedDelivery`. + +> [!NOTE] +> To attach notifications to every run created by a scheduled trigger, set `notifications` on the `flyte.Trigger` object instead. See [Notifications](../task-configuration/triggers#notifications). + +## Execution phases + +The `on_phase` parameter accepts a single phase or a tuple of phases from `flyte.models.ActionPhase`: + +| Phase | Description | +|-------|-------------| +| `ActionPhase.SUCCEEDED` | Run completed successfully | +| `ActionPhase.FAILED` | Run failed with an error | +| `ActionPhase.TIMED_OUT` | Run exceeded its timeout | +| `ActionPhase.ABORTED` | Run was manually aborted | + +## Template variables + +All message fields support template variables substituted at delivery time: + +| Variable | Description | +|----------|-------------| +| `{{.Run.Project}}` | Project name | +| `{{.Run.Domain}}` | Domain name | +| `{{.Run.Name}}` | Run ID | +| `{{.Phase}}` | Execution phase | +| `{{.Error}}` | Error message (failed) or abort reason (aborted) | + +## Slack notifications + +`notify.Slack` sends a message to a Slack channel via an [incoming webhook](https://api.slack.com/messaging/webhooks). + +**Simple message:** + +```python +notify.Slack( + on_phase=ActionPhase.FAILED, + webhook_url=SLACK_WEBHOOK_URL, + message="Run {{.Run.Name}} failed in {{.Run.Project}}/{{.Run.Domain}}: {{.Error}}", +) +``` + +**Rich formatting with [Block Kit](https://api.slack.com/block-kit):** + +Use `blocks` instead of `message` for structured layouts. When `blocks` is provided, `message` is ignored. + +```python +notify.Slack( + on_phase=ActionPhase.SUCCEEDED, + webhook_url=SLACK_WEBHOOK_URL, + blocks=[ + { + "type": "header", + "text": {"type": "plain_text", "text": "Task Succeeded"}, + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Run:*\n{{.Run.Name}}"}, + {"type": "mrkdwn", "text": "*Phase:*\n{{.Phase}}"}, + ], + }, + {"type": "divider"}, + { + "type": "context", + "elements": [ + {"type": "mrkdwn", "text": "{{.Run.Project}}/{{.Run.Domain}}"}, + ], + }, + ], +) +``` + +## Email notifications + +`notify.Email` sends an email notification. Provide `body` for plain text, `html_body` for HTML, or both (sent as multipart). + +```python +notify.Email( + on_phase=ActionPhase.FAILED, + recipients=[NOTIFICATION_EMAIL], + cc=["team-lead@example.com"], + subject="ALERT: Run {{.Run.Name}} failed", + body="Run: {{.Run.Name}}\nError: {{.Error}}", + html_body="Error: {{.Error}}
", +) +``` + +To receive emails locally while developing, start a debug SMTP server before running your script: + +```bash +# Python >= 3.12 +pip install aiosmtpd +sudo python -m aiosmtpd -n -l localhost:25 +``` + +The server prints received emails to stdout. Port 25 requires root; use port 1025 as an alternative (update the SMTP port in your configuration accordingly).