Airflow 129 sends alerts through notifiers, small classes that a on_failure_callback, on_success_callback, deadline or DAG-level callback can call: the Slack 353 provider (9.10.2, in the official image) has SlackNotifier for the Web API and SlackWebhookNotifier for incoming webhooks; the PagerDuty provider (apache-airflow-providers-pagerduty, 5.3.0) has PagerdutyNotifier, which raises an incident with a summary, severity, source and a dedup_key so repeated failures update one incident instead of paging again. BookNest's DAG attaches a Slack notifier to every task:
ON_FAILURE = SlackWebhookNotifier( # posts to the URL in connection booknest_alerts
slack_webhook_conn_id="booknest_alerts",
text="BookNest check failed: {{ ti.dag_id }}.{{ ti.task_id }} for {{ ds }} "
"(try {{ ti.try_number }}) {{ ti.log_url }}")
...
default_args={"on_failure_callback": ON_FAILURE},No real Slack workspace was used: the connection booknest_alerts points the webhook at l2-alerts, a 20-line Python server on the Docker 514 network that prints what it receives, exactly as Slack would get it:
04:17:09 POST /services/T0000/B0000/booknest-sample text='BookNest check failed: booknest_quality.mart_day_complete for 2026-06-29 (try 1) http://localhost:8080/dags/booknest_quality/runs/fail-0629/tasks/mart_day_complete?... 04:19:26 POST /services/T0000/B0000/booknest-sample text='Deadline missed: booknest_quality fail-0629 (failed) was due by 2026-10-02 04:19:05.731304+00:00' 04:19:28 POST /services/T0000/B0000/booknest-sample text='Deadline missed: booknest_quality late-0628 (running) was due by 2026-10-02 04:19:27.287320+00:00'
The log link points at localhost:8080 because [api] base_url was left at its default; set it to the URL your team opens. PagerDuty was not run here (it needs an account); configure it the same way, with an Events API integration key in a pagerduty_events connection. Page a person only for failures that block a promised deadline, send the rest to a channel, include the run, day and log link, and deduplicate.