This is an automated email from the ASF dual-hosted git repository.

onikolas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new c97532b4d73 Add `SesEmailOperator` (#58312)
c97532b4d73 is described below

commit c97532b4d73f9404c82d9d19cbaed6bd11778c62
Author: Henry Chen <[email protected]>
AuthorDate: Thu Mar 5 06:21:46 2026 +0800

    Add `SesEmailOperator` (#58312)
    
    * Add SesEmailOperator
---
 docs/spelling_wordlist.txt                         |   1 +
 providers/amazon/docs/operators/ses.rst            |  84 ++++++++++
 providers/amazon/provider.yaml                     |   7 +-
 .../airflow/providers/amazon/aws/operators/ses.py  | 143 ++++++++++++++++
 .../airflow/providers/amazon/get_provider_info.py  |   7 +-
 .../amazon/tests/system/amazon/aws/example_ses.py  | 118 ++++++++++++++
 .../tests/unit/amazon/aws/operators/test_ses.py    | 179 +++++++++++++++++++++
 7 files changed, 537 insertions(+), 2 deletions(-)

diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 36104f629a7..ad1fe18c75b 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -1670,6 +1670,7 @@ ServicePrincipalCredentials
 ServiceResource
 ServicesClient
 SES
+ses
 sessionmaker
 setattr
 setdefault
diff --git a/providers/amazon/docs/operators/ses.rst 
b/providers/amazon/docs/operators/ses.rst
new file mode 100644
index 00000000000..fed0230c098
--- /dev/null
+++ b/providers/amazon/docs/operators/ses.rst
@@ -0,0 +1,84 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+ ..   http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+====================================
+Amazon Simple Email Service (SES)
+====================================
+
+`Amazon Simple Email Service (Amazon SES) <https://aws.amazon.com/ses/>`__ is 
a cloud email
+service provider that can integrate into any application for bulk email 
sending. Whether you
+send transactional or marketing emails, you pay only for what you use. Amazon 
SES also supports
+a variety of deployments including dedicated, shared, or owned IP addresses. 
Reports on sender
+statistics and a deliverability dashboard help businesses make every email 
count.
+
+Prerequisite Tasks
+------------------
+
+.. include:: ../_partials/prerequisite_tasks.rst
+
+Generic Parameters
+------------------
+
+.. include:: ../_partials/generic_parameters.rst
+
+Operators
+---------
+
+.. _howto/operator:SesEmailOperator:
+
+Send an email using Amazon SES
+===============================
+
+To send an email using Amazon Simple Email Service you can use
+:class:`~airflow.providers.amazon.aws.operators.ses.SesEmailOperator`.
+
+The following example shows how to send a basic email:
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ses.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ses_email_basic]
+    :end-before: [END howto_operator_ses_email_basic]
+
+You can also send emails with CC and BCC recipients:
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ses.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ses_email_cc_bcc]
+    :end-before: [END howto_operator_ses_email_cc_bcc]
+
+For more advanced use cases, you can add custom headers and set reply-to 
addresses:
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ses.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ses_email_headers]
+    :end-before: [END howto_operator_ses_email_headers]
+
+The operator also supports Jinja templating for dynamic content:
+
+.. exampleinclude:: /../../amazon/tests/system/amazon/aws/example_ses.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_ses_email_templated]
+    :end-before: [END howto_operator_ses_email_templated]
+
+Reference
+---------
+
+* `AWS boto3 library documentation for SES 
<https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html>`__
diff --git a/providers/amazon/provider.yaml b/providers/amazon/provider.yaml
index 02cb30a1fd0..dee23e4dd86 100644
--- a/providers/amazon/provider.yaml
+++ b/providers/amazon/provider.yaml
@@ -176,6 +176,8 @@ integrations:
   - integration-name: Amazon ECS
     external-doc-url: https://aws.amazon.com/ecs/
     logo: 
/docs/integration-logos/[email protected]
+    how-to-guide:
+      - /docs/apache-airflow-providers-amazon/operators/ecs.rst
     tags: [aws]
   - integration-name: Amazon Elastic Kubernetes Service (EKS)
     external-doc-url: https://aws.amazon.com/eks/
@@ -272,7 +274,7 @@ integrations:
     external-doc-url: https://aws.amazon.com/ses/
     logo: 
/docs/integration-logos/[email protected]
     how-to-guide:
-      - /docs/apache-airflow-providers-amazon/operators/ecs.rst
+      - /docs/apache-airflow-providers-amazon/operators/ses.rst
     tags: [aws]
   - integration-name: Amazon Simple Notification Service (SNS)
     external-doc-url: https://aws.amazon.com/sns/
@@ -450,6 +452,9 @@ operators:
   - integration-name: Amazon SageMaker Unified Studio
     python-modules:
       - airflow.providers.amazon.aws.operators.sagemaker_unified_studio
+  - integration-name: Amazon Simple Email Service (SES)
+    python-modules:
+      - airflow.providers.amazon.aws.operators.ses
   - integration-name: Amazon Simple Notification Service (SNS)
     python-modules:
       - airflow.providers.amazon.aws.operators.sns
diff --git a/providers/amazon/src/airflow/providers/amazon/aws/operators/ses.py 
b/providers/amazon/src/airflow/providers/amazon/aws/operators/ses.py
new file mode 100644
index 00000000000..adf57f153fa
--- /dev/null
+++ b/providers/amazon/src/airflow/providers/amazon/aws/operators/ses.py
@@ -0,0 +1,143 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Send email using Amazon Simple Email Service (SES)."""
+
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence
+from typing import TYPE_CHECKING, Any
+
+from airflow.providers.amazon.aws.hooks.ses import SesHook
+from airflow.providers.amazon.aws.operators.base_aws import AwsBaseOperator
+from airflow.providers.amazon.aws.utils.mixins import aws_template_fields
+
+if TYPE_CHECKING:
+    from airflow.sdk import Context
+
+
+class SesEmailOperator(AwsBaseOperator[SesHook]):
+    """
+    Send an email using Amazon Simple Email Service (SES).
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:SesEmailOperator`
+
+    :param mail_from: Email address to set as email's from (templated)
+    :param to: List of email addresses to set as email's to (templated)
+    :param subject: Email's subject (templated)
+    :param html_content: Content of email in HTML format (templated)
+    :param files: List of paths of files to be attached
+    :param cc: List of email addresses to set as email's CC (templated)
+    :param bcc: List of email addresses to set as email's BCC (templated)
+    :param mime_subtype: Can be used to specify the subtype of the message. 
Default = mixed
+    :param mime_charset: Email's charset. Default = UTF-8
+    :param reply_to: The email address to which replies will be sent
+    :param return_path: The email address to which message bounces and 
complaints should be sent
+    :param custom_headers: Additional headers to add to the MIME message
+    :param aws_conn_id: The Airflow connection used for AWS credentials.
+        If this is ``None`` or empty then the default boto3 behaviour is used. 
If
+        running Airflow in a distributed manner and aws_conn_id is None or
+        empty, then default boto3 configuration would be used (and must be
+        maintained on each worker node).
+    :param region_name: AWS region_name. If not specified then the default 
boto3 behaviour is used.
+    :param verify: Whether or not to verify SSL certificates. See:
+        
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html
+    :param botocore_config: Configuration dictionary (key-values) for botocore 
client. See:
+        
https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html
+    """
+
+    aws_hook_class = SesHook
+    template_fields: Sequence[str] = aws_template_fields(
+        "mail_from",
+        "to",
+        "subject",
+        "html_content",
+        "cc",
+        "bcc",
+        "mime_subtype",
+        "mime_charset",
+        "reply_to",
+        "return_path",
+        "custom_headers",
+    )
+    template_fields_renderers = {
+        "custom_headers": "json",
+    }
+
+    def __init__(
+        self,
+        *,
+        mail_from: str,
+        to: str | Iterable[str],
+        subject: str,
+        html_content: str,
+        files: list[str] | None = None,
+        cc: str | Iterable[str] | None = None,
+        bcc: str | Iterable[str] | None = None,
+        mime_subtype: str = "mixed",
+        mime_charset: str = "utf-8",
+        reply_to: str | None = None,
+        return_path: str | None = None,
+        custom_headers: dict[str, Any] | None = None,
+        **kwargs,
+    ):
+        super().__init__(**kwargs)
+        self.mail_from = mail_from
+        self.to = to
+        self.subject = subject
+        self.html_content = html_content
+        self.files = files
+        self.cc = cc
+        self.bcc = bcc
+        self.mime_subtype = mime_subtype
+        self.mime_charset = mime_charset
+        self.reply_to = reply_to
+        self.return_path = return_path
+        self.custom_headers = custom_headers
+
+    def execute(self, context: Context) -> dict:
+        """
+        Send email using Amazon SES.
+
+        :param context: The task context
+        :return: Response from Amazon SES service with unique message 
identifier
+        """
+        self.log.info(
+            "Sending email via Amazon SES from %s to %s with subject: %s",
+            self.mail_from,
+            self.to,
+            self.subject,
+        )
+
+        response = self.hook.send_email(
+            mail_from=self.mail_from,
+            to=self.to,
+            subject=self.subject,
+            html_content=self.html_content,
+            files=self.files,
+            cc=self.cc,
+            bcc=self.bcc,
+            mime_subtype=self.mime_subtype,
+            mime_charset=self.mime_charset,
+            reply_to=self.reply_to,
+            return_path=self.return_path,
+            custom_headers=self.custom_headers,
+        )
+
+        self.log.info("Email sent successfully. Message ID: %s", 
response.get("MessageId"))
+        return response
diff --git a/providers/amazon/src/airflow/providers/amazon/get_provider_info.py 
b/providers/amazon/src/airflow/providers/amazon/get_provider_info.py
index fbdd7ef45de..4c52781f42e 100644
--- a/providers/amazon/src/airflow/providers/amazon/get_provider_info.py
+++ b/providers/amazon/src/airflow/providers/amazon/get_provider_info.py
@@ -100,6 +100,7 @@ def get_provider_info():
                 "integration-name": "Amazon ECS",
                 "external-doc-url": "https://aws.amazon.com/ecs/";,
                 "logo": 
"/docs/integration-logos/[email protected]",
+                "how-to-guide": 
["/docs/apache-airflow-providers-amazon/operators/ecs.rst"],
                 "tags": ["aws"],
             },
             {
@@ -222,7 +223,7 @@ def get_provider_info():
                 "integration-name": "Amazon Simple Email Service (SES)",
                 "external-doc-url": "https://aws.amazon.com/ses/";,
                 "logo": 
"/docs/integration-logos/[email protected]",
-                "how-to-guide": 
["/docs/apache-airflow-providers-amazon/operators/ecs.rst"],
+                "how-to-guide": 
["/docs/apache-airflow-providers-amazon/operators/ses.rst"],
                 "tags": ["aws"],
             },
             {
@@ -447,6 +448,10 @@ def get_provider_info():
                 "integration-name": "Amazon SageMaker Unified Studio",
                 "python-modules": 
["airflow.providers.amazon.aws.operators.sagemaker_unified_studio"],
             },
+            {
+                "integration-name": "Amazon Simple Email Service (SES)",
+                "python-modules": 
["airflow.providers.amazon.aws.operators.ses"],
+            },
             {
                 "integration-name": "Amazon Simple Notification Service (SNS)",
                 "python-modules": 
["airflow.providers.amazon.aws.operators.sns"],
diff --git a/providers/amazon/tests/system/amazon/aws/example_ses.py 
b/providers/amazon/tests/system/amazon/aws/example_ses.py
new file mode 100644
index 00000000000..df1d0d92ee1
--- /dev/null
+++ b/providers/amazon/tests/system/amazon/aws/example_ses.py
@@ -0,0 +1,118 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from datetime import datetime
+
+from airflow.providers.amazon.aws.operators.ses import SesEmailOperator
+
+from tests_common.test_utils.version_compat import AIRFLOW_V_3_0_PLUS
+
+if AIRFLOW_V_3_0_PLUS:
+    from airflow.sdk import DAG, chain
+else:
+    # Airflow 2 path
+    from airflow.models.baseoperator import chain  # type: 
ignore[attr-defined,no-redef]
+    from airflow.models.dag import DAG  # type: 
ignore[attr-defined,no-redef,assignment]
+
+from system.amazon.aws.utils import SystemTestContextBuilder
+
+SES_VERIFIED_EMAIL_KEY = "SES_VERIFIED_EMAIL"
+sys_test_context_task = 
SystemTestContextBuilder().add_variable(SES_VERIFIED_EMAIL_KEY).build()
+
+with DAG(
+    dag_id="example_ses",
+    start_date=datetime(2021, 1, 1),
+    schedule="@once",
+    catchup=False,
+    tags=["example"],
+) as dag:
+    test_context = sys_test_context_task()
+    verified_email = test_context[SES_VERIFIED_EMAIL_KEY]
+
+    # [START howto_operator_ses_email_basic]
+    # Basic email sending
+    # Note: In SES sandbox mode, both sender and recipient must be verified.
+    send_basic_email = SesEmailOperator(
+        task_id="send_basic_email",
+        mail_from=verified_email,
+        to=[verified_email],
+        subject="Test Email from Airflow",
+        html_content="<h1>Hello</h1><p>This is a test email sent via Amazon 
SES.</p>",
+        aws_conn_id="aws_default",
+    )
+    # [END howto_operator_ses_email_basic]
+
+    # [START howto_operator_ses_email_cc_bcc]
+    # Email with CC and BCC
+    send_email_with_cc_bcc = SesEmailOperator(
+        task_id="send_email_with_cc_bcc",
+        mail_from=verified_email,
+        to=[verified_email],
+        cc=[verified_email],
+        bcc=[verified_email],
+        subject="Test Email with CC and BCC",
+        html_content="<h1>Hello</h1><p>This email has CC and BCC 
recipients.</p>",
+        aws_conn_id="aws_default",
+    )
+    # [END howto_operator_ses_email_cc_bcc]
+
+    # [START howto_operator_ses_email_headers]
+    # Email with custom headers and reply-to
+    send_email_with_headers = SesEmailOperator(
+        task_id="send_email_with_headers",
+        mail_from=verified_email,
+        to=[verified_email],
+        subject="Test Email with Custom Headers",
+        html_content="<h1>Hello</h1><p>This email has custom headers.</p>",
+        reply_to=verified_email,
+        return_path=verified_email,
+        custom_headers={"X-Custom-Header": "CustomValue"},
+        aws_conn_id="aws_default",
+    )
+    # [END howto_operator_ses_email_headers]
+
+    # [START howto_operator_ses_email_templated]
+    # Email with template variables
+    send_templated_email = SesEmailOperator(
+        task_id="send_templated_email",
+        mail_from=verified_email,
+        to=[verified_email],
+        subject="DAG Run: {{ dag.dag_id }} - {{ ds }}",
+        html_content="""
+        <h1>DAG Run Report</h1>
+        <p>DAG ID: {{ dag.dag_id }}</p>
+        <p>Execution Date: {{ ds }}</p>
+        <p>Run ID: {{ run_id }}</p>
+        """,
+        aws_conn_id="aws_default",
+    )
+    # [END howto_operator_ses_email_templated]
+
+    chain(
+        test_context,
+        send_basic_email,
+        send_email_with_cc_bcc,
+        send_email_with_headers,
+        send_templated_email,
+    )
+
+
+from tests_common.test_utils.system_tests import get_test_run  # noqa: E402
+
+# Needed to run the example DAG with pytest (see: 
tests/system/README.md#run_via_pytest)
+test_run = get_test_run(dag)
diff --git a/providers/amazon/tests/unit/amazon/aws/operators/test_ses.py 
b/providers/amazon/tests/unit/amazon/aws/operators/test_ses.py
new file mode 100644
index 00000000000..12944078aac
--- /dev/null
+++ b/providers/amazon/tests/unit/amazon/aws/operators/test_ses.py
@@ -0,0 +1,179 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+from airflow.providers.amazon.aws.operators.ses import SesEmailOperator
+
+from unit.amazon.aws.utils.test_template_fields import validate_template_fields
+
+TASK_ID = "ses_email_job"
+AWS_CONN_ID = "custom_aws_conn"
+MAIL_FROM = "[email protected]"
+TO = ["[email protected]"]
+SUBJECT = "Test Subject"
+HTML_CONTENT = "<h1>Test Email</h1><p>This is a test.</p>"
+
+
+class TestSesEmailOperator:
+    @pytest.fixture(autouse=True)
+    def _setup_test_cases(self):
+        self.default_op_kwargs = {
+            "task_id": TASK_ID,
+            "mail_from": MAIL_FROM,
+            "to": TO,
+            "subject": SUBJECT,
+            "html_content": HTML_CONTENT,
+        }
+
+    def test_init(self):
+        """Test operator initialization with default parameters."""
+        op = SesEmailOperator(**self.default_op_kwargs)
+        assert op.hook.aws_conn_id == "aws_default"
+        assert op.hook._region_name is None
+        assert op.hook._verify is None
+        assert op.hook._config is None
+        assert op.mail_from == MAIL_FROM
+        assert op.to == TO
+        assert op.subject == SUBJECT
+        assert op.html_content == HTML_CONTENT
+
+    def test_init_with_custom_params(self):
+        """Test operator initialization with custom AWS parameters."""
+        op = SesEmailOperator(
+            **self.default_op_kwargs,
+            aws_conn_id=AWS_CONN_ID,
+            region_name="us-west-1",
+            verify="/spam/egg.pem",
+            botocore_config={"read_timeout": 42},
+            cc=["[email protected]"],
+            bcc=["[email protected]"],
+            reply_to="[email protected]",
+            return_path="[email protected]",
+        )
+        assert op.hook.aws_conn_id == AWS_CONN_ID
+        assert op.hook._region_name == "us-west-1"
+        assert op.hook._verify == "/spam/egg.pem"
+        assert op.hook._config is not None
+        assert op.hook._config.read_timeout == 42
+        assert op.cc == ["[email protected]"]
+        assert op.bcc == ["[email protected]"]
+        assert op.reply_to == "[email protected]"
+        assert op.return_path == "[email protected]"
+
+    @mock.patch.object(SesEmailOperator, "hook")
+    def test_execute_basic(self, mocked_hook):
+        """Test basic email sending execution."""
+        hook_response = {"MessageId": "test-message-id-123"}
+        mocked_hook.send_email.return_value = hook_response
+
+        op = SesEmailOperator(**self.default_op_kwargs)
+        result = op.execute({})
+
+        assert result == hook_response
+        mocked_hook.send_email.assert_called_once_with(
+            mail_from=MAIL_FROM,
+            to=TO,
+            subject=SUBJECT,
+            html_content=HTML_CONTENT,
+            files=None,
+            cc=None,
+            bcc=None,
+            mime_subtype="mixed",
+            mime_charset="utf-8",
+            reply_to=None,
+            return_path=None,
+            custom_headers=None,
+        )
+
+    @mock.patch.object(SesEmailOperator, "hook")
+    def test_execute_with_all_params(self, mocked_hook):
+        """Test email sending with all optional parameters."""
+        hook_response = {"MessageId": "test-message-id-456"}
+        mocked_hook.send_email.return_value = hook_response
+
+        custom_headers = {"X-Custom-Header": "CustomValue"}
+        files = ["/path/to/file1.txt", "/path/to/file2.pdf"]
+
+        op = SesEmailOperator(
+            **self.default_op_kwargs,
+            cc=["[email protected]"],
+            bcc=["[email protected]"],
+            files=files,
+            mime_subtype="alternative",
+            mime_charset="iso-8859-1",
+            reply_to="[email protected]",
+            return_path="[email protected]",
+            custom_headers=custom_headers,
+        )
+        result = op.execute({})
+
+        assert result == hook_response
+        mocked_hook.send_email.assert_called_once_with(
+            mail_from=MAIL_FROM,
+            to=TO,
+            subject=SUBJECT,
+            html_content=HTML_CONTENT,
+            files=files,
+            cc=["[email protected]"],
+            bcc=["[email protected]"],
+            mime_subtype="alternative",
+            mime_charset="iso-8859-1",
+            reply_to="[email protected]",
+            return_path="[email protected]",
+            custom_headers=custom_headers,
+        )
+
+    @mock.patch.object(SesEmailOperator, "hook")
+    def test_execute_with_string_to(self, mocked_hook):
+        """Test email sending with 'to' as a string instead of list."""
+        hook_response = {"MessageId": "test-message-id-789"}
+        mocked_hook.send_email.return_value = hook_response
+
+        op = SesEmailOperator(
+            task_id=TASK_ID,
+            mail_from=MAIL_FROM,
+            to="[email protected]",
+            subject=SUBJECT,
+            html_content=HTML_CONTENT,
+        )
+        result = op.execute({})
+
+        assert result == hook_response
+        mocked_hook.send_email.assert_called_once_with(
+            mail_from=MAIL_FROM,
+            to="[email protected]",
+            subject=SUBJECT,
+            html_content=HTML_CONTENT,
+            files=None,
+            cc=None,
+            bcc=None,
+            mime_subtype="mixed",
+            mime_charset="utf-8",
+            reply_to=None,
+            return_path=None,
+            custom_headers=None,
+        )
+
+    def test_template_fields(self):
+        """Test that template fields are properly configured."""
+        operator = SesEmailOperator(**self.default_op_kwargs)
+        validate_template_fields(operator)

Reply via email to