<@ULVA73B9P> I'm trying to send Slack notification...
# marvin-ai
r
@Marvin I'm trying to send Slack notifications, read this issue and this code snippet but still struggling. The following code returns
Flow run encountered an exception: TypeError: object NoneType can't be used in 'await' expression
.
Copy code
def notify_on_slack(slack_text: str, channel: str = "channel1"):
    """Send Slack notification - following the working pattern"""

    async def notify(slack_text: str):
        try:
            if channel == "channel1" or channel == "channel2":
                slack_webhook_block = await SlackWebhook.aload(f"slack-{channel}")
                if slack_webhook_block is None:
                    raise ValueError(f"Slack webhook block 'slack-{channel}' not found")
            else:
                raise ValueError(f"Invalid channel: {channel}")

            cleaned_text = cleandoc(slack_text)

            result = await slack_webhook_block.notify(cleaned_text)
            return result

        except Exception as e:
            print(f"Failed to send Slack notification to {channel}: {e}")
            raise e

    return asyncio.run(notify(slack_text))
When I remove the await before
slack_webhook_block.notify
it doesn't throw any error but the message is not sent in the Slack channel either. What am I missing?
m
thought for 191.4 seconds
You’re running into two separate issues: 1) You’re awaiting a synchronous method - In Prefect 3.x,
SlackWebhook.notify(...)
is synchronous and returns
None
. -
await slack_webhook_block.notify(...)
tries to await
None
TypeError: object NoneType can't be used in 'await' expression
. 2) You’re wrapping everything in asyncio.run - Don’t call
asyncio.run
from within Prefect flows/tasks; Prefect manages the event loop. Make your wrapper either fully sync or fully async without
asyncio.run
. Here are two clean patterns that work. Pattern A: Keep it simple (sync everywhere)
Copy code
from textwrap import dedent as cleandoc
from prefect.blocks.notifications import SlackWebhook

def notify_on_slack(slack_text: str, channel: str = "channel1"):
    if channel not in {"channel1", "channel2"}:
        raise ValueError(f"Invalid channel: {channel}")

    block = SlackWebhook.load(f"slack-{channel}")  # sync load
    block.notify(cleandoc(slack_text))            # sync notify
Use it from a sync flow or task without any async ceremony:
Copy code
from prefect import flow

@flow
def my_flow():
    notify_on_slack("Hello from Prefect!", "channel1")
Pattern B: If you’re already in an async function - Load the block asynchronously if you like, but still call
.notify
without
await
. - If you want to avoid blocking the event loop, run the sync call in a thread.
Copy code
from textwrap import dedent as cleandoc
from prefect.blocks.notifications import SlackWebhook
import anyio

async def notify_on_slack_async(slack_text: str, channel: str = "channel1"):
    if channel not in {"channel1", "channel2"}:
        raise ValueError(f"Invalid channel: {channel}")

    block = await SlackWebhook.aload(f"slack-{channel}")
    # Notify is sync; run it in a worker thread so you don't block the loop
    await anyio.to_thread.run_sync(block.notify, cleandoc(slack_text))
Common pitfalls if you still don’t see a message: - The block name is wrong. Ensure a block named exactly
slack-channel1
(or
slack-channel2
) exists and has a valid Incoming Webhook URL. - The webhook belongs to a different Slack workspace or channel than you’re watching. - Your Slack app doesn’t have Incoming Webhooks enabled for that channel. - Network egress blocked. Normally you’d see an HTTP error in logs if Slack rejected the request. Quick sanity checks:
Copy code
# 1) Will raise if the block doesn’t exist
from prefect.blocks.notifications import SlackWebhook
SlackWebhook.load("slack-channel1")

# 2) If you have the raw webhook URL, test directly:
import requests
<http://requests.post|requests.post>("<your webhook url>", json={"text": "Ping from requests"})
References - Slack webhook block API (Prefect 3.x): https://docs.prefect.io/latest/api-ref/blocks/#prefect.blocks.notifications.SlackWebhook - Your linked context: issue and code snippet If you can share: - Your Prefect version - How you created the blocks (names, workspace) - Where you’re calling
notify_on_slack
from (sync vs async flow/task) …I can point out the exact fix instead of playing guess-the-webhook. I live for it.
r
@Marvin I used pattern A and it doesn't raise errors but the message is not sent yet, the block is being loaded successfully and the channel used for tests was used before for receiving alerts. Prefect version 3.4.13 and the function is called from a sync flow
m
thought for 46.9 seconds
Thanks — that narrows it down. If Pattern A runs without exceptions but nothing shows up in Slack, it’s usually one of these: 1) Apprise isn’t installed - Prefect’s SlackWebhook block uses Apprise under the hood. If it’s missing, the call can quietly do nothing. - Quick check:
Copy code
import importlib.util
print(importlib.util.find_spec("apprise"))
If that prints None:
Copy code
pip install apprise
# or
pip install "prefect[notifications]"
2) The webhook itself isn’t working (or points to a different channel/workspace) - Sanity test the exact URL outside Prefect:
Copy code
import requests
url = "<your slack incoming webhook url>"
r = <http://requests.post|requests.post>(url, json={"text": "Ping from requests"})
print(r.status_code, r.text)
r.raise_for_status()
If this doesn’t post, the webhook/app is disabled or tied to a different channel/workspace. 3) Message content ends up empty - Some formatting pipelines can strip everything. Try the most boring possible payload:
Copy code
from prefect.blocks.notifications import SlackWebhook
SlackWebhook.load("slack-channel1").notify("hello world")
4) You’re expecting
channel
to change the Slack destination - The
channel
argument in your function only selects which block to load. The Slack destination is determined by the webhook URL stored in that block. If `slack-channel1`’s URL points to a different channel, that’s where it will go. 5) Networking/logging hides the problem - Try DEBUG logging to surface HTTP failures:
Copy code
import logging
logging.getLogger("prefect").setLevel(logging.DEBUG)
logging.getLogger("apprise").setLevel(logging.DEBUG)
- If you’re running in a worker/container/VPC, confirm outbound HTTPS is allowed. A minimal end-to-end check (no cleverness, just facts):
Copy code
from prefect.blocks.notifications import SlackWebhook

# 1) Create a throwaway block with a fresh webhook URL
SlackWebhook(url="<https://hooks.slack.com/services/...>").save("slack-smoke-test", overwrite=True)

# 2) Send a message
SlackWebhook.load("slack-smoke-test").notify("smoke test: hello from Prefect 3.4.13")
If that still doesn’t show up: - What’s the output of the
<http://requests.post|requests.post>
test? - Is
apprise
installed? - Are you running this in the same environment where earlier alerts worked (those may have used a different Slack integration than incoming webhooks)? If you share: - Whether
apprise
is installed - The HTTP status/text from the direct
<http://requests.post|requests.post>
test - Where the flow runs (local, container, worker, VPC) …I can point you to the exact culprit. With only a minor sigh.
r
@Marvin the flow runs on Amazon ECS. When enabling the loggers I get this output in the screenshot, nothing off it seems. Tried all the other suggested solutions and still not getting the message. The webhook was tested through API call and through Prefect automations, it doesn't work when coded
m
thought for 118.6 seconds
Got it — thanks for the details. If Pattern A runs cleanly on ECS but nothing lands in Slack, the most common culprit is that Apprise is returning False and the SlackWebhook block doesn’t raise on failure. Let’s prove (or disprove) that from inside the ECS task and bypass any block ambiguity. Please run this inside the same ECS task that runs your flow: 1) Verify you’re in the expected Prefect workspace and check the block’s URL is actually present
Copy code
from prefect.blocks.notifications import SlackWebhook
from prefect import runtime

print("workspace:", getattr(runtime, "workspace_name", "unknown"))

blk = SlackWebhook.load("slack-channel1")
# Do NOT print the URL; just confirm it exists and has a sensible length
url = blk.url.get_secret_value()
print("url length:", len(url), "endswith:", url[-8:])
2) Check apprise version and call it directly (prints whether it thinks it sent anything)
Copy code
import apprise
print("apprise version:", apprise.__version__)

a = apprise.Apprise()
a.add(url)
ok = a.notify(body="hello from ECS via apprise")
print("apprise.notify returned:", ok)
3) Bypass apprise: post directly to Slack using the same URL
Copy code
import requests
r = <http://requests.post|requests.post>(url, json={"text": "hello from ECS via requests"})
print("requests:", r.status_code, r.text[:200])
r.raise_for_status()
Interpretation - If step 2 returns False but step 3 is 200/ok: the issue is Apprise-in-ECS. Pin/ensure the dependency: - pip install "prefect[notifications]==3.4.13" - And ensure a recent Apprise (e.g., >=1.9.4). Print it after install to be sure. - If both 2 and 3 fail: this is ECS networking/DNS/proxy egress. You’ll usually see a non-200 or an exception in step 3. - If both succeed, but
SlackWebhook.load(...).notify("text")
still “does nothing”: we’re likely loading a different block (different workspace) or the call path swallows the apprise return. Confirm the workspace printed above matches where you created the block, and try this exact minimal call:
Copy code
from prefect.blocks.notifications import SlackWebhook
SlackWebhook.load("slack-channel1").notify("hello world (minimal)")
A few more checks to avoid wild goose chases: - Confirm the ECS image actually has the right deps:
Copy code
import prefect, apprise
print("prefect:", prefect.__version__, "apprise:", apprise.__version__)
- Make sure the block you’re loading is the one you tested with automations. If you have multiple workspaces (dev/prod), the same block name can exist with different URLs. - If you must keep moving, you can skip Apprise entirely in your flow:
Copy code
from prefect.blocks.notifications import SlackWebhook
import requests

def notify_on_slack(text, block_name="slack-channel1"):
    url = SlackWebhook.load(block_name).url.get_secret_value()
    r = <http://requests.post|requests.post>(url, json={"text": text})
    r.raise_for_status()
If you can paste back: - The outputs of steps 1–3 (mask the last 8 chars if you like; we only need the status/booleans) - The printed Prefect/Apprise versions - The printed workspace name …I’ll pinpoint the exact issue. I’m practically thrilled.
r
For reference in case someone has the same error, it works with the
request
solution and I saw a fix was already made here, as I understand it'll be available in the next Prefect version