> ## Documentation Index
> Fetch the complete documentation index at: https://astronomer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage Apache Airflow® Dag notifications

When you're using a data orchestration tool, how do you know when something has gone wrong? [Apache Airflow®](https://airflow.apache.org/) users can check the Airflow UI to determine the status of their Dags, but this is an inefficient way of managing errors systematically, especially if certain failures need to be addressed promptly or by multiple team members. Fortunately, Airflow has several notification mechanisms that can be used to configure error notifications in a way that works for your organization.

In this guide, you'll learn how to set up common Airflow notification mechanisms including [email (SMTP) notifications](#email-smtp-notifications), [Airflow callbacks](#airflow-callbacks) and [notifiers](#pre-built-notifiers).

## Assumed knowledge

To get the most out of this guide, you should have an understanding of:

* Airflow Dags. See [Introduction to Airflow Dags](/docs/learn/dags).
* Airflow operators. See [Operators 101](/docs/learn/what-is-an-operator).
* Airflow decorators. See [Introduction to the TaskFlow API and Airflow decorators](/docs/learn/airflow-decorators).
* Airflow connections. See [Manage connections in Apache Airflow](/docs/learn/connections).

## Notification types

When setting up Airflow notifications, you must first decide between using Airflow's built-in notification system, an external monitoring service, or a combination of both. The three types of notifications available when running Airflow on Astro are:

<CardGroup cols={3}>
  <Card title="Airflow notifications" icon="bell" iconType="light" href="#airflow-notification-concepts">
    Airflow notifications are available in open-source Airflow itself and defined using callback parameters and/or configuration variables relating to email and SMTP.
  </Card>

  <Card title="Astro alerts" icon="alarm-clock" iconType="light" href="/docs/astro/alerts">
    Astro alerts are a feature of Astro that allows you to configure alerts for many Dags and Deployments at once.
  </Card>

  <Card title="Astro Observe" icon="radar" iconType="light" href="/docs/astro/astro-observe">
    Astro Observe is a product provided by Astronomer that includes the ability to define data products spanning multiple Dags and Deployments, and define Service Level Agreements (SLAs) on them.
  </Card>
</CardGroup>

The advantage of Airflow notifications is that you can define them directly in your Dag code. The downside is that you need Airflow to be running to send notifications, which means you might run into silent failures if there is an issue with your Airflow infrastructure. Airflow notifications also have some limitations, for example relating to defining SLAs and timeouts.

For the cases where Airflow notifications aren't sufficient, [Astro alerts](/docs/astro/alerts) and [Astro Observe](/docs/astro/astro-observe) provide an additional level of observability. For guidance on when to choose Airflow notifications or Astro alerts, see [When to use Airflow or Astro alerts for your pipelines on Astro](/docs/astro/best-practices/airflow-vs-astro-alerts).

## Airflow notification concepts

When defining notifications in Airflow you should understand the following concepts:

<CardGroup cols={2}>
  <Card title="Email (SMTP)" icon="envelope" iconType="light" href="#email-smtp-notifications">
    Airflow allows you to send email alerts using an external SMTP server. There are different ways to configure email notifications.
  </Card>

  <Card title="Airflow Callbacks" icon="phone-flip" iconType="light" href="#airflow-callbacks">
    Dag- and task-level parameters that allow you to define code that should be executed when a Dag or task reaches a specific state. Can use plain Python functions or notifiers.
  </Card>

  <Card title="Airflow Notifiers" icon="bell" iconType="light" href="#pre-built-notifiers">
    A type of Airflow class like operators or hooks, that can be used to standardize your code. Each notifier has a `.notify()` method for sending notifications.
  </Card>

  <Card title="Airflow Listeners" icon="ear-listen" iconType="light" href="https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/listeners.html">
    An advanced Airflow feature that runs in the background and executes code when certain events occur anywhere in your Airflow environment.
  </Card>
</CardGroup>

### Choose your Airflow notification method

It's best practice to use pre-built solutions whenever possible. This approach makes your Dags more robust by reducing custom code and standardizing notifications across different Airflow environments.

If you want to deliver notifications to email, use the [SmtpNotifier](#use-the-smtpnotifier) or [`EmailOperator`](#use-the-emailoperator). If you want to use another email service like SendGrid or Amazon SES, see the [Airflow documentation](https://airflow.apache.org/docs/apache-airflow/stable/howto/email-config.html) for more information.

If you want to be notified using another system, check if a [notifier class](#pre-built-notifiers) exists for your use case. See the Airflow documentation for [an up-to-date list of available Notifiers](https://airflow.apache.org/docs/apache-airflow-providers/core-extensions/notifications.html) and the [Apprise wiki](https://github.com/caronc/apprise/wiki) for a list of services the [AppriseNotifier](https://airflow.apache.org/docs/apache-airflow-providers-apprise/stable/_api/airflow/providers/apprise/notifications/apprise/index.html) can connect to.

Only use custom [callback functions](#airflow-callbacks) when no notifier is available for your use case. Consider writing a [custom notifier](#pre-built-notifiers) to standardize the code you use to send notifications.

If you want to execute code based on events happening anywhere in your Airflow environment, for example whenever any asset is updated, a Dag run fails, or a new import error is detected, you can use [Airflow listeners](https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/listeners.html#listeners).

## Email (SMTP) notifications

Airflow email notifications can be set up in three different ways:

* (Recommended) You can provide the [`SmtpNotifier`](#use-the-smtpnotifier) with any [callback parameter](#airflow-callbacks) to send emails when a Dag or task reaches a specific state.
* (Recommended) You can use the [`EmailOperator`](#use-the-emailoperator) to create dedicated tasks in your Dags to send emails.
* (Legacy) You can configure email notifications using the `email` task parameter in combination with Airflow configuration variables in the `SMTP` section. This approach has limitations and will be removed in a future version. See [(Legacy) Email notifications using configuration variables](#legacy-email-notifications-using-configuration-variables).

All email notifications require you to install the [SMTP provider](https://airflow.apache.org/registry/providers/smtp/) by adding it to your `requirements.txt` file.

```text wrap theme={null}
apache-airflow-providers-smtp
```

### Use the SmtpNotifier

The `SmtpNotifier` is a pre-built notifier that can be provided to any [callback parameter](#airflow-callbacks) to send emails when a Dag or task reaches a specific state.

To connect the notifier to your SMTP server, you need to create an [Airflow connection](/docs/learn/connections), for example by setting the following environment variable to create the `smtp_default` connection:

```text wrap theme={null}
AIRFLOW_CONN_SMTP_DEFAULT='{
   "conn_type":"smtp",
   "host":"smtp.yourdomain.com",
   "port":<your-port>,
   "login":"<your-username>",
   "password":"<your-password>",
   "extra":{
      "disable_ssl":<your-setting>,
      "disable_tls":<your-setting>
   }
}'
```

The main parameters to configure for the [SmtpNotifier](https://airflow.apache.org/registry/providers/smtp#smtp-smtp-SmtpNotifier) are:

* `smtp_conn_id`: The ID of the Airflow connection to your SMTP server. Default: `smtp_default`.
* `to`: The email address to send the email to. You can provide a single email address as a string or multiple in a list. Default: `None`. This parameter is required.
* `cc`: The email address to send the email to as a carbon copy. You can provide a single email address as a string or multiple in a list. Default: `None`.
* `bcc`: The email address to send the email to as a blind carbon copy. You can provide a single email address as a string or multiple in a list. Default: `None`.
* `from_email`: The email address to send the email from. Default: `None`.
* `subject`: The subject of the email. Default: `None`.
* `html_content`: The HTML content of the email. Default: `None`.
* `files`: The files to attach to the email as a list of file paths. Default: `None`.
* `custom_headers`: A dictionary of custom headers to add to the email. Default: `None`.

You provide the instantiated notifier class directly to any [callback parameter](#airflow-callbacks) to send emails when that callback is triggered. To add information about the Dag run to the email, use [Jinja templating](/docs/learn/templating). All parameters listed above other than `smtp_conn_id` are templatable.

For example, to send an email notification when a task fails that includes information about the task, as well as the error message (`{{ exception }}`) and a link to the task's log (`{{ ti.log_url }}`), you can use the SmtpNotifier as shown in the code example below.

```python expandable wrap theme={null}
@task(
    on_failure_callback=SmtpNotifier(
        from_email="testnotifier@test.com",
        to=["primary@test.com"],
        cc=["manager@test.com", "team-lead@test.com"],
        bcc=["audit@test.com", "monitoring@test.com"],
        subject="{{ ti.task_id }} failed in {{ dag.dag_id }}",
        html_content="""
            <html>
                <body>
                    <h2 style="color: red;">Task Failure Alert</h2>
                    <p><strong>Task:</strong> {{ ti.task_id }}</p>
                    <p><strong>DAG:</strong> {{ dag.dag_id }}</p>
                    <p><strong>Execution Date:</strong> {{ ts }}</p>
                    <p><strong>Log URL:</strong> {{ ti.log_url }}</p>
                    <hr>
                    <h3>Error Details:</h3>
                    <pre>{{ exception }}</pre>
                </body>
            </html>
        """,
        files=["include/debug_info.json"],
        custom_headers={
            "X-Priority": "1",
            "X-Airflow-DAG": "{{ dag.dag_id }}",
            "X-Airflow-Task": "{{ ti.task_id }}",
            "Reply-To": "airflow-support@test.com"
        }
    )
)
def test_notifier_advanced():
    raise Exception("Oops, too much vibe coding!")
```

The resulting email looks like this:

<Frame>
  <img src="https://mintcdn.com/astronomer/UcTR28b0xqXhSSb1/images/img/guides/3_1_error-notifications-in-airflow-smtp-notifier-mailhog.png?fit=max&auto=format&n=UcTR28b0xqXhSSb1&q=85&s=15633db084ca60a5b01e46818ee13783" alt="Example email notification using the SmtpNotifier" width="925" height="551" data-path="images/img/guides/3_1_error-notifications-in-airflow-smtp-notifier-mailhog.png" />
</Frame>

<Note>
  If you'd like to test email formatting locally without connecting to a real SMTP server, you can use [MailHog](https://github.com/mailhog/MailHog) in a local Docker container to catch emails and view them in a web interface. Start the MailHog server using `docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog` and use the following connection string:

  ```text wrap theme={null}
  AIRFLOW_CONN_SMTP_DEFAULT='{
     "conn_type":"smtp",
     "host":"localhost",
     "port":1025,
     "login":"",
     "password":"",
     "extra":{
        "disable_ssl":true,
        "disable_tls":true
     }
  }'
  ```
</Note>

### Use the `EmailOperator`

You can use the `EmailOperator` to create dedicated tasks in your Dags to send emails. As with the `SmtpNotifier`, you need to have the [SMTP provider](https://airflow.apache.org/registry/providers/smtp/) installed and create an [Airflow connection](/docs/learn/connections) to your SMTP server. The main parameters are analogous to the `SmtpNotifier`.

```python wrap theme={null}
from airflow.providers.smtp.operators.smtp import EmailOperator

EmailOperator(
    task_id="send_email",
    conn_id="smtp_default",
    from_email="caller@mydomain.io",
    to="receiver@mydomain.io",     
    subject="Test Email",
    html_content="This is a test email"
)
```

### (Legacy) Email notifications using configuration variables

In older Airflow versions it was common to configure email notifications using a mix of configuration variables and task parameters. This approach is being deprecated in Airflow 3.0 and will be removed in a future version.

To configure email notifications using configuration variables, both the [SMTP](https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#smtp) configuration variables and the `email` task parameter are needed. Note that you can't use an `AIRFLOW_CONN_` connection with the email configuration parameters in Airflow 3.

The SMTP configuration variables define the connection to your SMTP server.

```text wrap theme={null}
AIRFLOW__SMTP__SMTP_HOST=<your-smtp-host>
AIRFLOW__SMTP__SMTP_PORT=<your-port>
AIRFLOW__SMTP__SMTP_USER=<your-username>
AIRFLOW__SMTP__SMTP_PASSWORD=<your-password>
AIRFLOW__SMTP__SMTP_SSL=<your-setting>
AIRFLOW__SMTP__SMTP_TLS=<your-setting>
AIRFLOW__SMTP__SMTP_MAIL_FROM=<your-from-email>
```

In order for a task to be able to send an email if it fails or retries, you need to provide the `email` task parameter to the task to specify who to send the email to.

It is common to provide this in the `default_args` parameter of a Dag to apply it to all tasks in the Dag.

```python wrap theme={null}
from airflow.sdk import dag

@dag(
    default_args={
        "email": ["myname@mydomain.com"],
    }
)
def my_dag():
```

But you can also provide it at the task level to override the default.

```python wrap theme={null}
@task(email=["myfriend@mydomain.com"])
def test():
    print("Test")
    raise Exception("Test Exception")
```

If you want a task to only send emails when it fails, set the `email_on_retry` parameter to `False`, if you want it to only send emails when it retries, set the `email_on_failure` parameter to `False`.

<Note>
  Most of the `AIRFLOW__EMAIL__` configuration variables are no longer supported in Airflow 3.0 for SMTP-based email notifications. Some of those parameters are still used when utilizing other email notification methods such as SendGrid or Amazon SES, see [Email Configuration](https://airflow.apache.org/docs/apache-airflow/stable/howto/email-config.html) in the Airflow documentation for more information.
</Note>

## Airflow callbacks

In Airflow you can define actions to be taken based on different Dag or task states using `*_callback` parameters:

* `on_success_callback`: Invoked when a task or Dag succeeds.
* `on_failure_callback`: Invoked when a task or Dag fails.
* `on_skipped_callback` : Invoked when a task is skipped. This callback only exists at the task level, and is only invoked when an `AirflowSkipException` is raised, not when a task is skipped due to other reasons, like a trigger rule.
* `on_execute_callback`: Invoked right before a task begins executing. This callback only exists at the task level.
* `on_retry_callback`: Invoked when a task is retried. This callback only exists at the task level.

You can provide any Python callable or [Airflow notifiers](#pre-built-notifiers) to the `*_callback` parameters. To execute multiple functions, you can provide several callback items to the same callback parameter in a list.

### Set Dag-level callbacks

To define a notification at the Dag level, you can set the `*_callback` parameter in your Dag instantiation. Dag-level notifications will trigger callback functions based on the terminal state of the entire Dag run. The example below shows one function being executed when the Dag succeeds and two functions being executed when the Dag fails (one custom function and one `SlackNotifier`).

```python wrap theme={null}
from airflow.sdk import dag
from airflow.providers.slack.notifications.slack_notifier import SlackNotifier

def my_success_callback_function(context):
    pass

def my_failure_callback_function(context):
    pass

@dag(
    on_success_callback=my_success_callback_function,
    on_failure_callback=[
        my_failure_callback_function,
        SlackNotifier(
            slack_conn_id="slack_conn",
            text="Dag failed",
            channel="alerts"
        )
    ],
)
```

<Note>
  Deadline alerts, which are executed when a Dag run exceeds a user-defined time threshold replace the removed SLA feature used with the `sla` and `sla_miss_callback` parameters. See [Deadline alerts](https://airflow.apache.org/docs/apache-airflow/stable/howto/deadline-alerts.html) in the Airflow documentation for more information.

  Astronomer customers should use [Astro alerts](/docs/astro/alerts) and [Astro Observe](/docs/astro/astro-observe) to define timeliness and freshness SLAs.
</Note>

### Set task-level callbacks

To apply a task-level callback to each task in your Dag, you can pass the callback function to the `default_args` parameter. Items listed in the dictionary provided to the `default_args` parameter will be set for each task in the Dag. While the example shows one callback function being assigned to each callback parameter, you can provide multiple callback functions and/or notifiers to the same callback parameter in a list as well.

```python wrap theme={null}
from airflow.sdk import dag

def my_execute_callback_function(context):
    pass

def my_retry_callback_function(context):
    pass

def my_success_callback_function(context):
    pass

def my_failure_callback_function(context):
    pass

def my_skipped_callback_function(context):
    pass

@dag(
    default_args={
        "on_execute_callback": my_execute_callback_function,
        "on_retry_callback": my_retry_callback_function,
        "on_success_callback": my_success_callback_function,
        "on_failure_callback": my_failure_callback_function,
        "on_skipped_callback": my_skipped_callback_function,
    }
)
```

For use cases where an individual task should use a specific callback, the task-level callback parameters can be defined in the task instantiation. Callbacks defined at the individual task level will override callbacks passed in using `default_args`.

<details open>
  <summary>TaskFlow</summary>

  ```python wrap theme={null}
  from airflow.sdk import task

  def my_execute_callback_function(context):
      pass

  def my_retry_callback_function(context):
      pass

  def my_success_callback_function(context):
      pass

  def my_failure_callback_function(context):
      pass

  def my_skipped_callback_function(context):
      pass

  @task(
      on_execute_callback=my_execute_callback_function,
      on_retry_callback=my_retry_callback_function,
      on_success_callback=my_success_callback_function,
      on_failure_callback=my_failure_callback_function,
      on_skipped_callback=my_skipped_callback_function,
  )
  def t1():
      return "hello"
  ```
</details>

<details>
  <summary>Traditional</summary>

  ```python wrap theme={null}
  from airflow.providers.standard.operators.python import PythonOperator

  def my_execute_callback_function(context):
      pass

  def my_retry_callback_function(context):
      pass

  def my_success_callback_function(context):
      pass

  def my_failure_callback_function(context):
      pass

  def my_skipped_callback_function(context):
      pass

  def say_hello():
      return "hello"

  t1 = PythonOperator(
      task_id="t1",
      python_callable=say_hello,
      on_execute_callback=my_execute_callback_function,
      on_retry_callback=my_retry_callback_function,
      on_success_callback=my_success_callback_function,
      on_failure_callback=my_failure_callback_function,
      on_skipped_callback=my_skipped_callback_function,
  )
  ```
</details>

### Pre-built notifiers

[Airflow notifiers](https://airflow.apache.org/docs/apache-airflow/stable/howto/notifications.html) are pre-built or custom classes and can be used to standardize and modularize the functions you use to send notifications. Notifiers can be passed to the relevant `*_callback` parameter of your Dag depending on what event you want to trigger the notification.

<Info>
  You can find a full list of all pre-built notifiers created for Airflow providers [here](https://airflow.apache.org/docs/apache-airflow-providers/core-extensions/notifications.html) and connect to [many more services](https://github.com/caronc/apprise/wiki) through the [AppriseNotifier](https://airflow.apache.org/docs/apache-airflow-providers-apprise/stable/_api/airflow/providers/apprise/notifications/apprise/index.html).
</Info>

Notifiers are defined in provider packages or imported from the `include` folder and can be used across any of your Dags. This feature has the advantage that community members can define and share functionality previously used in callback functions as Airflow modules, creating pre-built callbacks to send notifications to other data tools.

#### Example pre-built notifier: Slack

An example of a community provided pre-built notifier is the [SlackNotifier](https://airflow.apache.org/docs/apache-airflow-providers-slack/stable/_api/airflow/providers/slack/notifications/slack/index.html#module-airflow.providers.slack.notifications.slack).

It can be imported from the Slack provider package and used with any `*_callback` function:

```python expandable wrap theme={null}
"""
Example showing how to use the SlackNotifier. Needs a Slack connection set
up with Slack API Token for a Slack bot (starts with 'xoxb-...')
"""

from airflow.sdk import dag, task
from pendulum import datetime
from airflow.providers.slack.notifications.slack_notifier import SlackNotifier

SLACK_CONNECTION_ID = "slack_conn"
SLACK_CHANNEL = "alerts"
SLACK_MESSAGE = """
Hello! The {{ ti.task_id }} task is saying hi :wave: 
Today is the {{ ds }} and this task finished with the state: {{ ti.state }} :tada:.
"""


@dag
def slack_notifier_example_dag():
    @task(
        on_success_callback=SlackNotifier(
            slack_conn_id=SLACK_CONNECTION_ID,
            text=SLACK_MESSAGE,
            channel=SLACK_CHANNEL,
        ),
    )
    def post_to_slack():
        return 10

    post_to_slack()


slack_notifier_example_dag()
```

The Dag above has one task sending a notification to Slack. It uses a Slack [Airflow connection](/docs/learn/connections) with the connection ID `slack_conn`.

<Frame>
  <img src="https://mintcdn.com/astronomer/JDQhNoS6sO6BnvP_/images/img/guides/slack_notification.png?fit=max&auto=format&n=JDQhNoS6sO6BnvP_&q=85&s=355732cc812de588b4645773ef29521e" alt="Slack notification" width="1094" height="158" data-path="images/img/guides/slack_notification.png" />
</Frame>

### Custom notifiers

If no notifier exists for your use case you can write your own! An Airflow notifier can be created by inheriting from the `BaseNotifier` class and defining the action which should be taken in case the notifier is used in the `.notify()` method.

```python wrap theme={null}
from airflow.sdk import BaseNotifier

class MyNotifier(BaseNotifier):
    """
    Basic notifier, prints the task_id, state and a message.
    """

    template_fields = ("message",)

    def __init__(self, message):
        self.message = message

    def notify(self, context):
        t_id = context["ti"].task_id
        t_state = context["ti"].state
        print(
            f"Hi from MyNotifier! {t_id} finished as: {t_state} and says {self.message}"
        )
```

To use the custom notifier in a Dag, provide its instantiation to any callback parameter. For example:

<details open>
  <summary>TaskFlow</summary>

  ```python wrap theme={null}
  from airflow.sdk import task

  def say_hello():
      return "hello"

  @task(
      on_failure_callback=MyNotifier(message="Hello failed!"),
  )
  def t1():
      return "hello"
  ```
</details>

<details>
  <summary>Traditional</summary>

  ```python wrap theme={null}
  from airflow.providers.standard.operators.python import PythonOperator

  def say_hello():
      return "hello"

  t1 = PythonOperator(
      task_id="t1",
      python_callable=say_hello,
      on_failure_callback=MyNotifier(message="Hello failed!"),
  )
  ```
</details>
