Repository: localstack/localstack

Issue:
feature request: implement DELETE /_localstack/ses endpoint
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Feature description

Provide an URL where I can delete stored SES emails.
My motivation is to write integration tests much easier, if I could delete the stored emails from the localstack before each test case. 

I use the docker image to run localstack. Mount the `/var/lib/localstack` folder to the host and delete the files from the `tmp/state/ses` is cumbersome because of the file permission issue of docker.

I think the simplest way to achieve the empty SES emails state is to implement a new endpoint in a same way as to the GET /_localstack/ses has been implemented:

**path:** /_localstack/ses
**method:** DELETE

### 🧑‍💻 Implementation

I am not a python developer, but I think one solution is to extend the [SesServiceApiResource](https://github.com/localstack/localstack/blob/590de96b3dd65e349709300b668e70f1f9664e99/localstack/services/ses/provider.py#L121) with an `on_delete` method.

Open question for me is either delete the `/var/lib/localstack/tmp/state/ses` folder or just delete the files of the `ses` folder.
The folder does not exist when the container starts. It is created when the first email received and saved by the service.
I think both solutions have the same performance impact, so the real question is what is preferred by the maintainers.

As I mentioned I am not familiar with python ecosystem but I am happy to help implementing the feature if you accept the request.

### Anything else?

_No response_


Discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [contributing guide](https://docs.localstack.cloud/contributing/).
Hi Guys,
I'm new here and wanted to contribute to the project. Can anyone please assign this issue to me? 
Hello, just adding myself as a subscriber and happy to help out in testing if something comes of this. I have a similar case and currently waste a bit of CPU and we restart localstack to "clear" the emails when we re-run our tests but being able to just clear them explicitly in the test flow via an API call would be great. 
Currently, I am very busy but I am planning to implement it at the beginning of February unless anyone does.
We are also looking for this functionality. It would also be very helpful in integration testing to have the ability to delete the message by id.

path: /_localstack/ses/ucppwjopwzbhfbxa-avxbjepo-qpxj-qfcx-actv-ntdeeqgfjikp-fajleq
method: DELETE
I am new to this I would love to help great advice or insight into this problem would be helpful.

Hi @shadowmaxxl what sort of information would you be looking for?

Resolving patch:
diff --git a/localstack/services/ses/provider.py b/localstack/services/ses/provider.py
--- a/localstack/services/ses/provider.py
+++ b/localstack/services/ses/provider.py
@@ -51,7 +51,7 @@
     VerificationStatus,
 )
 from localstack.constants import TEST_AWS_SECRET_ACCESS_KEY
-from localstack.http import Resource
+from localstack.http import Resource, Response
 from localstack.services.internal import DeprecatedResource, get_internal_apis
 from localstack.services.moto import call_moto
 from localstack.services.plugins import ServiceLifecycleHook
@@ -125,21 +125,32 @@ class SesServiceApiResource:
     This is registered as a LocalStack internal HTTP resource.
 
     This endpoint accepts:
-    - GET param `email`: filter for `source` field in SES message
+    - GET param `id`: filter for `id` field in SES message
+    - GET param `email`: filter for `source` field in SES message, when `id` filter is specified then filters on both
     """
 
     def on_get(self, request):
+        filter_id = request.args.get("id")
         filter_source = request.args.get("email")
         messages = []
 
         for msg in EMAILS.values():
-            if filter_source in (msg.get("Source"), None, ""):
-                messages.append(msg)
+            if filter_id in (msg.get("Id"), None, ""):
+                if filter_source in (msg.get("Source"), None, ""):
+                    messages.append(msg)
 
         return {
             "messages": messages,
         }
 
+    def on_delete(self, request):
+        filter_id = request.args.get("id")
+        if filter_id is not None:
+            del EMAILS[filter_id]
+        else:
+            EMAILS.clear()
+        return Response(status=204)
+
 
 def register_ses_api_resource():
     """Register the email retrospection endpoint as an internal LocalStack endpoint."""
@@ -156,7 +167,7 @@ def register_ses_api_resource():
                     ses_service_api_resource,
                     previous_path="/_localstack/ses",
                     deprecation_version="1.4.0",
-                    new_path="/_aws/ses/",
+                    new_path="/_aws/ses",
                 ),
             )
         )


Test patch:
diff --git a/tests/integration/test_ses.py b/tests/integration/test_ses.py
--- a/tests/integration/test_ses.py
+++ b/tests/integration/test_ses.py
@@ -182,8 +182,10 @@ def _read_message_from_filesystem(message_id: str) -> dict:
 
         # Ensure all sent messages can be retrieved using the API endpoint
         emails_url = config.get_edge_url() + EMAILS_ENDPOINT
-        api_contents = requests.get(emails_url).json()
-        api_contents = {msg["Id"]: msg for msg in api_contents["messages"]}
+        api_contents = []
+        api_contents.extend(requests.get(emails_url + f"?id={message1_id}").json()["messages"])
+        api_contents.extend(requests.get(emails_url + f"?id={message2_id}").json()["messages"])
+        api_contents = {msg["Id"]: msg for msg in api_contents}
         assert len(api_contents) >= 1
         assert message1_id in api_contents
         assert message2_id in api_contents
@@ -196,6 +198,11 @@ def _read_message_from_filesystem(message_id: str) -> dict:
         emails_url = config.get_edge_url() + EMAILS_ENDPOINT + f"?email={email}"
         assert len(requests.get(emails_url).json()["messages"]) == 2
 
+        emails_url = config.get_edge_url() + EMAILS_ENDPOINT
+        assert requests.delete(emails_url + f"?id={message1_id}").status_code == 204
+        assert requests.delete(emails_url + f"?id={message2_id}").status_code == 204
+        assert requests.get(emails_url).json() == {"messages": []}
+
     def test_send_templated_email_can_retrospect(self, ses_client, create_template):
         # Test that sent emails can be retrospected through saved file and API access
         data_dir = config.dirs.data or config.dirs.tmp
@@ -229,6 +236,9 @@ def test_send_templated_email_can_retrospect(self, ses_client, create_template):
         assert message_id in api_contents
         assert api_contents[message_id] == contents
 
+        assert requests.delete("http://localhost:4566/_aws/ses").status_code == 204
+        assert requests.get("http://localhost:4566/_aws/ses").json() == {"messages": []}
+
     def test_clone_receipt_rule_set(self, ses_client):
         # Test that rule set is cloned properly