Uma nova maneira de usar e-mail para aplicativos de suporte: um tutorial da AWS
Publicados: 2022-03-11O e-mail pode não ser tão legal quanto outras plataformas de comunicação, mas trabalhar com ele ainda pode ser divertido. Recentemente, fui encarregado de implementar mensagens em um aplicativo móvel. O único problema era que a comunicação real precisava ser por e-mail. Queríamos que os usuários do aplicativo pudessem se comunicar com uma equipe de suporte da mesma forma que você enviaria uma mensagem de texto. Os membros da equipe de suporte precisavam receber essas mensagens por e-mail e também precisavam responder ao usuário de origem. Para o usuário final, tudo precisava parecer e funcionar como qualquer outro aplicativo de mensagens moderno.
Neste artigo, veremos como implementar um serviço semelhante ao descrito acima usando Java e alguns serviços da Web da Amazon. Você precisará de uma conta válida da AWS, um nome de domínio e acesso ao seu IDE Java favorito.
A Infraestrutura
Antes de escrevermos qualquer código, vamos configurar os serviços necessários da AWS para roteamento e consumo de e-mail. Vamos usar SES para enviar e consumir e-mails e SNS+SQS para rotear mensagens recebidas.
Tudo começa aqui com SES. Comece fazendo login em sua conta da AWS e navegando até o console do SES.
Antes de começarmos, você precisará de um nome de domínio verificado do qual possa enviar e-mails.
Este será o aplicativo de domínio que os usuários enviarão mensagens de e-mail e os membros do suporte responderão. A verificação de um domínio com o SES é um processo simples, e mais informações podem ser encontradas aqui.
Se esta é a primeira vez que você está usando o SES ou se não solicitou um limite de envio, sua conta será colocada em sandbox. Isso significa que você não poderá enviar e-mails para endereços que não foram verificados com a AWS. Isso pode causar um erro posteriormente no tutorial, quando enviarmos um e-mail para nosso suporte técnico fictício. Para evitar isso, você pode verificar o endereço de e-mail que planeja usar como suporte técnico no console SES na guia Endereços de e-mail.
Depois de ter um domínio verificado, podemos criar um conjunto de regras. Navegue até a guia Conjuntos de regras no console SES e crie uma nova Regra de recebimento.
A primeira etapa ao criar uma regra de recebimento será definir um destinatário.
Os filtros de destinatários permitirão definir quais emails o SES consumirá e como processar cada mensagem recebida. O destinatário que definimos aqui precisa corresponder ao domínio e padrão de endereço do qual as mensagens do usuário do aplicativo são enviadas por e-mail. O caso mais simples aqui seria adicionar um destinatário para o domínio que verificamos anteriormente, em nosso caso example.com . Isso configurará o SES para aplicar nossa regra a todos os emails enviados para example.com . (por exemplo, [email protected], [email protected]).
Para criar uma regra para todo o nosso domínio, adicionaríamos um destinatário para example.com .
Também é possível combinar padrões de endereço. Isso é útil se você deseja rotear mensagens de entrada para diferentes filas do SQS.
Digamos que temos a fila A e a fila B. Poderíamos adicionar dois destinatários: [email protected] e [email protected] . Se quisermos inserir uma mensagem na fila A, enviaremos um e-mail para [email protected]. A parte disso corresponderá ao nosso destinatário [email protected] . Tudo entre o + e @ são dados de usuário arbitrários, não afetará a correspondência de endereços do SES. Para inserir na fila B, basta substituir a por b.
Depois de definir seus destinatários, a próxima etapa é configurar a ação que o SES executará após consumir um novo email. Eventualmente, queremos que eles acabem no SQS, mas atualmente não é possível ir diretamente do SES para o SQS. Para preencher a lacuna, precisamos usar o SNS. Selecione a ação SNS e crie um novo tópico. Eventualmente, configuraremos este tópico para inserir mensagens no SQS.
Selecione criar tópico SNS e dê um nome a ele.
Após a criação do tópico, precisamos selecionar uma codificação de mensagem. Vou usar Base64 para preservar caracteres especiais. A codificação que você escolher afetará como as mensagens são decodificadas quando as consumimos em nosso serviço.
Uma vez que a regra é definida, só precisamos nomeá-la.
O próximo passo será configurar o SQS e o SNS, para isso precisamos ir até o console do SQS e criar uma nova fila.
Para manter as coisas simples, estou usando o mesmo nome do nosso tópico SNS.
Após definirmos nossa fila, precisaremos ajustar sua política de acesso. Queremos apenas conceder permissão ao nosso tópico SNS para inserir. Podemos conseguir isso adicionando uma condição que corresponda ao nosso tópico SNS arn.
O campo de valor deve ser preenchido com o ARN para o tópico do SNS que o SES está notificando.
Depois que o SQS estiver configurado, é hora de mais uma viagem de volta ao console do SNS para configurar seu tópico para inserir notificações em sua nova e brilhante fila do SQS.
No console do SNS, selecione o tópico que o SES está notificando. A partir daí, crie uma nova assinatura. O protocolo de assinatura deve ser Amazon SQS e o destino deve ser o ARN da fila SQS que você acabou de gerar.
Depois de tudo isso, o lado AWS da equação deve estar todo configurado. Podemos testar nosso trabalho enviando um e-mail para nós mesmos. Envie um e-mail para o domínio configurado com SES, depois vá para o console SQS e selecione sua fila. Você deve conseguir ver a carga que contém seu e-mail.
Serviço Java para lidar com e-mails
Agora vamos para a parte divertida! Nesta seção, vamos criar um microsserviço simples capaz de enviar mensagens e processar e-mails recebidos. A primeira etapa será definir uma API que enviará um e-mail para nossa central de suporte em nome de um usuário.
Uma nota rápida. Vamos nos concentrar nos componentes de lógica de negócios desse serviço e não definiremos endpoints REST ou uma camada de persistência.
Para construir um serviço Spring, vamos usar Spring Boot e Maven. Podemos usar o Spring Initializer para gerar um projeto para nós, start.spring.io.
Para começar, nosso pom.xml deve ficar assim:
<?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>
Enviando suporte por e-mail em nome de um usuário
Primeiro, vamos definir um bean para enviar um e-mail à nossa central de suporte em nome de um usuário. O trabalho desse bean será processar uma mensagem recebida de um ID de usuário e enviar essa mensagem por e-mail para nosso endereço de e-mail de suporte predefinido.
Vamos começar definindo uma interface.
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); }
E uma implementação vazia:
@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 } }
Vamos também adicionar o AWS SDK ao nosso pom, vamos usar o cliente SES para enviar nossos emails:
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>
A primeira coisa que precisamos fazer é gerar um endereço de e-mail para enviar a mensagem do nosso usuário. O endereço que geramos desempenhará um papel crítico no lado consumidor de nosso serviço. Ele precisa conter informações suficientes para encaminhar a resposta do suporte técnico de volta ao usuário de origem.
Para conseguir isso, vamos incluir o ID do usuário de origem em nosso endereço de e-mail gerado. Para manter as coisas limpas, vamos criar um objeto contendo o ID do usuário e usar a string JSON codificada em Base64 como endereço de e-mail.
Vamos criar um novo bean responsável por transformar um ID de usuário em um endereço de e-mail.
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); }
Vamos começar nossa implementação adicionando os consentimentos necessários e uma classe interna simples que nos ajudará a serializar nosso 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; } } }
Gerar nosso endereço de e-mail é simples, tudo o que precisamos fazer é criar um objeto UserDetails e Base64 codificar a representação JSON. A versão final do nosso método createAddressForUserID deve se parecer com isto:
@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; }
Agora podemos voltar ao SupportBeanSesImpl e atualizá-lo para usar o novo bean de email que acabamos de criar.
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 e-mails, usaremos o cliente AWS SES incluído no AWS SDK.
/** * 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 o DefaultAWSCredentialsProviderChain para gerenciar credenciais para nós, essa classe pesquisará as credenciais da AWS conforme definido aqui.
Estamos indo para uma chave de acesso da AWS provisionada com acesso ao SES e, eventualmente, ao SQS. Para mais informações, confira a documentação da Amazon.
A próxima etapa será atualizar nosso método messageSupport para o suporte por e-mail usando o AWS SDK. O SES SDK torna isso um processo direto. O método finalizado deve se parecer com isto:
@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 experimentar, crie uma classe de teste e injete o SupportBean. Certifique-se de que SUPPORT_EMAIL_ADDRESS definido em SupportBeanSesImpl aponte para um endereço de e-mail que você possui. Se sua conta SES estiver em área restrita, esse endereço também precisará ser verificado. Os endereços de e-mail podem ser verificados no console SES na seção Endereços de e-mail.
@Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }
Depois de executar isso, você deverá ver uma mensagem na sua caixa de entrada. Melhor ainda, responda à mensagem e verifique a fila SQS que configuramos anteriormente. Você deverá ver um payload contendo sua resposta.
Consumindo respostas do SQS
A última etapa será ler os e-mails do SQS, analisar a mensagem de e-mail e descobrir a qual ID de usuário a resposta deve ser encaminhada pertence.
Para ouvir novas mensagens SQS, usaremos o SDK de mensagens Spring Cloud AWS. Isso nos permitirá configurar um ouvinte de mensagem SQS por meio de anotações e, assim, evitar um pouco de código clichê.

Primeiro, as dependências necessárias.
Adicione a dependência de mensagens do Spring Cloud:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>
E adicione o Spring Cloud AWS ao seu gerenciamento de dependências 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>
Atualmente, o Spring Cloud AWS não oferece suporte à configuração orientada por anotação, portanto, teremos que definir um bean XML. Felizmente não precisamos de muita configuração, então nossa definição de bean será bem leve. O ponto principal deste arquivo será habilitar os listeners de fila acionados por anotação, isso nos permitirá anotar um método como um SqsListener.
Crie um novo arquivo XML chamado aws-config.xml em sua pasta de recursos. Nossa definição deve ser algo assim:
<?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>
A parte importante deste arquivo é <aws-messaging:annotation-driven-queue-listener />
. Também estamos definindo uma região padrão. Isso não é necessário, mas isso nos permitirá referenciar nossa fila SQS por nome em vez de URL. Não estamos definindo nenhuma credencial da AWS, omitindo-as, o Spring assumirá como padrão DefaultAWSCredentialsProviderChain, o mesmo provedor que usamos anteriormente em nosso bean SES. Mais informações podem ser encontradas nos documentos do Spring Cloud AWS.
Para usar essa configuração XML em nosso aplicativo Spring Boot, precisamos importá-la explicitamente. Vá até sua classe @SpringBootApplication e importe-a.
@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); } }
Agora vamos definir um bean que manipulará as mensagens SQS recebidas. O Spring Cloud AWS nos permite fazer isso com uma única anotação!
/** * 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); } }
A mágica aqui está na anotação @SqsListener. Com isso, o Spring configurará um Executor e começará a pesquisar SQS para nós. Toda vez que uma nova mensagem é encontrada, nosso método anotado será invocado com o conteúdo da mensagem. Opcionalmente, o Spring Cloud pode ser configurado para empacotar mensagens recebidas, dando a você a capacidade de trabalhar com objetos de tipo forte dentro de seu ouvinte de fila. Além disso, você pode injetar um único cabeçalho ou um mapa de todos os cabeçalhos retornados da chamada subjacente da AWS.
Podemos usar o nome da fila lógica aqui, pois definimos anteriormente a região em aws-config.xml, se quiséssemos omitir que poderíamos substituir o valor por nossa URL SQS totalmente qualificada. Também estamos definindo uma política de exclusão, isso configurará o Spring para excluir a mensagem recebida do SQS se sua condição for atendida. Existem várias políticas definidas em SqsMessageDeletionPolicy, estamos configurando o Spring para excluir nossa mensagem se nosso método consumerSqsMessage for executado com sucesso.
Também estamos injetando os cabeçalhos SQS retornados em nosso método usando @Headers, e o mapa injetado conterá metadados relacionados à fila e à carga recebida. O corpo da mensagem é injetado usando @NotificationMessage. O Spring suporta empacotamento utilizando Jackson ou por meio de um conversor de corpo de mensagem personalizado. Por conveniência, vamos apenas injetar a string JSON bruta e trabalhar com ela usando a classe JSONObject incluída no AWS SDK.
A carga recuperada do SQS conterá muitos dados. Dê uma olhada no JSONObject para se familiarizar com a carga retornada. Nossa carga útil contém dados de todos os serviços da AWS pelos quais passou, SES, SNS e, finalmente, SQS. Por causa deste tutorial, nós realmente só nos importamos com duas coisas: a lista de endereços de e-mail para os quais isso foi enviado e o corpo do e-mail. Vamos começar analisando os e-mails.
//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); }
Como no mundo real, nosso helpdesk pode incluir mais do que apenas o remetente original em sua resposta, vamos querer verificar o endereço antes de analisar o ID do usuário. Isso dará à nossa central de suporte a capacidade de enviar mensagens a vários usuários ao mesmo tempo, bem como a capacidade de incluir usuários que não são do aplicativo.
Vamos voltar para nossa interface UserEmailBean e adicionar outro 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);
Em UserEmailBeanJSONImpl, para implementar este método vamos querer fazer duas coisas. Primeiro, verifique se o endereço termina com nosso EMAIL_DOMAIN e, em seguida, verifique se podemos organizá-lo.
@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 dois novos métodos, emailMatchesUserFormat, que acabamos de adicionar à nossa interface, e um método utilitário simples para dividir um endereço de e-mail no @. Nossa implementação emailMatchesUserFormat funciona tentando decodificar Base64 e empacotar a parte do endereço de volta em nossa classe auxiliar UserDetails. Se isso for bem-sucedido, verificamos se o ID do usuário necessário foi preenchido. Se tudo isso der certo, podemos assumir com segurança uma correspondência.
Volte para o nosso EmailSqsListener e injete o UserEmailBean recém-atualizado.
private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }
Agora vamos atualizar o consumerSqsMethod. Primeiro vamos analisar o corpo do e-mail:
//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); }
Agora vamos criar um novo método que processará o endereço de e-mail e o corpo do e-mail.
private void processEmail(String emailAddress, String emailBody){ }
E, finalmente, atualize o loop de email para invocar esse método se encontrar uma correspondência.
//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 implementarmos processEmail, precisamos adicionar mais um método ao nosso UserEmailBean. Precisamos de um método para retornar o ID do usuário de um email. Volte para a interface UserEmailBean para adicionar seu ú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);
O objetivo desse método será retornar o ID do usuário de um endereço formatado. A implementação será semelhante ao nosso método de verificação. Vamos para UserEmailBeanJSONImpl e preencha 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; } }
Agora volte para o nosso EmailSqsListener e atualize o processEmail para usar esse novo 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; } }
Excelente! Agora temos quase tudo que precisamos. A última coisa que precisamos fazer é analisar a resposta da mensagem bruta.
Analisar respostas de e-mails é realmente uma tarefa bastante complicada. Os formatos de mensagens de e-mail não são padronizados e as variações entre diferentes clientes de e-mail podem ser enormes. A resposta bruta também incluirá muito mais do que a resposta e uma assinatura. A mensagem original provavelmente também será incluída. Pessoas inteligentes do Mailgun criaram uma ótima postagem no blog explicando alguns dos desafios. Eles também abriram o código-fonte de sua abordagem baseada em aprendizado de máquina para analisar e-mails, confira aqui.
A biblioteca Mailgun é escrita em Python, portanto, para nosso tutorial, usaremos uma solução baseada em Java mais simples. O usuário do GitHub edlio montou um analisador de e-mail licenciado pelo MIT em Java baseado em uma das bibliotecas do GitHub. Vamos usar esta grande biblioteca.
Primeiro vamos atualizar nosso pom, vamos usar https://jitpack.io para puxar EmailReplyParser.
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
Agora adicione a dependência do GitHub.
<dependency> <groupId>com.github.edlio</groupId> <artifactId>EmailReplyParser</artifactId> <version>v1.0</version> </dependency>
Também vamos usar o e-mail comum do Apache. Vamos precisar analisar o email bruto em um javax.mail MimeMessage antes de passá-lo para o EmailReplyParser. Adicione a dependência de commons.
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
Agora podemos voltar ao nosso EmailSqsListener e finalizar o processEmail. Neste ponto, temos o userID de origem e o corpo do email bruto. A única coisa que resta a fazer é analisar a resposta.
Para fazer isso, vamos usar uma combinação de javax.mail e EmailReplyParser do 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); }
Embrulhar
E é isso! Agora temos tudo o que precisamos para entregar uma resposta ao usuário de origem!
Neste artigo, vimos como o Amazon Web Services pode ser usado para orquestrar pipelines complexos. Embora neste artigo, o pipeline foi projetado em torno de e-mails; essas mesmas ferramentas podem ser aproveitadas para projetar sistemas ainda mais complexos, nos quais você não precisa se preocupar com a manutenção da infraestrutura e pode se concentrar nos aspectos divertidos da engenharia de software.