Repository: apache/airflow

Issue:
Send default email if file "html_content_template" not found
### Apache Airflow version

2.3.2 (latest released)

### What happened

I created a new email template to be sent when there are task failures. I accidentally added the path to the `[email] html_content_template` and `[email] subject_template` with a typo and no email was sent. The task's log is the following:

```
Traceback (most recent call last):
  File "/home/user/.conda/envs/airflow/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 1942, in handle_failure
    self.email_alert(error, task)
  File "/home/user/.conda/envs/airflow/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 2323, in email_alert
    subject, html_content, html_content_err = self.get_email_subject_content(exception, task=task)
  File "/home/user/.conda/envs/airflow/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 2315, in get_email_subject_content
    subject = render('subject_template', default_subject)
  File "/home/user/.conda/envs/airflow/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 2311, in render
    with open(path) as f:
FileNotFoundError: [Errno 2] No such file or directory: '/home/user/airflow/config/templates/email_failure_subject.tmpl'
```

I've looked the TaskInstance class (https://github.com/apache/airflow/blob/main/airflow/models/taskinstance.py).

I've seen that the `render` function (https://github.com/apache/airflow/blob/bcf2c418d261c6244e60e4c2d5de42b23b714bd1/airflow/models/taskinstance.py#L2271) has a `content` parameter, which is not used inside.

I guess the solution to this bug is simple: just add a `try - catch` block and return the default content in the `catch` part.

### What you think should happen instead

_No response_

### How to reproduce

_No response_

### Operating System

CentOS Linux 8

### Versions of Apache Airflow Providers

_No response_

### Deployment

Other

### Deployment details

Conda environment

### Anything else

_No response_

### Are you willing to submit PR?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)



Discussion:
You seem to know how to fix it, would you like to make a PR fixing it? Otherwise it will have to wait for someone who would likel to pick it.
Hi @potiuk 

I have never created a PR on such a complex project. However, since this seems easy to fix, it should prove useful to learn! I'll create the PR during the following week.
Just follow https://github.com/apache/airflow/blob/main/CONTRIBUTORS_QUICK_START.rst

Resolving patch:
diff --git a/airflow/models/taskinstance.py b/airflow/models/taskinstance.py
--- a/airflow/models/taskinstance.py
+++ b/airflow/models/taskinstance.py
@@ -2296,8 +2296,13 @@ def get_email_subject_content(
             def render(key: str, content: str) -> str:
                 if conf.has_option('email', key):
                     path = conf.get_mandatory_value('email', key)
-                    with open(path) as f:
-                        content = f.read()
+                    try:
+                        with open(path) as f:
+                            content = f.read()
+                    except FileNotFoundError:
+                        self.log.warning(f"Could not find email template file '{path!r}'. Using defaults...")
+                    except OSError:
+                        self.log.exception(f"Error while using email template '{path!r}'. Using defaults...")
                 return render_template_to_string(jinja_env.from_string(content), jinja_context)
 
             subject = render('subject_template', default_subject)


Test patch:
diff --git a/tests/models/test_taskinstance.py b/tests/models/test_taskinstance.py
--- a/tests/models/test_taskinstance.py
+++ b/tests/models/test_taskinstance.py
@@ -1411,6 +1411,40 @@ def test_email_alert_with_config(self, mock_send_email, dag_maker):
         assert 'template: test_email_alert_with_config' == title
         assert 'template: test_email_alert_with_config' == body
 
+    @patch('airflow.models.taskinstance.send_email')
+    def test_email_alert_with_filenotfound_config(self, mock_send_email, dag_maker):
+        with dag_maker(dag_id='test_failure_email'):
+            task = BashOperator(
+                task_id='test_email_alert_with_config',
+                bash_command='exit 1',
+                email='to',
+            )
+        ti = dag_maker.create_dagrun(execution_date=timezone.utcnow()).task_instances[0]
+        ti.task = task
+
+        # Run test when the template file is not found
+        opener = mock_open(read_data='template: {{ti.task_id}}')
+        opener.side_effect = FileNotFoundError
+        with patch('airflow.models.taskinstance.open', opener, create=True):
+            try:
+                ti.run()
+            except AirflowException:
+                pass
+
+        (email_error, title_error, body_error), _ = mock_send_email.call_args
+
+        # Rerun task without any error and no template file
+        try:
+            ti.run()
+        except AirflowException:
+            pass
+
+        (email_default, title_default, body_default), _ = mock_send_email.call_args
+
+        assert email_error == email_default == 'to'
+        assert title_default == title_error
+        assert body_default == body_error
+
     @pytest.mark.parametrize("task_id", ["test_email_alert", "test_email_alert__1"])
     @patch('airflow.models.taskinstance.send_email')
     def test_failure_mapped_taskflow(self, mock_send_email, dag_maker, session, task_id):