Email ACL Template
Purpose and Scope
This project acts as an Email ACL (Access Control Layer). Its main objective is to receive email sending requests through Google Cloud Pub/Sub messages and process them for delivery using Amazon Simple Email Service (SES).
Problem It Solves: It provides a centralized, decoupled interface for sending emails, allowing other services to delegate this functionality without directly integrating the Amazon SES SDK or managing AWS credentials in a distributed manner.
General Scope:
- Reception of messages in JSON format from a Google Cloud Pub/Sub subscription.
- Parsing of the message to extract recipients, subject, email body (HTML supported), and attachments.
- Dynamic construction of the email sender based on requesting application metadata and the environment.
- Conversion and handling of attachments (Base64-encoded).
- Integration with Amazon SES for actual email delivery.
- Environment-specific configuration handling (local, dev, intg, prod).
Out of Scope:
- Does not expose a public REST API for sending emails (interaction is via Pub/Sub).
- Does not manage complex email templates (the email body is received directly).
- Does not implement advanced retry logic beyond what Pub/Sub may offer.
Architecture
The project follows a hexagonal architecture (ports and adapters), seeking good decoupling between business logic (domain) and infrastructure components (integrations with external services).
Main Flow:
- A message is published to a Google Cloud Pub/Sub topic by a client service.
- The
PubSubReceiver(input adapter) in this project is subscribed to that topic (through a specific subscription) and receives the message. - The message (JSON payload) is processed by
PubSubService(application/domain logic). PubSubServicetransforms the payload into anEmailDetailsobject, determines the sender (fromemail), and prepares the details for sending.- If there are attachments, they are decoded from Base64 and prepared.
EmailAmazonServiceandEmailAmazonRawService(output adapters) use the Amazon SES client to build and send the email.- Credentials for AWS SES (
AWS_ACCESS_KEY,AWS_SECRET_KEY) are managed securely, typically through secrets in GCP Secret Manager injected as environment variables in the GKE deployment.
Project Structure (simplified):
com.latam.ltmds.email.acl
├── application
│ └── request/ # DTOs such as EmailDetails, Attachment
├── domain
│ ├── service/ # Main business logic
│ │ ├── email/ # Email-related services (SES interface)
│ │ └── pubsub/ # Pub/Sub-related services (message processing)
│ └── exception/ # Custom exceptions
└── infrastructure
├── config/ # Spring configuration, Beans
├── pubsub/ # Adapter to receive Pub/Sub messages
└── ses/ # Adapter to send emails via Amazon SES
Technologies and Dependencies
- Language: Java 21
- Framework: Spring Boot 3.4.5
- Build Tool: Gradle 8.12.1
- Asynchronous Messaging:
- Google Cloud Pub/Sub (Receiver):
com.google.cloud:spring-cloud-gcp-starter-pubsub - Amazon Simple Email Service (SES) (Sender):
software.amazon.awssdk:ses
- Google Cloud Pub/Sub (Receiver):
- Embedded Server: Tomcat (default in Spring Boot Starter Web)
- Key Libraries:
spring-boot-starter-web: For basic web functionality and Actuator.spring-boot-starter-integration: For Pub/Sub integration.spring-boot-starter-validation: For validations.jakarta.mail: API for building emails.org.projectlombok:lombok: To reduce boilerplate code.com.latam:latam-logging-library: For standardized logging.com.google.code.gson:gson: For JSON manipulation (if needed in addition to Jackson).io.netty: Transitive dependency, managed to resolve vulnerabilities.
- Testing:
- JUnit 5
- Mockito
- Observability:
- Spring Boot Actuator
- Integration with Google Cloud Logging & Trace (if enabled)
- Licenses: See
LibrariesLicenses.txtfor full details. The project itself isProprietary and confidentialperLICENSE.
Local Setup
Prerequisites
- JDK 21.
- Gradle (the
gradlewwrapper is included in the project). - An IDE such as IntelliJ IDEA or Eclipse.
- Access to LATAM JFrog Artifactory for corporate dependencies.
- AWS Credentials:
AWS_ACCESS_KEYandAWS_SECRET_KEYwith permissions for Amazon SES. - GCP Credentials: A service account credentials file (
GOOGLE_APPLICATION_CREDENTIALS) with permissions for Google Cloud Pub/Sub (read from subscription) and, if used, Secret Manager. - A created Pub/Sub subscription and a topic to publish test messages to.
Setup Steps
-
Clone the repository: Follow these steps
-
Configure Artifactory credentials: Create a
gradle.propertiesfile at the project root (this file is in.gitignore) with your credentials:ARTIFACTORY_BASE_URL=https://artifactoryrepo1.appslatam.com/artifactory/ARTIFACTORY_GRADLE_CORP_REPO=corp-libs-gradle-releaseARTIFACTORY_USERNAME=<tu_usuario_artifactory>ARTIFACTORY_PASSWORD=<tu_api_key_artifactory> -
Configure environment variables for AWS and GCP locally: You can configure these variables on your system or in your IDE run configuration.
AWS_ACCESS_KEY=<tu_aws_access_key>AWS_SECRET_KEY=<tu_aws_secret_key>GOOGLE_APPLICATION_CREDENTIALS=/ruta/a/tu/archivo-credenciales-gcp.jsonPROJECT_ID=<tu_gcp_project_id>SUBSCRIPTION-NAME=<tu_nombre_de_suscripcion_pubsub>(This value is used inapplication.yaml; it may need adjustment inapplication-local.yamlor specific profiles)
-
Adjust
application-local.yaml(if necessary): Verify configurations insrc/main/resources/application-local.yamlandsrc/main/resources/application.yamlfor the Pub/Sub subscription and other specific parameters. For example, inapplication.yaml:gcp:subscription:name: ${{ SUBSCRIPTION-NAME }} # Ensure this variable resolves or configure directly for local -
Build the project:
./gradlew clean build -
Run the application:
./gradlew bootRunThe application will start and begin listening for messages on the configured Pub/Sub subscription.
Send a Test Message (via GCP Console or gcloud CLI)
Publish a JSON message to the Pub/Sub topic associated with your configured subscription. Message format:
{
"toEmail": "destinatario1@example.com,destinatario2@example.com",
"subject": "Asunto del correo de prueba",
"body": "<h1>Hola!</h1><p>Este es el cuerpo <b>HTML</b> del correo de prueba.</p>",
"itelement": "MI_ITELEMENT",
"application": "MI_APLICACION",
"sender": "opcional-remitente-explicito@example.com",
"attachments": [
{
"byteArray": "BASE64_ENCODED_STRING_OF_FILE_BYTES_1",
"fileName": "archivo1.pdf"
},
{
"byteArray": "BASE64_ENCODED_STRING_OF_FILE_BYTES_2",
"fileName": "imagen.png"
}
]
}
toEmail: Comma-separated list of email addresses.body: Can be plain text or HTML.itelement,application: Used to build the sender ifsenderis not provided.sender(optional): If provided, this sender is used directly. Otherwise, it is built as<application>-<itelement>@mails-noprod.appslatam.com(or@mails.appslatam.comfor prod).attachments(optional): List of objects, each withbyteArray(file content in Base64) andfileName.
Deployment
The project is configured to be deployed on Google Kubernetes Engine (GKE) using the LATAM corporate pipeline.
- Jenkins Pipeline: The
Jenkinsfiledefines the pipeline to use:pipelineLatam("gke-krane-java21") - Kubernetes Configuration: Located in the
deploy/gke/folder. The main file isdeployment.yaml.erb, a template processed during deployment. - Environment Variables per Environment: Files in
deploy/env/(e.g.dev.yaml,intg.yaml,prod.yaml) are used to specify dynamic environment variables for each environment. - Dockerfile: Located at the project root, defines the Docker image to build. Uses a LATAM
JRE 21 base image.
FROM <%= redhat_ubi_8_jre_21 %>(or similarlatam-jre21). - Key Environment Variables in Deployment:
ENVIRONMENT: (dev, intg, prod)AWS_ACCESS_KEY: AWS access key (obtained from Secret Manager).AWS_SECRET_KEY: AWS secret key (obtained from Secret Manager).PROJECT_ID: GCP project ID.SUBSCRIPTION-NAME: Full Pub/Sub subscription name (e.g.projects/mi-proyecto/subscriptions/mi-subscripcion).- Other
configMapconfigurations indeployment.yaml.erb.
API Endpoints
This service does not expose a traditional REST API for its main functionality, as it operates by consuming Google Cloud Pub/Sub messages.
Main Interaction via Pub/Sub:
- Mechanism: The service listens to a Pub/Sub subscription.
- Message Format: JSON (see example in "Local Setup" section).
Actuator Endpoints (Management and Health):
These endpoints are exposed by Spring Boot Actuator and are configured in deployment.yaml.erb and application.yaml.
The contextPath is /api/email/ses.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/email/ses/actuator/health | Overall application health status. |
GET | /api/email/ses/actuator/health/readiness | Kubernetes Readiness probe. |
GET | /api/email/ses/actuator/health/liveness | Kubernetes Liveness probe. |
GET | /api/email/ses/actuator/info | Application information (build, git). |
GET | /api/email/ses/actuator/loggers | View and modify log levels. |
GET | /api/email/ses/actuator/metrics | Application metrics (JVM, HTTP, etc.). |
There is no explicit swagger.yaml for business endpoints, but if springdoc-openapi is active, /v3/api-docs
may be available for Actuator endpoints.
Specific Components
Google Cloud Pub/Sub
- Purpose: Receive email sending requests asynchronously.
- Configuration:
In
src/main/resources/application.yaml:Authentication is typically handled via thespring:cloud:gcp:project-id: ${PROJECT_ID} # Environment variable# ...gcp:subscription:name: ${{ SUBSCRIPTION-NAME }} # Environment variable, e.g. projects/mi-proyecto/subscriptions/mi-suscripcion-emailGOOGLE_APPLICATION_CREDENTIALSenvironment variable (locally) or the service account assigned to the Kubernetes pod (on GKE). - Main Implementation:
com.latam.ltmds.email.acl.infrastructure.pubsub.PubSubReceiver: Contains the@ServiceActivatorthat listens to messages fromPubSubInboundChannelAdapter.com.latam.ltmds.email.acl.domain.service.pubsub.PubSubService: Processes the received message logic, transforms the payload, and calls the email service.
- Disable (if necessary):
- Remove the
com.google.cloud:spring-cloud-gcp-starter-pubsubdependency fromgradle/dependencies.gradle. - Remove or comment out Pub/Sub-related classes (
PubSubReceiver,PubSubService) and their configuration. - Remove
gcp.subscription.nameandgcp.project-idconfigurations fromapplication.yaml.
- Remove the
Amazon Simple Email Service (SES)
-
Purpose: Actually send emails.
-
Configuration: In
src/main/resources/application.yaml:aws:region:static: us-east-1 # AWS SES regionauto: falsestack:auto: falsecredentials:access-key: ${AWS_ACCESS_KEY:aws_key} # Environment variablesecret-key: ${AWS_SECRET_KEY:aws_secret_key} # Environment variablemail:sender:non-prod: "@mails-noprod.appslatam.com" # Domain for senders in non-productionprod: "@mails.appslatam.com" # Domain for senders in productionCredentials
AWS_ACCESS_KEYandAWS_SECRET_KEYmust be configured as environment variables or secrets (on GKE, they are obtained from GCP Secret Manager). -
Main Implementation:
com.latam.ltmds.email.acl.infrastructure.ses.AmazonSesConfig: Configures the AWS SDKSesClient.com.latam.ltmds.email.acl.domain.service.email.EmailAmazonService: Facade service for sending emails.com.latam.ltmds.email.acl.domain.service.email.EmailAmazonRawService: Logic to build the MIME message (including HTML body and attachments) and send it using SESSendRawEmail.
-
Disable (if necessary):
- Remove the
software.amazon.awssdk:sesdependency fromgradle/dependencies.gradle. - Remove or comment out SES-related classes (
AmazonSesConfig,EmailAmazonService,EmailAmazonRawService). - Remove
aws.*andmail.sender.*configurations fromapplication.yaml.
- Remove the
Testing
The project uses JUnit 5 and Mockito for unit tests and lightweight integration tests.
-
Run Tests:
./gradlew testThis will run all tests in
src/test/java. -
Test Configuration: Located in
gradle/test.gradle. Includes Jacoco configuration for coverage report generation.jacoco {toolVersion = "0.8.12"reportsDirectory = layout.buildDirectory.dir('reports/jacoco/test/html/')}test {systemProperty 'spring.profiles.active', 'test' // Activates the 'test' profile for testsuseJUnitPlatform()finalizedBy jacocoTestReport}jacocoTestReport {// ... report configuration ...} -
Coverage Reports: After running
./gradlew test, Jacoco HTML reports will be found atbuild/reports/jacoco/test/html/index.html. -
Test Configuration Files:
src/main/resources/application-test.yamlmay contain specific configurations for the test environment.
Security Considerations
- Credential Management:
- AWS credentials (
AWS_ACCESS_KEY,AWS_SECRET_KEY) must NEVER be hardcoded in code or in versioned configuration files. They must be injected as environment variables at runtime. On GKE, this is managed through GCP Secret Manager. - For GCP, authentication is preferably performed via the service account associated with the GKE pod.
Locally, the file referenced by
GOOGLE_APPLICATION_CREDENTIALSis used.
- AWS credentials (
- License: The project has a proprietary license (
LICENSE- "Proprietary and confidential"). Ensure you comply with its terms. - Dependencies: Keep dependencies up to date to mitigate known vulnerabilities. Regularly review SonarQube reports or other security analysis tools.
- Input Validation: Although the service consumes from Pub/Sub (an internal system), it is good practice to validate
received JSON payload data (e.g. email format, attachment size if relevant) to avoid unexpected
errors or abuse. Spring Validation (
spring-boot-starter-validation) is included and can be used in DTOs. - Logging: Ensure sensitive information such as credentials or full attachment content is not logged, unless strictly necessary for debugging and with the appropriate log level.