Una nueva forma de usar el correo electrónico para aplicaciones de soporte: un tutorial de AWS

Publicado: 2022-03-11

Es posible que el correo electrónico no sea tan bueno como otras plataformas de comunicación, pero trabajar con él aún puede ser divertido. Recientemente me encargaron implementar la mensajería en una aplicación móvil. El único inconveniente era que la comunicación real debía realizarse por correo electrónico. Queríamos que los usuarios de la aplicación pudieran comunicarse con un equipo de soporte de la misma manera que enviaría un mensaje de texto. Los miembros del equipo de soporte necesitaban recibir estos mensajes por correo electrónico y también debían poder responder al usuario que los originó. Para el usuario final, todo lo necesario para verse y funcionar como cualquier otra aplicación de mensajería moderna.

En este artículo, veremos cómo implementar un servicio similar al descrito anteriormente utilizando Java y algunos servicios web de Amazon. Necesitará una cuenta de AWS válida, un nombre de dominio y acceso a su IDE de Java favorito.

La infraestructura

Antes de escribir cualquier código, vamos a configurar los servicios de AWS necesarios para enrutar y consumir correo electrónico. Vamos a utilizar SES para enviar y consumir correos electrónicos y SNS+SQS para enrutar los mensajes entrantes.

Consumo de correo electrónico programáticamente con AWS

Revitalice el correo electrónico en las aplicaciones de soporte con Amazon SES.
Pío

Todo comienza aquí con SES. Comience iniciando sesión en su cuenta de AWS y navegando a la consola de SES.

Antes de comenzar, necesitará un nombre de dominio verificado desde el que pueda enviar correos electrónicos.

Esta será la aplicación de dominio desde la que los usuarios enviarán mensajes de correo electrónico y los miembros de soporte responderán. Verificar un dominio con SES es un proceso sencillo, y puede encontrar más información aquí.

Si es la primera vez que usa SES o no ha solicitado un límite de envío, su cuenta se aislará. Esto significa que no podrá enviar correos electrónicos a direcciones que no estén verificadas con AWS. Esto puede causar un error más adelante en el tutorial, cuando enviamos un correo electrónico a nuestra mesa de ayuda ficticia. Para evitar esto, puede verificar cualquier dirección de correo electrónico que planee usar como su mesa de ayuda en la consola SES en la pestaña Direcciones de correo electrónico.

Una vez que tenga un dominio verificado, podemos crear un conjunto de reglas. Navegue a la pestaña Conjuntos de reglas en la consola SES y cree una nueva regla de recibo.

El primer paso al crear una regla de recepción será definir un destinatario.

Los filtros de destinatarios le permitirán definir qué correos electrónicos consumirá SES y cómo procesar cada mensaje entrante. El destinatario que definimos aquí debe coincidir con el dominio y el patrón de dirección desde el que se envían los mensajes de usuario de la aplicación. El caso más simple aquí sería agregar un destinatario para el dominio que verificamos previamente, en nuestro caso ejemplo.com . Esto configurará SES para aplicar nuestra regla a todos los correos electrónicos enviados a example.com . (por ejemplo, [email protected], [email protected]).

Para crear una regla para todo nuestro dominio, agregaríamos un destinatario para ejemplo.com .

También es posible hacer coincidir los patrones de dirección. Esto es útil si desea enrutar los mensajes entrantes a diferentes colas de SQS.

Digamos que tenemos la cola A y la cola B. Podríamos agregar dos destinatarios: [email protected] y [email protected] . Si queremos insertar un mensaje en la cola A, enviaremos un correo electrónico [email protected]. La parte de esto coincidirá con nuestro destinatario [email protected] . Todo lo que esté entre + y @ son datos de usuario arbitrarios, no afectará la coincidencia de direcciones de SES. Para insertar en la cola B, simplemente reemplace a con b.

Después de definir sus destinatarios, el siguiente paso es configurar la acción que SES realizará después de consumir un nuevo correo electrónico. Eventualmente queremos que estos terminen en SQS, sin embargo, actualmente no es posible pasar directamente de SES a SQS. Para cerrar la brecha, necesitamos usar SNS. Seleccione la acción SNS y cree un tema nuevo. Eventualmente configuraremos este tema para insertar mensajes en SQS.

Seleccione crear tema SNS y asígnele un nombre.

Después de crear el tema, debemos seleccionar una codificación de mensaje. Voy a usar Base64 para conservar los caracteres especiales. La codificación que elija afectará la forma en que se decodifican los mensajes cuando los consumimos en nuestro servicio.

Una vez que se establece la regla, solo tenemos que nombrarla.

El siguiente paso será configurar SQS y SNS, para eso debemos dirigirnos a la consola de SQS y crear una nueva cola.

Para mantener las cosas simples, estoy usando el mismo nombre que nuestro tema de SNS.

Después de definir nuestra cola, necesitaremos ajustar su política de acceso. Solo queremos otorgarle a nuestro tema de SNS permiso para insertar. Podemos lograr esto agregando una condición que coincida con nuestro tema SNS arn.

El campo de valor debe completarse con el ARN para el tema de SNS que SES está notificando.

Después de configurar SQS, es hora de volver a la consola de SNS para configurar su tema e insertar notificaciones en su nueva y brillante cola de SQS.

En la consola de SNS, seleccione el tema que SES está notificando. A partir de ahí, cree una nueva suscripción. El protocolo de suscripción debe ser Amazon SQS y el destino debe ser el ARN de la cola de SQS que acaba de generar.

Después de todo eso, el lado AWS de la ecuación debería estar listo. Podemos probar nuestro trabajo enviándonos un correo electrónico. Envíe un correo electrónico al dominio configurado con SES, luego diríjase a la consola de SQS y seleccione su cola. Debería poder ver la carga útil que contiene su correo electrónico.

Servicio Java para gestionar correos electrónicos

¡Ahora vamos a la parte divertida! En esta sección, vamos a crear un microservicio simple capaz de enviar mensajes y procesar correos electrónicos entrantes. El primer paso será definir una API que enviará un correo electrónico a nuestra mesa de soporte en nombre de un usuario.

Una nota rápida. Nos vamos a centrar en los componentes de lógica empresarial de este servicio y no definiremos puntos finales REST ni una capa de persistencia.

Para construir un servicio Spring, usaremos Spring Boot y Maven. Podemos usar Spring Initializer para generar un proyecto para nosotros, start.spring.io.

Para empezar, nuestro pom.xml debería verse así:

 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.toptal.tutorials</groupId> <artifactId>email-processor</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>email-processor</name> <description>A simple "micro-service" for emailing support on behalf of a user and processing replies</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

Envío de soporte por correo electrónico en nombre de un usuario

Primero, definamos un bean para enviar correos electrónicos a nuestra mesa de soporte en nombre de un usuario. El trabajo de este bean será procesar un mensaje entrante de una identificación de usuario y enviar ese mensaje por correo electrónico a nuestra dirección de correo electrónico de soporte predefinida.

Comencemos definiendo una interfaz.

 public interface SupportBean { /** * Send a message to the application support desk on behalf of a user * @param fromUserId The ID of the originating user * @param message The message to send */ void messageSupport(long fromUserId, String message); }

Y una implementación vacía:

 @Component public class SupportBeanSesImpl implements SupportBean { /** * Email address for our application help-desk * This is the destination address user support emails will be sent to */ private static final String SUPPORT_EMAIL_ADDRESS = "[email protected]"; @Override public void messageSupport(long fromUserId, String message) { //todo: send an email to our support address } }

Agreguemos también el SDK de AWS a nuestro pom, usaremos el cliente SES para enviar nuestros correos electrónicos:

 <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>

Lo primero que debemos hacer es generar una dirección de correo electrónico para enviar el mensaje de nuestro usuario. La dirección que generamos jugará un papel fundamental en el lado consumidor de nuestro servicio. Debe contener suficiente información para enrutar la respuesta de la mesa de ayuda al usuario de origen.

Para lograr esto, vamos a incluir la ID de usuario de origen en nuestra dirección de correo electrónico generada. Para mantener las cosas limpias, vamos a crear un objeto que contenga el ID de usuario y usaremos la cadena JSON codificada en Base64 como la dirección de correo electrónico.

Vamos a crear un nuevo bean responsable de convertir una ID de usuario en una dirección de correo electrónico.

 public interface UserEmailBean { /** * Returns a unique per user email address * @param userID Input user ID * @return An email address unique for the input userID */ String emailAddressForUserID(long userID); }

Comencemos nuestra implementación agregando los consentimientos necesarios y una clase interna simple que nos ayudará a serializar nuestro JSON.

 @Component public class UserEmailBeanJSONImpl implements UserEmailBean { /** * The TLD for all our generated email addresses */ private static final String EMAIL_DOMAIN = "example.com"; /** * com.fasterxml.jackson.databind.ObjectMapper used to create a JSON object including our user ID */ private final ObjectMapper objectMapper = new ObjectMapper(); @Override public String emailAddressForUserID(long userID) { //todo: create the email address return null; } /** * Simple helper class we will serialize. * The JSON representation of this class will become our user email address */ private static class UserDetails{ private Long userID; public Long getUserID() { return userID; } public void setUserID(Long userID) { this.userID = userID; } } }

Generar nuestra dirección de correo electrónico es sencillo, todo lo que tenemos que hacer es crear un objeto UserDetails y codificar en Base64 la representación JSON. La versión final de nuestro método createAddressForUserID debería verse así:

 @Override public String emailAddressForUserID(long userID) { UserDetails userDetails = new UserDetails(); userDetails.setUserID(userID); //create a JSON representation. String jsonString = objectMapper.writeValueAsString(userDetails); //Base64 encode it String base64String = Base64.getEncoder().encodeToString(jsonString.getBytes()); //create an email address out of it String emailAddress = base64String + "@" + EMAIL_DOMAIN; return emailAddress; }

Ahora podemos regresar a SupportBeanSesImpl y actualizarlo para usar el nuevo bean de correo electrónico que acabamos de crear.

 private final UserEmailBean userEmailBean; @Autowired public SupportBeanSesImpl(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; } @Override public void messageSupport(long fromUserId, String message) throws JsonProcessingException { //user specific email String fromEmail = userEmailBean.emailAddressForUserID(fromUserId); }

Para enviar correos electrónicos, utilizaremos el cliente AWS SES incluido con el SDK de AWS.

 /** * SES client */ private final AmazonSimpleEmailService amazonSimpleEmailService = new AmazonSimpleEmailServiceClient( new DefaultAWSCredentialsProviderChain() //see http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html );

Estamos utilizando DefaultAWSCredentialsProviderChain para administrar las credenciales por nosotros, esta clase buscará las credenciales de AWS como se define aquí.

Vamos a una clave de acceso de AWS provista con acceso a SES y eventualmente a SQS. Para obtener más información, consulte la documentación de Amazon.

El siguiente paso será actualizar nuestro método messageSupport para enviar soporte por correo electrónico utilizando el SDK de AWS. El SES SDK hace que este sea un proceso sencillo. El método terminado debería verse así:

 @Override public void messageSupport(long fromUserId, String message) throws JsonProcessingException { //User specific email String fromEmail = userEmailBean.emailAddressForUserID(fromUserId); //create the email Message supportMessage = new Message( new Content("New support request from userID " + fromUserId), //Email subject new Body().withText(new Content(message)) //Email body, this contains the user's message ); //create the send request SendEmailRequest supportEmailRequest = new SendEmailRequest( fromEmail, //From address, our user's generated email new Destination(Collections.singletonList(SUPPORT_EMAIL_ADDRESS)), //to address, our support email address supportMessage //Email body defined above ); //Send it off amazonSimpleEmailService.sendEmail(supportEmailRequest); }

Para probarlo, cree una clase de prueba e inyecte SupportBean. Asegúrese de que SUPPORT_EMAIL_ADDRESS definido en SupportBeanSesImpl apunte a una dirección de correo electrónico de su propiedad. Si su cuenta de SES está en un espacio aislado, esta dirección también debe verificarse. Las direcciones de correo electrónico se pueden verificar en la consola SES en la sección Direcciones de correo electrónico.

 @Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }

Después de ejecutar esto, debería ver un mensaje en su bandeja de entrada. Mejor aún, responda al mensaje y verifique la cola de SQS que configuramos anteriormente. Debería ver una carga útil que contiene su respuesta.

Consumo de respuestas de SQS

El último paso será leer los correos electrónicos de SQS, analizar el mensaje de correo electrónico y averiguar a qué ID de usuario pertenece la respuesta.

Los servicios de colas de mensajes como Amazon SQS desempeñan un papel vital en la arquitectura orientada a servicios al permitir que los servicios se comuniquen entre sí sin tener que comprometer la velocidad, la confiabilidad o la escalabilidad.

Para escuchar nuevos mensajes de SQS, vamos a utilizar el SDK de mensajería Spring Cloud AWS. Esto nos permitirá configurar un detector de mensajes SQS a través de anotaciones y, por lo tanto, evitar un poco de código repetitivo.

Primero, las dependencias requeridas.

Agregue la dependencia de mensajería de Spring Cloud:

 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>

Y agregue Spring Cloud AWS a su administración de dependencia pom:

 <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws</artifactId> <version>1.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

Actualmente, Spring Cloud AWS no admite la configuración basada en anotaciones, por lo que tendremos que definir un bean XML. Afortunadamente, no necesitamos mucha configuración, por lo que nuestra definición de bean será bastante ligera. El punto principal de este archivo será habilitar los oyentes de cola controlados por anotaciones, esto nos permitirá anotar un método como un SqsListener.

Cree un nuevo archivo XML llamado aws-config.xml en su carpeta de recursos. Nuestra definición debería ser algo como esto:

 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aws-context="http://www.springframework.org/schema/cloud/aws/context" xmlns:aws-messaging="http://www.springframework.org/schema/cloud/aws/messaging" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cloud/aws/context http://www.springframework.org/schema/cloud/aws/context/spring-cloud-aws-context.xsd http://www.springframework.org/schema/cloud/aws/messaging http://www.springframework.org/schema/cloud/aws/messaging/spring-cloud-aws-messaging.xsd"> <!--enable annotation driven queue listeners --> <aws-messaging:annotation-driven-queue-listener /> <!--define our region, this lets us reference queues by name instead of by URL. --> <aws-context:context-region region="us-east-1" /> </beans>

La parte importante de este archivo es <aws-messaging:annotation-driven-queue-listener /> . También estamos definiendo una región predeterminada. Esto no es necesario, pero hacerlo nos permitirá hacer referencia a nuestra cola SQS por nombre en lugar de URL. No estamos definiendo ninguna credencial de AWS, al omitirlas, Spring usará de manera predeterminada DefaultAWSCredentialsProviderChain, el mismo proveedor que usamos anteriormente en nuestro bean SES. Se puede encontrar más información en los documentos de Spring Cloud AWS.

Para usar esta configuración XML en nuestra aplicación Spring Boot, debemos importarla explícitamente. Dirígete a tu clase @SpringBootApplication e impórtala.

 @SpringBootApplication @ImportResource("classpath:aws-config.xml") //Explicit import for our AWS XML bean definition public class EmailProcessorApplication { public static void main(String[] args) { SpringApplication.run(EmailProcessorApplication.class, args); } }

Ahora definamos un bean que manejará los mensajes SQS entrantes. ¡Spring Cloud AWS nos permite lograr esto con una sola anotación!

 /** * Bean reasonable for polling SQS and processing new emails */ @Component public class EmailSqsListener { @SuppressWarnings("unused") //IntelliJ isn't quite smart enough to recognize methods marked with @SqsListener yet @SqsListener(value = "com-example-ses", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS) //Mark this method as a SQS listener //Since we already set up our region we can use the logical queue name here //Spring will automatically delete messages if this method executes successfully public void consumeSqsMessage(@Headers Map<String, String> headers, //Map of headers returned when requesting a message from SQS //This map will include things like the relieved time, count and message ID @NotificationMessage String rawJsonMessage //JSON string representation of our payload //Spring Cloud AWS supports marshaling here as well //For the sake of simplicity we will work with incoming messages as a JSON object ) throws Exception{ //com.amazonaws.util.json.JSONObject included with the AWS SDK JSONObject jsonSqsMessage = new JSONObject(rawJsonMessage); } }

La magia aquí radica en la anotación @SqsListener. Con esto, Spring configurará un Ejecutor y comenzará a sondear SQS por nosotros. Cada vez que se encuentre un nuevo mensaje, se invocará nuestro método anotado con el contenido del mensaje. Opcionalmente, Spring Cloud se puede configurar para ordenar los mensajes entrantes, lo que le brinda la capacidad de trabajar con objetos tipificados fuertes dentro de su oyente de cola. Además, tiene la capacidad de inyectar un solo encabezado o un mapa de todos los encabezados devueltos por la llamada de AWS subyacente.

Podemos usar el nombre de la cola lógica aquí ya que previamente definimos la región en aws-config.xml, si quisiéramos omitir eso, podríamos reemplazar el valor con nuestra URL de SQS completamente calificada. También estamos definiendo una política de eliminación, esto configurará Spring para eliminar el mensaje entrante de SQS si se cumple su condición. Hay varias políticas definidas en SqsMessageDeletionPolicy, estamos configurando Spring para eliminar nuestro mensaje si nuestro método consumeSqsMessage se ejecuta correctamente.

También estamos inyectando los encabezados SQS devueltos en nuestro método usando @Headers, y el mapa inyectado contendrá metadatos relacionados con la cola y la carga útil recibida. El cuerpo del mensaje se inyecta usando @NotificationMessage. Spring admite la clasificación utilizando Jackson o mediante un convertidor de cuerpo de mensaje personalizado. En aras de la comodidad, solo inyectaremos la cadena JSON sin formato y trabajaremos con ella mediante la clase JSONObject incluida con el SDK de AWS.

La carga útil recuperada de SQS contendrá una gran cantidad de datos. Eche un vistazo a JSONObject para familiarizarse con la carga útil devuelta. Nuestra carga útil contiene datos de todos los servicios de AWS por los que pasó, SES, SNS y, finalmente, SQS. Por el bien de este tutorial, solo nos importan dos cosas: la lista de direcciones de correo electrónico a las que se envió y el cuerpo del correo electrónico. Comencemos analizando los correos electrónicos.

 //Pull out the array containing all email addresses this was sent to JSONArray emailAddressArray = jsonSqsMessage.getJSONObject("mail").getJSONArray("destination"); for(int i = 0 ; i < emailAddressArray.length() ; i++){ String emailAddress = emailAddressArray.getString(i); }

Dado que en el mundo real, nuestro servicio de asistencia puede incluir más que solo el remitente original en su respuesta, vamos a querer verificar la dirección antes de analizar la identificación del usuario. Esto le dará a nuestra mesa de soporte tanto la capacidad de enviar mensajes a múltiples usuarios al mismo tiempo como la capacidad de incluir usuarios que no sean de la aplicación.

Volvamos a nuestra interfaz UserEmailBean y agreguemos otro método.

 /** * Returns true if the input email address matches our template * @param emailAddress Email to check * @return true if it matches */ boolean emailMatchesUserFormat(String emailAddress);

En UserEmailBeanJSONImpl, para implementar este método vamos a querer hacer dos cosas. Primero, verifique si la dirección termina con nuestro EMAIL_DOMAIN, luego verifique si podemos ordenarla.

 @Override public boolean emailMatchesUserFormat(String emailAddress) { //not our address, return right away if(!emailAddress.endsWith("@" + EMAIL_DOMAIN)){ return false; } //We just care about the email part, not the domain part String emailPart = splitEmail(emailAddress); try { //Attempt to decode our email UserDetails userDetails = objectMapper.readValue(Base64.getDecoder().decode(emailPart), UserDetails.class); //We assume this email matches if the address is successfully decoded and marshaled return userDetails != null && userDetails.getUserID() != null; } catch (IllegalArgumentException | IOException e) { //The Base64 decoder will throw an IllegalArgumentException it the input string is not Base64 formatted //Jackson will throw an IOException if it can't read the string into the UserDetails class return false; } } /** * Splits an email address on @ * Returns everything before the @ * @param emailAddress Address to split * @return all parts before @. If no @ is found, the entire address will be returned */ private static String splitEmail(String emailAddress){ if(!emailAddress.contains("@")){ return emailAddress; } return emailAddress.substring(0, emailAddress.indexOf("@")); }

Definimos dos métodos nuevos, emailMatchesUserFormat que acabamos de agregar a nuestra interfaz, y un método de utilidad simple para dividir una dirección de correo electrónico en @. Nuestra implementación de emailMatchesUserFormat funciona al intentar decodificar en Base64 y ordenar la parte de la dirección nuevamente en nuestra clase de ayuda UserDetails. Si esto tiene éxito, verificamos para asegurarnos de que se complete el ID de usuario requerido. Si todo esto funciona, podemos asumir con seguridad una coincidencia.

Regrese a nuestro EmailSqsListener e inyecte el UserEmailBean recién actualizado.

 private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }

Ahora vamos a actualizar el método consumaSqsMethod. Primero analicemos el cuerpo del correo electrónico:

 //Pull our content, remember the content will be Base64 encoded as per our SES settings String encodedContent = jsonSqsMessage.getString("content"); //Create a new String after decoding our body String decodedBody = new String( Base64.getDecoder().decode(encodedContent.getBytes()) ); for(int i = 0 ; i < emailAddressArray.length() ; i++){ String emailAddress = emailAddressArray.getString(i); }

Ahora vamos a crear un nuevo método que procesará la dirección de correo electrónico y el cuerpo del correo electrónico.

 private void processEmail(String emailAddress, String emailBody){ }

Y finalmente, actualice el bucle de correo electrónico para invocar este método si encuentra una coincidencia.

 //Loop over all sent to addresses for(int i = 0 ; i < emailAddressArray.length() ; i++){ String emailAddress = emailAddressArray.getString(i); //If we find a match, process the email and method if(userEmailBean.emailMatchesUserFormat(emailAddress)){ processEmail(emailAddress, decodedBody); } }

Antes de implementar processEmail, debemos agregar un método más a nuestro UserEmailBean. Necesitamos un método para devolver el ID de usuario de un correo electrónico. Regrese a la interfaz UserEmailBean para agregar su último método.

 /** * Returns the userID from a formatted email address. * Returns null if no userID is found. * @param emailAddress Formatted email address, this address should be verified using {@link #emailMatchesUserFormat(String)} * @return The originating userID if found, null if not */ Long userIDFromEmail(String emailAddress);

El objetivo de este método será devolver el ID de usuario de una dirección formateada. La implementación será similar a nuestro método de verificación. Vayamos a UserEmailBeanJSONImpl y completemos este método.

 @Override public Long userIDFromEmail(String emailAddress) { String emailPart = splitEmail(emailAddress); try { //Attempt to decode our email UserDetails userDetails = objectMapper.readValue(Base64.getDecoder().decode(emailPart), UserDetails.class); if(userDetails == null || userDetails.getUserID() == null){ //We couldn't find a userID return null; } //ID found, return it return userDetails.getUserID(); } catch (IllegalArgumentException | IOException e) { //The Base64 decoder will throw an IllegalArgumentException it the input string is not Base64 formatted //Jackson will throw an IOException if it can't read the string into the UserDetails class //Return null since we didn't find a userID return null; } }

Ahora regrese a nuestro EmailSqsListener y actualice processEmail para usar este nuevo método.

 private void processEmail(String emailAddress, String emailBody){ //Parse out the email address Long userID = userEmailBean.userIDFromEmail(emailAddress); if(userID == null){ //Whoops, we couldn't find a userID. Abort! return; } }

¡Genial! Ahora tenemos casi todo lo que necesitamos. Lo último que debemos hacer es analizar la respuesta del mensaje sin procesar.

Los clientes de correo electrónico, al igual que los navegadores web de hace unos años, están plagados de inconsistencias en sus implementaciones.

Analizar las respuestas de los correos electrónicos es en realidad una tarea bastante complicada. Los formatos de los mensajes de correo electrónico no están estandarizados y las variaciones entre los diferentes clientes de correo electrónico pueden ser enormes. La respuesta sin procesar también incluirá mucho más que la respuesta y una firma. Lo más probable es que también se incluya el mensaje original. La gente inteligente de Mailgun elaboró ​​una excelente publicación de blog que explica algunos de los desafíos. También abren su enfoque basado en el aprendizaje automático para analizar correos electrónicos, échale un vistazo aquí.

La biblioteca de Mailgun está escrita en Python, por lo que para nuestro tutorial usaremos una solución basada en Java más simple. El usuario de GitHub, edlio, armó un analizador de correo electrónico con licencia de MIT en Java basado en una de las bibliotecas de GitHub. Vamos a utilizar esta gran biblioteca.

Primero actualicemos nuestro pom, vamos a usar https://jitpack.io para extraer EmailReplyParser.

 <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>

Ahora agregue la dependencia de GitHub.

 <dependency> <groupId>com.github.edlio</groupId> <artifactId>EmailReplyParser</artifactId> <version>v1.0</version> </dependency>

También vamos a utilizar el correo electrónico común de Apache. Vamos a tener que analizar el correo electrónico sin formato en un MimeMessage javax.mail antes de pasarlo al EmailReplyParser. Agregue la dependencia de bienes comunes.

 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>

Ahora podemos regresar a nuestro EmailSqsListener y finalizar el proceso de correo electrónico. En este punto, tenemos el ID de usuario de origen y el cuerpo del correo electrónico sin procesar. Lo único que queda por hacer es analizar la respuesta.

Para lograr esto, vamos a utilizar una combinación de javax.mail y EmailReplyParser de edlio.

 private void processEmail(String emailAddress, String emailBody) throws Exception { //Parse out the email address Long userID = userEmailBean.userIDFromEmail(emailAddress); if(userID == null){ //Whoops, we couldn't find a userID. Abort! return; } //Default javax.mail session Session session = Session.getDefaultInstance(new Properties()); //Create a new mimeMessage out of the raw email body MimeMessage mimeMessage = MimeMessageUtils.createMimeMessage( session, emailBody ); MimeMessageParser mimeMessageParser = new MimeMessageParser(mimeMessage); //Parse the message mimeMessageParser.parse(); //Parse out the reply for our message String replyText = EmailReplyParser.parseReply(mimeMessageParser.getPlainContent()); //Now we're done! //We have both the userID and the response! System.out.println("Processed reply for userID: " + userID + ". Reply: " + replyText); }

Envolver

¡Y eso es! ¡Ahora tenemos todo lo que necesitamos para entregar una respuesta al usuario original!

¿Ver? ¡Te dije que el correo electrónico puede ser divertido!

En este artículo, vimos cómo se pueden usar los servicios web de Amazon para orquestar canalizaciones complejas. Aunque en este artículo, la canalización se diseñó en torno a los correos electrónicos; estas mismas herramientas se pueden aprovechar para diseñar sistemas aún más complejos, donde no tiene que preocuparse por mantener la infraestructura y puede concentrarse en los aspectos divertidos de la ingeniería de software.

Relacionado: Aumente su productividad con Amazon Web Services