Repository: pagopa/pagopa-notifications-service
Pull request #101: [CHK-3990] feat: add aws-ses mock 
Author: login: scaminati-bv
Labels: name: size/small
name: minor
name: chart-minor
Created: 2025-04-01T09:44:25Z | Merged: 2025-04-09T12:59:51Z

<!--- Please always add a PR description as if nobody knows anything about the context these changes come from. -->
<!--- Even if we are all from our internal team, we may not be on the same page. -->
<!--- Write this PR as you were contributing to a public OSS project, where nobody knows you and you have to earn their trust. -->
<!--- This will improve our projects in the long run! Thanks. -->

#### List of Changes
<!--- Describe your changes in detail -->
- add `AWS_SES_ENDPOINT` config to target mock
- if `AWS_SES_ENDPOINT` is not configured `endpoint` property is not added to client in order to use default aws value.
- add `aws-ses-mock.yaml` configuration for cluster installation

#### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
The goal of this PR is to add configuration to support `aws-ses-mock` to simulate email sending.

#### How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->
Unit test

#### Screenshots (if appropriate):

#### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

#### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.



Diff:
diff --git a/.env.example b/.env.example
index 4b544078..22648e19 100644
--- a/.env.example
+++ b/.env.example
@@ -12,6 +12,7 @@ MAX_RETRY_ATTEMPTS=3
 AI_SAMPLING_PERCENTAGE=30
 AI_ENABLED=false
 AWS_SES_ACCESS_KEY_ID=test-access-key
+AWS_SES_ENDPOINT=http://localhost:8005
 AWS_SES_REGION=test-region
 AWS_SES_SECRET_ACCESS_KEY=test-secret-key
 PAGOPA_MAIL_LOGO_URI=https://dev.checkout.pagopa.it/assets/logos/logo_pagopa.png
diff --git a/aws-ses-mock/README.md b/aws-ses-mock/README.md
new file mode 100644
index 00000000..5075c136
--- /dev/null
+++ b/aws-ses-mock/README.md
@@ -0,0 +1,31 @@
+# aws-ses-mock configuration
+
+The `notification-service` has `aws-ses` as an external dependency in order to send emails. In some cases (e.g. soak-test) it may be useful to have a mock for making sure that a large number of requests not impacting costs or risk being detected as spam.
+
+To do this, the [aws-ses-v2-local](https://github.com/domdomegg/aws-ses-v2-local) project is chosen to be used, that allow us to simulate emails sending without using aws external dependency.
+
+## Setup
+
+In order to use the mock in one of the environments (dev/uat), it is necessary to start a pod on the cluster with the `aws-ses-mock.yaml` configuration.
+
+### Starting pod on the cluster
+
+```
+$ kubectl apply -n ecommerce -f aws-ses-mock.yaml
+```
+
+### Modify notification-service pointing.
+
+In order to properly point to the mock, it is necessary to modify the `ConfigMap` of the `notification-service` pod by updating the `AWS_SES_ENDPOINT` variable as follows:
+
+```
+$ AWS_SES_ENDPOINT: http://aws-ses-mock.ecommerce.svc.cluster.local:8005
+```
+
+## Verify mock working
+
+Once `notification-service` pod has restarted, the configuration is ready. Now it is possible to open the webapp made available by `aws-ses-v2-local` to view all the mails that the service has attempted to send, ad the following url:
+
+```
+$ http://<ip-mock-address>:8005
+```
\ No newline at end of file
diff --git a/aws-ses-mock/aws-ses-mock.yaml b/aws-ses-mock/aws-ses-mock.yaml
new file mode 100644
index 00000000..d79bcd9b
--- /dev/null
+++ b/aws-ses-mock/aws-ses-mock.yaml
@@ -0,0 +1,36 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  labels:
+    run: aws-ses-mock
+  name: aws-ses-mock
+  namespace: ecommerce
+spec:
+  containers:
+  - image: dasprid/aws-ses-v2-local:2.3.1
+    name: aws-ses-mock
+    resources:
+      limits:
+        cpu: "0.5"
+        memory: "256Mi"
+      requests:
+        cpu: "0.5"
+        memory: "256Mi"
+  dnsPolicy: ClusterFirst
+  restartPolicy: Always
+---
+#http://aws-ses-mock.ecommerce.svc.cluster.local:8005
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    run: aws-ses-mock
+  name: aws-ses-mock
+  namespace: ecommerce
+spec:
+  ports:
+  - port: 8005
+    protocol: TCP
+    targetPort: 8005
+  selector:
+    run: aws-ses-mock
diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml
index b8914bbe..e99bd097 100644
--- a/helm/values-dev.yaml
+++ b/helm/values-dev.yaml
@@ -92,6 +92,7 @@ microservice-chart:
       maxUnavailable: 0
   envConfig:
     NOTIFICATIONS_EMAIL_ENABLED: "true"
+    AWS_SES_ENDPOINT: ""
     AWS_SES_REGION: "eu-south-1"
     CLIENT_ECOMMERCE_TEST: "{\"TEMPLATE_IDS\":[\"poc-1\", \"success\", \"ko\"]}"
     CLIENT_PAYMENT_MANAGER: "{\"TEMPLATE_IDS\":[\"poc-1\", \"success\", \"ko\"]}"
diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml
index db28ee83..66c720fe 100644
--- a/helm/values-prod.yaml
+++ b/helm/values-prod.yaml
@@ -88,6 +88,7 @@ microservice-chart:
       maxUnavailable: 0
   envConfig:
     NOTIFICATIONS_EMAIL_ENABLED: "true"
+    AWS_SES_ENDPOINT: ""
     AWS_SES_REGION: "eu-south-1"
     CLIENT_ECOMMERCE_TEST: "{\"TEMPLATE_IDS\":[\"poc-1\", \"success\", \"ko\"]}"
     CLIENT_PAYMENT_MANAGER: "{\"TEMPLATE_IDS\":[\"poc-1\", \"success\", \"ko\"]}"
diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml
index 902d47e4..9e463c1c 100644
--- a/helm/values-uat.yaml
+++ b/helm/values-uat.yaml
@@ -94,6 +94,7 @@ microservice-chart:
       maxUnavailable: 0
   envConfig:
     NOTIFICATIONS_EMAIL_ENABLED: "true"
+    AWS_SES_ENDPOINT: ""
     AWS_SES_REGION: "eu-south-1"
     CLIENT_ECOMMERCE_TEST: "{\"TEMPLATE_IDS\":[\"poc-1\", \"success\", \"ko\"]}"
     CLIENT_PAYMENT_MANAGER: "{\"TEMPLATE_IDS\":[\"poc-1\", \"success\", \"ko\"]}"
diff --git a/jest.setup.js b/jest.setup.js
index 20c5854b..87253822 100644
--- a/jest.setup.js
+++ b/jest.setup.js
@@ -12,6 +12,7 @@ process.env.MAX_RETRY_ATTEMPTS="3"
 process.env.AI_SAMPLING_PERCENTAGE="30"
 process.env.AI_ENABLED=false
 process.env.AWS_SES_ACCESS_KEY_ID="test-access-key"
+process.env.AWS_SES_ENDPOINT="http://localhost:8005"
 process.env.AWS_SES_REGION="test-region"
 process.env.AWS_SES_SECRET_ACCESS_KEY="test-secret-key"
 process.env.PAGOPA_MAIL_LOGO_URI="https://dev.checkout.pagopa.it/assets/logos/pagopa-logo.svg"
diff --git a/src/__tests__/RetryQueueListener.test.ts b/src/__tests__/RetryQueueListener.test.ts
index 4bf50f87..63c3dd5b 100644
--- a/src/__tests__/RetryQueueListener.test.ts
+++ b/src/__tests__/RetryQueueListener.test.ts
@@ -59,7 +59,8 @@ describe("retry queue", () => {
       accessKeyId: config.AWS_SES_ACCESS_KEY_ID,
       secretAccessKey: config.AWS_SES_SECRET_ACCESS_KEY
     },
-    region: config.AWS_SES_REGION
+    region: config.AWS_SES_REGION,
+    ...(config.AWS_SES_ENDPOINT !== "" && { endpoint: config.AWS_SES_ENDPOINT })
   };
 
   const sentMessageMock = (a: number): SentMessageInfo => { return {
diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts
index 444ce5ca..23aa928d 100644
--- a/src/__tests__/config.test.ts
+++ b/src/__tests__/config.test.ts
@@ -15,5 +15,10 @@ describe("config", () => {
       const spyGetConfig = jest.spyOn(config,'getConfig').mockImplementation(() => {throw new Error("")});
       const confOrThrow = config.getConfigOrThrow();
     });
+
+    it("check getConfig with AWS_SES_ENDPOINT", () => {
+      const conf = config.getConfigOrThrow();
+      expect(conf.AWS_SES_ENDPOINT).toEqual("http://localhost:8005");
+    });
     
   });
\ No newline at end of file
diff --git a/src/app.ts b/src/app.ts
index abe0f672..4e418013 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -37,7 +37,8 @@ export const startApp = async (
       accessKeyId: config.AWS_SES_ACCESS_KEY_ID,
       secretAccessKey: config.AWS_SES_SECRET_ACCESS_KEY
     },
-    region: config.AWS_SES_REGION
+    region: config.AWS_SES_REGION,
+    ...(config.AWS_SES_ENDPOINT !== "" && { endpoint: config.AWS_SES_ENDPOINT })
   };
 
   const mailTrasporter: Transporter = nodemailer.createTransport({
diff --git a/src/controllers/HealthControllers.ts b/src/controllers/HealthControllers.ts
index 5cf92672..b48f4b40 100644
--- a/src/controllers/HealthControllers.ts
+++ b/src/controllers/HealthControllers.ts
@@ -32,7 +32,8 @@ export const checkSESTask = (
       secretAccessKey: config.AWS_SES_SECRET_ACCESS_KEY
     },
     region: config.AWS_SES_REGION,
-    tls: true
+    tls: true,
+    ...(config.AWS_SES_ENDPOINT !== "" && { endpoint: config.AWS_SES_ENDPOINT })
   });
   const command = new GetSendQuotaCommand({});
 
diff --git a/src/controllers/__tests__/EmailsControllers.test.ts b/src/controllers/__tests__/EmailsControllers.test.ts
index d927a054..2c55a85d 100644
--- a/src/controllers/__tests__/EmailsControllers.test.ts
+++ b/src/controllers/__tests__/EmailsControllers.test.ts
@@ -28,7 +28,8 @@ const SES_CONFIG = {
     accessKeyId: config.AWS_SES_ACCESS_KEY_ID,
     secretAccessKey: config.AWS_SES_SECRET_ACCESS_KEY
   },
-  region: config.AWS_SES_REGION
+  region: config.AWS_SES_REGION,
+  ...(config.AWS_SES_ENDPOINT !== "" && { endpoint: config.AWS_SES_ENDPOINT })
 };
 
 const getMailTransporter = () =>  createTransport({
diff --git a/src/controllers/__tests__/HealthControllers.test.ts b/src/controllers/__tests__/HealthControllers.test.ts
index c51e95a3..02d769c9 100644
--- a/src/controllers/__tests__/HealthControllers.test.ts
+++ b/src/controllers/__tests__/HealthControllers.test.ts
@@ -24,6 +24,7 @@ describe("health check", () => {
     AI_ENABLED: false,
     AI_SAMPLING_PERCENTAGE: 0,
     AWS_SES_ACCESS_KEY_ID: "aws_access_key",
+    AWS_SES_ENDPOINT: "",
     AWS_SES_REGION: "aws_region",
     AWS_SES_SECRET_ACCESS_KEY: "aws_secret",
     CLIENT_ECOMMERCE: {TEMPLATE_IDS: ["fake template"]} as NotificationsServiceClientConfig,
diff --git a/src/util/config.ts b/src/util/config.ts
index 0394d455..663136f8 100644
--- a/src/util/config.ts
+++ b/src/util/config.ts
@@ -38,6 +38,7 @@ export const IConfig = t.interface({
   AI_ENABLED: t.boolean,
   AI_SAMPLING_PERCENTAGE: t.number,
   AWS_SES_ACCESS_KEY_ID: t.string,
+  AWS_SES_ENDPOINT: t.string,
   AWS_SES_REGION: t.string,
   AWS_SES_SECRET_ACCESS_KEY: t.string,
   CLIENT_ECOMMERCE: NotificationsServiceClientConfig,
@@ -67,6 +68,9 @@ const envConfig = {
   AI_SAMPLING_PERCENTAGE: process.env.AI_SAMPLING_PERCENTAGE
     ? parseInt(process.env.AI_SAMPLING_PERCENTAGE, 10)
     : 30,
+  AWS_SES_ENDPOINT: process.env.AWS_SES_ENDPOINT
+    ? process.env.AWS_SES_ENDPOINT
+    : "",
   CLIENT_ECOMMERCE: JSON.parse(process.env.CLIENT_ECOMMERCE || "{}"),
   CLIENT_ECOMMERCE_TEST: JSON.parse(process.env.CLIENT_ECOMMERCE_TEST || "{}"),
   CLIENT_PAYMENT_MANAGER: JSON.parse(


Reviews:
user: login: pietro-tota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T03:46:17Z
user: login: scaminati-bv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T07:46:05Z
user: login: sfarralorenzo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T12:52:43Z
user: login: EmanueleBVtech
body: 🚀 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T13:16:06Z
user: login: pietro-tota
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T14:10:32Z
user: login: infantesimone
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:03:06Z

Review comments:
diff_hunk: @@ -0,0 +1,30 @@
+# Configurazione aws-ses-mock
path: aws-ses-mock/README.md
user: login: pietro-tota
body: please translate it to english
created_at: 2025-04-02T03:46:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+# Configurazione aws-ses-mock
path: aws-ses-mock/README.md
user: login: scaminati-bv
body: Done here d1cfbd7c94423cab7f139ad5090e2a75dc53f353
created_at: 2025-04-02T07:46:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov-commenter
created_at: 2025-04-02T07:50:42Z
body: ## [Codecov](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/pull/101?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa) Report
Attention: Patch coverage is `60.00000%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 87.26%. Comparing base [(`55395bb`)](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/commit/55395bbe422fb95532e4709f1855e4624fa0ff91?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa) to head [(`cc47ee9`)](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/commit/cc47ee973ae1a99595dae946bd12ee04c8f8bacf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa).

| [Files with missing lines](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/pull/101?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa) | Patch % | Lines |
|---|---|---|
| [src/util/config.ts](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/pull/101?src=pr&el=tree&filepath=src%2Futil%2Fconfig.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa#diff-c3JjL3V0aWwvY29uZmlnLnRz) | 33.33% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/pull/101?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #101      +/-   ##
==========================================
- Coverage   87.70%   87.26%   -0.45%     
==========================================
  Files          10       10              
  Lines         309      314       +5     
  Branches       39       44       +5     
==========================================
+ Hits          271      274       +3     
- Misses         31       32       +1     
- Partials        7        8       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pagopa/pagopa-notifications-service/pull/101?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pagopa).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE