Template Email ACL
Propósito y Alcance
Este proyecto actúa como un ACL (Access Control Layer) de Correos. Su principal objetivo es recibir solicitudes de envío de correo electrónico a través de mensajes en Google Cloud Pub/Sub y procesarlos para enviarlos utilizando Amazon Simple Email Service (SES).
Problema que resuelve: Proporciona una interfaz centralizada y desacoplada para el envío de correos electrónicos, permitiendo que otros servicios deleguen esta funcionalidad sin necesidad de integrar directamente el SDK de Amazon SES ni gestionar credenciales de AWS de forma distribuida.
Alcance General:
- Recepción de mensajes en formato JSON desde una suscripción de Google Cloud Pub/Sub.
- Parseo del mensaje para extraer destinatarios, asunto, cuerpo del correo (HTML soportado) y adjuntos.
- Construcción dinámica del remitente del correo basado en metadatos de la aplicación solicitante y el entorno.
- Conversión y manejo de archivos adjuntos (codificados en Base64).
- Integración con Amazon SES para el envío efectivo de los correos.
- Manejo de configuración específica por entorno (local, dev, intg, prod).
Fuera del Alcance:
- No expone una API REST pública para el envío de correos (la interacción es vía Pub/Sub).
- No gestiona plantillas de correo complejas (el cuerpo del correo se recibe directamente).
- No implementa lógica de reintentos avanzada más allá de lo que Pub/Sub pueda ofrecer.
Arquitectura
El proyecto sigue una arquitectura hexagonal ( puertos y adaptadores), buscando un buen desacoplamiento entre la lógica de negocio (dominio) y los componentes de infraestructura (integraciones con servicios externos).
Flujo Principal:
- Un mensaje es publicado en un topic de Google Cloud Pub/Sub por un servicio cliente.
- El
PubSubReceiver(adaptador de entrada) en este proyecto está suscrito a dicho topic (a través de una suscripción específica) y recibe el mensaje. - El mensaje (payload JSON) es procesado por el
PubSubService(lógica de aplicación/dominio). - El
PubSubServicetransforma el payload en un objetoEmailDetails, determina el remitente (fromemail), y prepara los detalles para el envío. - Si hay adjuntos, se decodifican de Base64 y se preparan.
- El
EmailAmazonServiceyEmailAmazonRawService(adaptadores de salida) utilizan el cliente de Amazon SES para construir y enviar el correo electrónico. - Las credenciales para AWS SES (
AWS_ACCESS_KEY,AWS_SECRET_KEY) se gestionan de forma segura, típicamente a través de secretos en GCP Secret Manager inyectados como variables de entorno en el despliegue de GKE.
Estructura del Proyecto (simplificada):
com.latam.ltmds.email.acl
├── application
│ └── request/ # DTOs como EmailDetails, Attachment
├── domain
│ ├── service/ # Lógica de negocio principal
│ │ ├── email/ # Servicios relacionados con Email (interfaz con SES)
│ │ └── pubsub/ # Servicios relacionados con Pub/Sub (procesamiento de mensajes)
│ └── exception/ # Excepciones customizadas
└── infrastructure
├── config/ # Configuración de Spring, Beans
├── pubsub/ # Adaptador para recibir mensajes de Pub/Sub
└── ses/ # Adaptador para enviar correos vía Amazon SES
Tecnologías y Dependencias
- Lenguaje: Java 21
- Framework: Spring Boot 3.4.5
- Build Tool: Gradle 8.12.1
- Mensajería Asíncrona:
- Google Cloud Pub/Sub (Receptor):
com.google.cloud:spring-cloud-gcp-starter-pubsub - Amazon Simple Email Service (SES) (Emisor):
software.amazon.awssdk:ses
- Google Cloud Pub/Sub (Receptor):
- Servidor Embebido: Tomcat (default en Spring Boot Starter Web)
- Bibliotecas Clave:
spring-boot-starter-web: Para funcionalidades web básicas y Actuator.spring-boot-starter-integration: Para la integración con Pub/Sub.spring-boot-starter-validation: Para validaciones.jakarta.mail: API para la construcción de correos.org.projectlombok:lombok: Para reducir código boilerplate.com.latam:latam-logging-library: Para logging estandarizado.com.google.code.gson:gson: Para manipulación de JSON (si es necesario adicionalmente a Jackson).io.netty: Dependencia transitiva, gestionada para resolver vulnerabilidades.
- Pruebas:
- JUnit 5
- Mockito
- Observabilidad:
- Spring Boot Actuator
- Integración con Google Cloud Logging & Trace (si está habilitado)
- Licencias: Consultar
LibrariesLicenses.txtpara un detalle completo. El proyecto en sí esProprietary and confidentialsegúnLICENSE.
Configuración Local
Prerrequisitos
- JDK 21.
- Gradle (el wrapper
gradlewestá incluido en el proyecto). - Una IDE como IntelliJ IDEA o Eclipse.
- Acceso a JFrog Artifactory de LATAM para dependencias corporativas.
- Credenciales de AWS:
AWS_ACCESS_KEYyAWS_SECRET_KEYcon permisos para Amazon SES. - Credenciales de GCP: Un archivo de credenciales de cuenta de servicio (
GOOGLE_APPLICATION_CREDENTIALS) con permisos para Google Cloud Pub/Sub (leer de suscripción) y, si se usa, Secret Manager. - Una suscripción de Pub/Sub creada y un topic al que enviar mensajes de prueba.
Pasos de Configuración
-
Clonar el repositorio: Sigue los siguientes pasos
-
Configurar credenciales de Artifactory: Crea un archivo
gradle.propertiesen la raíz del proyecto (este archivo está en.gitignore) con tus credenciales: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> -
Configurar variables de entorno para AWS y GCP localmente: Puedes configurar estas variables en tu sistema o en la configuración de ejecución de tu IDE.
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>(Este valor se usa enapplication.yaml, podría necesitar ajuste enapplication-local.yamlo perfiles específicos)
-
Ajustar
application-local.yaml(si es necesario): Verifica las configuraciones ensrc/main/resources/application-local.yamlysrc/main/resources/application.yamlpara la suscripción de Pub/Sub y otros parámetros específicos. Por ejemplo, enapplication.yaml:gcp:subscription:name: ${{ SUBSCRIPTION-NAME }} # Asegúrate que esta variable se resuelva o configura directamente para local -
Compilar el proyecto:
./gradlew clean build -
Ejecutar la aplicación:
./gradlew bootRunLa aplicación se iniciará y comenzará a escuchar mensajes en la suscripción de Pub/Sub configurada.
Enviar un Mensaje de Prueba (vía GCP Console o gcloud CLI)
Publica un mensaje JSON al topic de Pub/Sub asociado con tu suscripción configurada. Formato del mensaje:
{
"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: Lista de correos separados por coma.body: Puede ser texto plano o HTML.itelement,application: Usados para construir el remitente sisenderno se provee.sender(opcional): Si se provee, se usa este remitente directamente. Si no, se construye como<application>-<itelement>@mails-noprod.appslatam.com(o@mails.appslatam.compara prod).attachments(opcional): Lista de objetos, cada uno conbyteArray(contenido del archivo en Base64) yfileName.
Despliegue
El proyecto está configurado para ser desplegado en Google Kubernetes Engine (GKE) utilizando el pipeline corporativo de LATAM.
- Pipeline Jenkins: El archivo
Jenkinsfiledefine el pipeline a utilizar:pipelineLatam("gke-krane-java21") - Configuración de Kubernetes: Se encuentra en la carpeta
deploy/gke/. El archivo principal esdeployment.yaml.erb, que es una plantilla procesada durante el despliegue. - Variables de Entorno por Ambiente: Los archivos en
deploy/env/(ej.dev.yaml,intg.yaml,prod.yaml) se utilizan para especificar variables de entorno dinámicas para cada ambiente. - Dockerfile: Ubicado en la raíz del proyecto, define la imagen Docker a construir. Utiliza una imagen base de
LATAM JRE 21.
FROM <%= redhat_ubi_8_jre_21 %>(o similarlatam-jre21). - Variables de Entorno Clave en Despliegue:
ENVIRONMENT: (dev, intg, prod)AWS_ACCESS_KEY: Clave de acceso AWS (obtenida de Secret Manager).AWS_SECRET_KEY: Clave secreta AWS (obtenida de Secret Manager).PROJECT_ID: ID del proyecto GCP.SUBSCRIPTION-NAME: Nombre completo de la suscripción Pub/Sub (ej.projects/mi-proyecto/subscriptions/mi-subscripcion).- Otras configuraciones del
configMapendeployment.yaml.erb.
Endpoints de la API
Este servicio no expone una API REST tradicional para sus funcionalidades principales, ya que opera consumiendo mensajes de Google Cloud Pub/Sub.
Interacción Principal vía Pub/Sub:
- Mecanismo: El servicio escucha una suscripción de Pub/Sub.
- Formato de Mensaje: JSON (ver ejemplo en sección "Configuración Local").
Endpoints de Actuator (Manejo y Salud):
Estos endpoints son expuestos por Spring Boot Actuator y se configuran en deployment.yaml.erb y application.yaml.
El contextPath es /api/email/ses.
| Método | Endpoint | Descripción |
|---|---|---|
GET | /api/email/ses/actuator/health | Estado general de salud de la aplicación. |
GET | /api/email/ses/actuator/health/readiness | Sonda de Readiness para Kubernetes. |
GET | /api/email/ses/actuator/health/liveness | Sonda de Liveness para Kubernetes. |
GET | /api/email/ses/actuator/info | Información de la aplicación (build, git). |
GET | /api/email/ses/actuator/loggers | Visualizar y modificar niveles de log. |
GET | /api/email/ses/actuator/metrics | Métricas de la aplicación (JVM, HTTP, etc.). |
No hay swagger.yaml explícito para endpoints de negocio, pero si springdoc-openapi está activo, /v3/api-docs
podría estar disponible para los endpoints de Actuator.
Componentes Específicos
Google Cloud Pub/Sub
- Propósito: Recibir las solicitudes de envío de correo de forma asíncrona.
- Configuración:
En
src/main/resources/application.yaml:La autenticación se maneja típicamente mediante la variable de entornospring:cloud:gcp:project-id: ${PROJECT_ID} # Variable de entorno# ...gcp:subscription:name: ${{ SUBSCRIPTION-NAME }} # Variable de entorno, ej: projects/mi-proyecto/subscriptions/mi-suscripcion-emailGOOGLE_APPLICATION_CREDENTIALS(localmente) o la cuenta de servicio asignada al pod de Kubernetes (en GKE). - Implementación Principal:
com.latam.ltmds.email.acl.infrastructure.pubsub.PubSubReceiver: Contiene el@ServiceActivatorque escucha los mensajes delPubSubInboundChannelAdapter.com.latam.ltmds.email.acl.domain.service.pubsub.PubSubService: Procesa la lógica del mensaje recibido, transforma el payload y llama al servicio de email.
- Deshabilitar (si fuera necesario):
- Eliminar la dependencia
com.google.cloud:spring-cloud-gcp-starter-pubsubdegradle/dependencies.gradle. - Remover o comentar las clases relacionadas con Pub/Sub (
PubSubReceiver,PubSubService) y su configuración. - Quitar las configuraciones
gcp.subscription.nameygcp.project-iddeapplication.yaml.
- Eliminar la dependencia
Amazon Simple Email Service (SES)
-
Propósito: Enviar efectivamente los correos electrónicos.
-
Configuración: En
src/main/resources/application.yaml:aws:region:static: us-east-1 # Región de AWS SESauto: falsestack:auto: falsecredentials:access-key: ${AWS_ACCESS_KEY:aws_key} # Variable de entornosecret-key: ${AWS_SECRET_KEY:aws_secret_key} # Variable de entornomail:sender:non-prod: "@mails-noprod.appslatam.com" # Dominio para remitentes en no-producciónprod: "@mails.appslatam.com" # Dominio para remitentes en producciónLas credenciales
AWS_ACCESS_KEYyAWS_SECRET_KEYdeben ser configuradas como variables de entorno o secretos (en GKE, se obtienen de GCP Secret Manager). -
Implementación Principal:
com.latam.ltmds.email.acl.infrastructure.ses.AmazonSesConfig: Configura elSesClientde AWS SDK.com.latam.ltmds.email.acl.domain.service.email.EmailAmazonService: Servicio de fachada para el envío de correos.com.latam.ltmds.email.acl.domain.service.email.EmailAmazonRawService: Lógica para construir el mensaje MIME (incluyendo cuerpo HTML y adjuntos) y enviarlo usandoSendRawEmailde SES.
-
Deshabilitar (si fuera necesario):
- Eliminar la dependencia
software.amazon.awssdk:sesdegradle/dependencies.gradle. - Remover o comentar las clases relacionadas con SES (
AmazonSesConfig,EmailAmazonService,EmailAmazonRawService). - Quitar las configuraciones
aws.*ymail.sender.*deapplication.yaml.
- Eliminar la dependencia
Pruebas
El proyecto utiliza JUnit 5 y Mockito para pruebas unitarias y de integración ligera.
-
Ejecutar Pruebas:
./gradlew testEsto ejecutará todas las pruebas en
src/test/java. -
Configuración de Pruebas: Se encuentra en
gradle/test.gradle. Incluye la configuración de Jacoco para la generación de reportes de cobertura.jacoco {toolVersion = "0.8.12"reportsDirectory = layout.buildDirectory.dir('reports/jacoco/test/html/')}test {systemProperty 'spring.profiles.active', 'test' // Activa el perfil 'test' para las pruebasuseJUnitPlatform()finalizedBy jacocoTestReport}jacocoTestReport {// ... configuración de reportes ...} -
Reportes de Cobertura: Después de ejecutar
./gradlew test, los reportes HTML de Jacoco se encontrarán enbuild/reports/jacoco/test/html/index.html. -
Archivos de Configuración para Pruebas:
src/main/resources/application-test.yamlpuede contener configuraciones específicas para el entorno de pruebas.
Consideraciones de Seguridad
- Gestión de Credenciales:
- Las credenciales de AWS (
AWS_ACCESS_KEY,AWS_SECRET_KEY) NUNCA deben estar hardcodeadas en el código o en archivos de configuración versionados. Deben ser inyectadas como variables de entorno en tiempo de ejecución. En GKE, esto se gestiona a través de GCP Secret Manager. - Para GCP, la autenticación se realiza preferentemente mediante la cuenta de servicio asociada al pod de GKE.
Localmente, se usa el archivo referenciado por
GOOGLE_APPLICATION_CREDENTIALS.
- Las credenciales de AWS (
- Licencia: El proyecto tiene una licencia propietaria (
LICENSE- "Proprietary and confidential"). Asegúrate de cumplir con sus términos. - Dependencias: Mantén las dependencias actualizadas para mitigar vulnerabilidades conocidas. Revisa regularmente los informes de SonarQube u otras herramientas de análisis de seguridad.
- Validación de Entradas: Aunque el servicio consume de Pub/Sub (un sistema interno), es una buena práctica validar
los datos del payload JSON recibido (ej. formato de emails, tamaño de adjuntos si es relevante) para evitar errores
inesperados o abusos. Spring Validation (
spring-boot-starter-validation) está incluido y puede ser utilizado en los DTOs. - Logging: Asegúrate de no loguear información sensible como las credenciales o el contenido completo de archivos adjuntos, a menos que sea estrictamente necesario para debugging y con el nivel de log adecuado.