Una de las actividades que más disfruto implementar es la simplificación de procesos que, tradicionalmente, deben ser realizados por un ser humano o que, por diversas razones, aún no han sido automatizados.

Un ejemplo que frecuentemente me ha correspondido atender es el caso de uso en el cual un grupo de usuarios, periódicamente, necesita llevar a cabo la carga o actualización de información. Para ello, envían un correo electrónico con un archivo adjunto (generalmente en formato Excel). Dicho archivo es recibido por algún miembro del equipo técnico, quien debe ejecutar una serie de pasos para cargar o actualizar la información correspondiente. Entiendo que lo ideal sería contar con una API expuesta a la contraparte que simplifique todo este proceso; sin embargo, la realidad es que en muchas empresas esto aún no sucede.

Con esto en mente, en este artículo les explicaré cómo he utilizado AWS SES en conjunto con AWS Lambda para llevar a cabo este procesamiento de manera automatizada y eficiente.

AWS SES

Primeramente, para aquellos que no lo conozcan, AWS Simple Email Service (SES) es un servicio de correo electrónico basado en la nube, diseñado para ayudar a las empresas y desarrolladores a enviar correos electrónicos a granel y transaccionales de manera fácil, rentable y confiable. AWS SES se encarga de los detalles relacionados con la infraestructura de envío de correos electrónicos, permitiendo a los usuarios centrarse en la creación y personalización de contenido. Este servicio ofrece una API flexible y un conjunto de funcionalidades de administración de correos electrónicos, como la autenticación de mensajes, la entrega de correo y el monitoreo de la reputación del remitente.

Además de enviar correos electrónicos, AWS SES también ofrece la capacidad de recibir mensajes. Esto se logra mediante el uso de reglas de recepción configurables que permiten procesar y almacenar los correos electrónicos entrantes de acuerdo a las necesidades específicas del usuario. Al recibir un correo electrónico, SES puede ejecutar diferentes acciones, como guardar el mensaje en un bucket de Amazon S3, llamar a una función de AWS Lambda para procesar el contenido, activar la notificación por Amazon Simple Notification Service (SNS), o incluso reenviar el mensaje a otro destinatario. Esta versatilidad en la recepción y manejo de correos electrónicos entrantes es lo que necesitaba para poder dar la solución a este caso.

Voy a suponer que SES ya esta configurado a nivel de identidades, entradas de DNS y otros aspectos. Me enfocare en la implementación de ejemplo.

Configuración

El primer paso a seguir es crear un nuevo ‘Rule Set’ y definir para ella un nombre, para este ejemplo lo haremos desde la consola.

Rule Set

Luego, debemos establecer las reglas de recepción que queremos utilizar; como vemos en la imágen debemos dar un nombre, si requiere de TLS y si habilitamos el escaneo de spam y virus.

Regla

Posteriormente, para este ejemplo yo quiero que la regla aplique si el receptor del correo es una cuenta específica que he definido para esto.

Correo

El siguiente paso es indicar que acciones queremos aplicar cuando llegue un correo dirigido a esta cuenta. Para mi caso el primer paso es enviar el correo a un bucket de S3.

S3

Y la acción luego de enviar el correo al S3 es invocar a una función lambda que se encargara de procesar ese correo. Más adelante veremos un ejemplo del mismo en Java.

Lambda

Finalmente confirmamos está configuración y ya veriamos nuestra configuración activa.

Lambda

Función Lambda

Para procesar los correos que van llegando y almacenandose en el bucket de S3; usaremos una función Lambda. En este ejemplo me base en una función creada con Quarkus 2.16.6.

El código que muestro realiza varios pasos

  • (1) Obtiene por cada registro que reciba la referencia al ojeto en S3 (el messageId)
  • (2) Extrae de los encabezados, quien es el emisor (lo cual puede ser útil si sólo ciertas cuentas pueden enviar a procesar)
  • (3) Traemos el correo de S3 y lo convertimos en un MimeMessage
  • (4) Extramos los archivos anexos del correo
@Override
public void handleRequest(InputStream inputStream,
    OutputStream outputStream, Context context) {
...
MimeMessage msg;
try {
  JsonReader jsonReader = Json.createReader(inputStream);
  JsonObject jobj = jsonReader.readObject();

  JsonArray records = jobj.getJsonArray("Records");
  for (int i = 0; i < records.size(); i++) {
    JsonObject record = records.getJsonObject(i);

    // Tomamos el objeto SES
    JsonObject mailObject = record.getJsonObject("ses")
        .getJsonObject("mail");

    // (1)
    // Este es el id hacia el bucket de S3
    String messageId = mailObject.getString("messageId");

    JsonObject headers = mailObject.getJsonObject("commonHeaders");
    JsonArray arrayFrom = headers.getJsonArray("from");

    // (2)
    from = arrayFrom.get(0).toString();

    logger.log("El messageId es [" + messageId + "] y el from es [" + from + "]");

    // (3) Leemos el objeto de S3 y lo convertimos a un MimeMessage
    ByteArrayOutputStream baos = getObject(messageId);                
    InputStream is = new ByteArrayInputStream(baos.toByteArray());
    msg = new MimeMessage(session, is);

    if (msg.getContentType().contains("multipart")) {
        // Nosd interesa los multipart
        Multipart multiPart = (Multipart) msg.getContent();
        extractAttachments(multiPart, logger, from);

    } else {
        ...
    }
  }
...
}

El código para obtener el objeto de S3 es el siguiente:

private ByteArrayOutputStream getObject(String key) {

    // Create a GetObject request
    GetObjectRequest getObjectRequest = GetObjectRequest.builder()
            .bucket(bucketName)
            .key(key)
            .build();

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    GetObjectResponse objectResponse = s3.getObject(getObjectRequest,
       ResponseTransformer.toOutputStream(baos));

    return baos;
}

Y el código que extrae los anexos es el siguiente; de notar lo siguiente:

  • (1) Puede ser que el archivo este dentro de otro correo; se que suena algo ilógico pero sucede y para atender esos casos aplico algo de recursión
  • (2) Tomamos el anexo y lo convierto en un Workbook usando POI para a partir de aquí hacer lo que necesito con los datos en el Excel, por ejemplo: procesar y guardarlo en una base de datos, llamar a un API interno, invocar a otros servicios en AWS. Las opciones son ilimitadas
private void extractAttachments(Multipart multiPart,
    LambdaLogger logger, String sourceEmail) {
try {
  for (int i = 0; i < multiPart.getCount(); i++) {
      BodyPart part = multiPart.getBodyPart(i);
      if (part.getContent() instanceof Multipart) {
        // (1)                    
          extractAttachments((Multipart) part.getContent(),
              logger, sourceEmail);
      } else if (part instanceof MimeBodyPart) {
        MimeBodyPart mimeBodyPart = (MimeBodyPart) part;
        if (Part.ATTACHMENT.equalsIgnoreCase(
                mimeBodyPart.getDisposition())) {
           // (2)
            Workbook workbook =
                  new XSSFWorkbook(mimeBodyPart.getInputStream());
            ....
        }
       ...
  ....           

Conclusión

El uso de AWS SES resulta sumamente eficiente cuando necesitamos procesar correos electrónicos entrantes para realizar alguna acción o tratamiento específico. En este caso, hemos empleado S3 y Lambda para desarrollar un proceso asíncrono; sin embargo, las posibilidades son vastas y se ajustan a nuestras necesidades particulares.

Además, nos permite simplificar procesos que normalmente serían realizados por una persona, minimizando así la posibilidad de errores. Lo más destacable es que la implementación de esta solución no requiere mucho tiempo y, a largo plazo, ahorra un valioso tiempo en nuestras operaciones.

Espero que este artículo les haya sido de utilidad.