Bryan
12/11/2025, 12:13 AMMarvin
12/11/2025, 12:13 AMMarvin
12/11/2025, 12:17 AMFlow run completed ✔️
| Key | Value |
|---------- |-------|
| Flow | {{ flow.name }}
| Run Name | {{ flow_run.name }}
| Run ID | {{ flow_run.id }}
| State | {{ flow_run.state.name }}
| Message | {{ flow_run.state.message }}
| Start | {{ flow_run.start_time }}
| End | {{ flow_run.end_time }}
| Link | {{ flow_run|ui_url }}
Option B — Clean key:value list (most reliable)
This is the most consistent across Teams clients:
Flow run completed ✔️
• *Flow:* {{ flow.name }}
• *Run Name:* {{ flow_run.name }}
• *Run ID:* {{ flow_run.id }}
• *State:* {{ flow_run.state.name }}
• *Message:* {{ flow_run.state.message }}
• *Start:* {{ flow_run.start_time }}
• *End:* {{ flow_run.end_time }}
• *Link:* {{ flow_run|ui_url }}
If you truly need a native two‑column layout
Teams’ Adaptive Cards support a FactSet (name/value) layout. Prefect’s built‑in Microsoft Teams notification block doesn’t expose custom card templates, but if you’re sending to a Power Automate (Flow) URL, you can have your Flow construct an Adaptive Card with a FactSet from the incoming payload. Here’s a minimal Adaptive Card body your Flow could send to Teams:
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "<http://adaptivecards.io/schemas/adaptive-card.json>",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "Flow Run Completed",
"weight": "Bolder",
"size": "Large"
},
{
"type": "FactSet",
"facts": [
{
"name": "Flow",
"value": "{{ flow.name }}"
},
{
"name": "Run Name",
"value": "{{ flow_run.name }}"
},
{
"name": "Run ID",
"value": "{{ flow_run.id }}"
},
{
"name": "State",
"value": "{{ flow_run.state.name }}"
},
{
"name": "Start",
"value": "{{ flow_run.start_time }}"
},
{
"name": "End",
"value": "{{ flow_run.end_time }}"
},
{
"name": "Link",
"value": "{{ flow_run|ui_url }}"
}
]
}
]
}
}
]
}
If you want, share how you’re wiring the notification (Prefect block vs. Power Automate customization), and I can tailor the snippet exactly to your setup.Bryan
12/11/2025, 12:21 AMMarvin
12/11/2025, 12:21 AMMarvin
12/11/2025, 12:22 AMFlow Run Results
| Field | Value |
|---------------------|-------|
| Flow run name | {{ flow_run.name }} |
| Flow name | {{ flow.name }} |
| Deployment Name | {{ deployment.name if deployment else "—" }} |
| Deployment Version | {{ deployment.version if deployment else "—" }} |
| Flow run URL | {{ flow_run|ui_url }} |
| State Name | {{ flow_run.state.name }} |
| State Message | {{ flow_run.state.message or "—" }} |
| Error List | {% if flow_run.state.name in ["Failed", "Crashed"] %}{{ flow_run.state.message }}{% else %}None{% endif %} |
If you prefer a native two-column layout (FactSet) via Power Automate, say the word and I’ll share an Adaptive Card JSON snippet you can drop into your Flow.Bryan
12/11/2025, 12:31 AMMarvin
12/11/2025, 12:31 AMMarvin
12/11/2025, 12:34 AMREPO_URL if available)
- Indicates whether the run was scheduled or manual
- Lists deployment tags
Paste this into your Automation action body:
Flow Run Results
| Field | Value |
|---------------------------- |-------|
| Flow run name | {{ flow_run.name }} |
| Flow name | {{ flow.name }} |
| Deployment Name | {{ deployment.name if deployment else "—" }} |
| Deployment Version | {{ deployment.version if deployment else "—" }} |
| Flow run URL | <{{ flow_run|ui_url }}|Open Prefect Flow Run> |
| State Name | {{ flow_run.state.name }} |
| State Message | {{ flow_run.state.message or "—" }} |
| Error List | {% if flow_run.state.name in ["Failed", "Crashed"] %}{{ flow_run.state.message }}{% else %}None{% endif %} |
| Executed Via | {% if flow_run.auto_scheduled %}Scheduled{% else %}Manual{% endif %} |
| Deployment Tags | {% if deployment and deployment.tags %}{{ deployment.tags | join(", ") }}{% else %}—{% endif %} |
| GitHub Repository (version) | {% if variables is defined and variables.REPO_URL is defined and variables.REPO_URL %}{% if deployment and deployment.version %}<{{ variables.REPO_URL }}/tree/{{ deployment.version }}|{{ variables.REPO_URL | replace('https://', '') }} @ {{ deployment.version }}>{% else %}<{{ variables.REPO_URL }}|{{ variables.REPO_URL | replace('https://', '') }}>{% endif %}{% else %}—{% endif %} |
Notes:
- To populate the GitHub row, set a workspace variable named REPO_URL (for example `https://github.com/org/repo`). If a deployment version is present, the link will point to /tree/{{ deployment.version }}.
- “Executed Via” uses flow_run.auto_scheduled to distinguish Scheduled vs Manual.Bryan
12/11/2025, 12:42 AMMarvin
12/11/2025, 12:42 AMMarvin
12/11/2025, 12:48 AM| to fake a table, Teams often stops parsing Markdown links on those lines and treats the pipes as column separators. That’s why your “<…|Open Prefect Flow Run>” didn’t render as a hyperlink and looked like multiple columns.
Two reliable fixes
- If you must keep the 2‑column look in a single Prefect message, keep the “table” for visual alignment but put actual clickable links underneath as a short “Links” section.
- If you truly need a native 2‑column layout with clickable links inside the rows, use a Power Automate flow to post an Adaptive Card with a FactSet (or buttons). That’s the only way to get a real table-like layout with fully working links.
Updated Prefect Automation body (keeps the visual table, adds working links below)
This version:
- Keeps the 2‑column look with pipes
- Uses “Open Prefect Flow Run” text in the Value cell, then adds a clickable link below in a Links section
- Tries to populate the GitHub row from a workspace variable REPO_URL, falling back to scanning deployment.pull_steps for a repository URL
- Shows Scheduled vs Manual from flow_run.auto_scheduled
- Lists deployment tags
{# --- Try to find a repository URL --- #}
{% set repo_url = None %}
{% if variables is defined and variables.REPO_URL %}
{% set repo_url = variables.REPO_URL %}
{% elif deployment and deployment.pull_steps %}
{% for s in deployment.pull_steps %}
{% if s.get('type') in ['git', 'git_clone', 'git-repo', 'pull', 'checkout'] %}
{% set repo_url = s.get('repository') or s.get('url') or s.get('repo') %}
{% endif %}
{% endfor %}
{% endif %}
{# --- Optional: compute a repo link that points at the version if available --- #}
{% if repo_url and deployment and deployment.version %}
{% set repo_link_url = repo_url.rstrip('/') ~ '/tree/' ~ deployment.version %}
{% set repo_link_text = (repo_url | replace('https://','') | replace('http://','')) ~ ' @ ' ~ deployment.version %}
{% elif repo_url %}
{% set repo_link_url = repo_url %}
{% set repo_link_text = (repo_url | replace('https://','') | replace('http://','')) %}
{% else %}
{% set repo_link_url = None %}
{% set repo_link_text = None %}
{% endif %}
Flow Run Results
| Field | Value |
|---------------------------- |-------|
| Flow run name | {{ flow_run.name }} |
| Flow name | {{ flow.name }} |
| Deployment Name | {{ deployment.name if deployment else "—" }} |
| Deployment Version | {{ deployment.version if deployment else "—" }} |
| Flow run URL | Open Prefect Flow Run |
| State Name | {{ flow_run.state.name }} |
| State Message | {{ flow_run.state.message or "—" }} |
| Error List | {% if flow_run.state.name in ["Failed", "Crashed"] %}{{ flow_run.state.message or "—" }}{% else %}—{% endif %} |
| Executed Via | {% if flow_run.auto_scheduled %}Scheduled{% else %}Manual{% endif %} |
| Deployment Tags | {% if deployment and deployment.tags %}{{ deployment.tags | join(", ") }}{% else %}—{% endif %} |
| GitHub Repository (version) | {% if repo_link_text %}{{ repo_link_text }}{% else %}—{% endif %} |
Links:
- <{{ flow_run|ui_url }}|Open Prefect Flow Run>
{% if repo_link_url %}- <{{ repo_link_url }}|View Repository>{% endif %}
If you want a true two‑column layout with clickable links inside the row
Use a Power Automate flow to post an Adaptive Card to Teams. In your flow, construct a card with a FactSet and include buttons (Action.OpenUrl) for reliable links. Example card body:
```
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",Marvin
12/11/2025, 12:48 AMREPO_URL (e.g., https://github.com/org/repo), or
- Your deployment was created with source pull steps that include a Git repository; the template scans deployment.pull_steps for a repository or url.
- If neither is present, there’s nothing to display and it will show “—”. If you can share how the deployment was created (e.g., flow.from_source(...) vs local path), I can tailor the repo extraction to your setup.
If you’d like to stick with the table look, I recommend the first snippet (table + “Links” section). If you want fully native two-column with clickable links inside rows, go with the Power Automate Adaptive Card approach.Bryan
12/11/2025, 12:52 AMMarvin
12/11/2025, 12:52 AMMarvin
12/11/2025, 12:54 AMBryan
12/11/2025, 1:10 AMMarvin
12/11/2025, 1:10 AMresearch_prefect_topic
📊 Tools used:
• research_prefect_topic (1x)Bryan
12/11/2025, 1:58 AMMarvin
12/11/2025, 1:58 AMMarvin
12/11/2025, 1:58 AMBryan
12/11/2025, 1:58 AMMarvin
12/11/2025, 1:58 AMMarvin
12/11/2025, 2:01 AMdeployment.version_info.url)
- Sets the Flow Run URL to the full UI URL (via prefect.runtime.flow_run.ui_url())
- Removes any extra/bottom link
Drop-in snippet:
from prefect.context import FlowRunContext
from prefect.runtime import flow_run as runtime_flow_run
from prefect.client.orchestration import get_client
async def get_links_for_slack():
# Full URL to the Prefect Flow Run
flow_run_url = runtime_flow_run.ui_url()
# URL to the exact code version from the Deployment -> Versions tab
source_url = None
ctx = FlowRunContext.get()
if ctx and ctx.flow_run and ctx.flow_run.deployment_id:
async with get_client() as client:
deployment = await client.read_deployment(ctx.flow_run.deployment_id)
if deployment.version_info and getattr(deployment.version_info, "url", None):
# Use the URL recorded by Prefect for this deployment version (e.g., GitHub link)
source_url = deployment.version_info.url
return flow_run_url, source_url
Example Slack blocks without the extra/bottom link:
flow_run_url, source_url = await get_links_for_slack()
blocks = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Flow Run URL:* <{flow_run_url}|{flow_run_url}>"
}
}
]
if source_url:
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Code Version:* <{source_url}|View on GitHub>"
}
})
Notes:
- We no longer construct the GitHub URL manually. We take it directly from deployment.version_info.url, which matches what you see in the UI’s Deployment → Versions tab.
- The flow run link uses prefect.runtime.flow_run.ui_url() to ensure it’s the full, direct URL to the run.
If you paste a snippet of your current Slack message builder, I can wire these changes directly into it.Bryan
12/11/2025, 2:05 AMMarvin
12/11/2025, 2:05 AMMarvin
12/11/2025, 2:09 AMui_url filter: {{ flow_run | ui_url }}
- The Deployment’s code version URL (the same link you see in Deployment → Versions) via: {{ deployment.version_info.url }}
Example Teams message body:
Flow run {{ flow_run.name }} entered state {{ flow_run.state.name }}.
View in Prefect: {{ flow_run | ui_url }}
{% if deployment and deployment.version_info and deployment.version_info.url %}
Code version: {{ deployment.version_info.url }}
{% else %}
Code version: Not available
{% endif %}
Optional subject:
Flow {{ flow_run.name }} {{ flow_run.state.name }}
Notes:
- ui_url is a built-in Jinja filter that returns the full Prefect UI URL for the object.
- deployment.version_info.url is only present if the deployment was created from source with versioning info. Use the conditional as shown to handle missing data.
Docs for reference:
- Automations templating overview: Automations (templating)
- Jinja filters (includes ui_url): Jinja filters for Automations
- Microsoft Teams notification block: Notification blocks API and Blocks conceptsBryan
12/11/2025, 2:13 AMMarvin
12/11/2025, 2:13 AMMarvin
12/11/2025, 2:14 AMsend-notification action’s block_document_id is not templatable, so it must be a fixed block per action.
Practical pattern: create multiple automations, each targeting the states you care about, and point each one at the appropriate Teams block.
Example setup (3 automations):
- Automation 1: Failures → teams-fail block
- Automation 2: Warnings/Info (e.g., Running/Scheduled) → teams-info block
- Automation 3: Completed → teams-warning block
YAML-style examples:
# 1) Failures -> Teams "fail" block
- name: "Notify Teams - Failed runs"
trigger:
type: flow-run-state-change
match:
flow_run.state.type: ["FAILED"]
actions:
- type: send-notification
block_document_id: "<UUID_TEAMS_FAIL_BLOCK>"
subject: "🚨 Flow failed: {{ flow_run.name }}"
body: |
Flow: {{ flow_run.name }}
State: {{ flow_run.state.name }}
{{ flow_run.state.message }}
View: {{ flow_run | ui_url }}
{% if deployment and deployment.version_info and deployment.version_info.url %}
Code: {{ deployment.version_info.url }}
{% endif %}
# 2) Info (Running/Scheduled) -> Teams "info" block
- name: "Notify Teams - Info updates"
trigger:
type: flow-run-state-change
match:
flow_run.state.type: ["RUNNING", "SCHEDULED"]
actions:
- type: send-notification
block_document_id: "<UUID_TEAMS_INFO_BLOCK>"
subject: "ℹ️ Flow update: {{ flow_run.name }} is {{ flow_run.state.name }}"
body: |
Flow: {{ flow_run.name }}
State: {{ flow_run.state.name }}
View: {{ flow_run | ui_url }}
# 3) Completed -> Teams "warning" block (as you defined)
- name: "Notify Teams - Completed runs"
trigger:
type: flow-run-state-change
match:
flow_run.state.type: ["COMPLETED"]
actions:
- type: send-notification
block_document_id: "<UUID_TEAMS_WARNING_BLOCK>"
subject: "✅ Flow completed: {{ flow_run.name }}"
body: |
Flow: {{ flow_run.name }}
State: {{ flow_run.state.name }}
View: {{ flow_run | ui_url }}
Notes:
- Use the state TYPEs in match (COMPLETED, FAILED, RUNNING, SCHEDULED). These are reliable across names.
- You can still tailor the subject/body with Jinja as shown.
- If you also want to catch “Crashed”, add it to the first automation’s match list: ["FAILED", "CRASHED"].
If you share your three Teams block IDs, I can plug them into a ready-to-paste JSON/YAML for you.