Nikhil Jain
04/02/2026, 6:15 PMfrom prefect.blocks.notifications import SlackWebhook
from prefect import flow
def send_slack_message(mesg):
# message slack channel
slack_webhook_block = SlackWebhook.load('ideon-updates-slack-notification')
slack_webhook_block.notify(mesg)
@flow
def my_flow():
send_slack_message('this is a test message')
But when I run the flow locally I am getting an error like this:
Traceback (most recent call last):
File "/Users/nikhiljain/Code/artemis/.venv/lib/python3.12/site-packages/prefect/flow_engine.py", line 1371, in run_context
yield self
File "/Users/nikhiljain/Code/artemis/.venv/lib/python3.12/site-packages/prefect/flow_engine.py", line 1433, in run_flow_async
await engine.call_flow_fn()
File "/Users/nikhiljain/Code/artemis/.venv/lib/python3.12/site-packages/prefect/flow_engine.py", line 1385, in call_flow_fn
result = await call_with_parameters(self.flow.fn, self.parameters)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/nikhiljain/Code/artemis/server/flows/auto_qa/cms_plan_validation_flow.py", line 166, in cms_plan_ids_validation_flow
send_slack_message(message)
File "/Users/nikhiljain/Code/artemis/server/flows/auto_loading/utils.py", line 17, in send_slack_message
slack_webhook_block.notify(mesg)
^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'coroutine' object has no attribute 'notify'
Note that this is happening when I am running the flow locally.Marvin
04/02/2026, 6:16 PMMarvin
04/02/2026, 6:18 PMBlock.load(...) and most notification block methods are async-compatible. When called inside an async context (like a flow), SlackWebhook.load(...) returns a coroutine that you must await. The same applies to notify(...).
That’s why you’re seeing 'coroutine' object has no attribute 'notify' — SlackWebhook.load(...) returned a coroutine, not a block instance.
Fix: make your helper async and await both load and notify, then make your flow async and await the helper.
Here’s a corrected version using the built-in notification block:
from prefect.blocks.notifications import SlackWebhook
from prefect import flow
async def send_slack_message(msg: str):
webhook = await SlackWebhook.load("ideon-updates-slack-notification")
await webhook.notify(msg)
@flow
async def my_flow():
await send_slack_message("this is a test message")
if __name__ == "__main__":
my_flow()
A few notes:
- This will also work the same when running “locally” — the key is that inside a flow you’re in an async context, so you must await async-compatible methods.
- If you prefer the dedicated Slack integration (attachments/blocks, richer features), you can use prefect-slack similarly:
from prefect import flow
from prefect_slack import SlackWebhook
async def send_slack_message(msg: str):
webhook = await SlackWebhook.load("ideon-updates-slack-notification")
await webhook.notify(msg)
@flow
async def my_flow():
await send_slack_message("this is a test message")
Docs for reference:
- Built-in notification blocks: Notification blocks
- Prefect Slack integration: prefect-slack docs
If you’d prefer to keep my_flow as a sync function, we can wrap the awaits with Prefect’s sync utilities — but the async flow pattern above is the most straightforward.Nikhil Jain
04/02/2026, 6:25 PMsend_slack_message is working fine for the sync flows. Should I create separate versions of send_slack_message for the two kinds of flows (sync vs async)? e.g.:
def send_slack_message(mesg):
# message slack channel
slack_webhook_block = SlackWebhook.load('ideon-updates-slack-notification')
slack_webhook_block.notify(mesg)
async def send_slack_message_async(mesg):
slack_webhook_block = await SlackWebhook.load('ideon-updates-slack-notification')
await slack_webhook_block.notify(mesg)
Or is there a way to somehow reuse the same function for sync as well as async flows?
@MarvinMarvin
04/02/2026, 6:37 PMMarvin
04/02/2026, 6:40 PMfrom prefect.blocks.notifications import SlackWebhook
from prefect.utilities.asyncutils import sync_compatible
from prefect import flow
@sync_compatible
async def send_slack_message(msg: str):
block = await SlackWebhook.load("ideon-updates-slack-notification")
await block.notify(msg)
@flow
def sync_flow():
send_slack_message("hello from sync")
@flow
async def async_flow():
await send_slack_message("hello from async")
Docs: sync_compatible API
Option B (mirrors Prefect internals): pair sync and async impls with async_dispatch
- Keep a true sync implementation and a true async implementation, and let async_dispatch route based on context.
- In async flows you must await; in sync flows you just call it.
Example:
from prefect.blocks.notifications import SlackWebhook
from prefect._internal.compatibility.async_dispatch import async_dispatch
from prefect import flow
async def _send_slack_message_async(msg: str):
block = await SlackWebhook.load("ideon-updates-slack-notification")
await block.notify(msg)
@async_dispatch(_send_slack_message_async)
def send_slack_message(msg: str):
block = SlackWebhook.load("ideon-updates-slack-notification")
block.notify(msg)
@flow
def sync_flow():
send_slack_message("hello from sync")
@flow
async def async_flow():
await send_slack_message("hello from async")
Why your original code failed in async flows:
- In Prefect 3.x, Block.load() is context-aware; inside an async context it returns a coroutine you must await. That’s why you saw "'coroutine' object has no attribute 'notify'".
- Source for context-aware load: Block.load implementation
Either Option A or B will let you reuse a single function across both sync and async flows. If you want to avoid internal APIs, use Option A.