Une nouvelle façon d'utiliser les e-mails pour les applications d'assistance : un didacticiel AWS

Publié: 2022-03-11

Le courrier électronique n'est peut-être pas aussi cool que d'autres plateformes de communication, mais travailler avec lui peut toujours être amusant. J'ai récemment été chargé d'implémenter la messagerie dans une application mobile. Le seul problème était que la communication réelle devait se faire par courrier électronique. Nous voulions que les utilisateurs de l'application puissent communiquer avec une équipe d'assistance comme vous enverriez un SMS. Les membres de l'équipe d'assistance devaient recevoir ces messages par e-mail et devaient également pouvoir répondre à l'utilisateur d'origine. Pour l'utilisateur final, tout ce qui est nécessaire pour ressembler et fonctionner comme n'importe quelle autre application de messagerie moderne.

Dans cet article, nous verrons comment implémenter un service similaire à celui décrit ci-dessus en utilisant Java et une poignée de services Web d'Amazon. Vous aurez besoin d'un compte AWS valide, d'un nom de domaine et d'un accès à votre IDE Java préféré.

Les Infrastructures

Avant d'écrire du code, nous allons configurer les services AWS requis pour le routage et la consommation des e-mails. Nous allons utiliser SES pour envoyer et consommer des e-mails et SNS+SQS pour router les messages entrants.

Consommer des e-mails par programmation à l'aide d'AWS

Revitalisez les e-mails dans les applications d'assistance avec Amazon SES.
Tweeter

Tout commence ici avec SES. Commencez par vous connecter à votre compte AWS et accédez à la console SES.

Avant de commencer, vous aurez besoin d'un nom de domaine vérifié à partir duquel vous pourrez envoyer des e-mails.

Ce sera le domaine à partir duquel les utilisateurs de l'application enverront des e-mails et auquel les membres de l'assistance répondront. La vérification d'un domaine avec SES est un processus simple, et plus d'informations peuvent être trouvées ici.

Si c'est la première fois que vous utilisez SES, ou si vous n'avez pas demandé de limite d'envoi, votre compte sera mis en sandbox. Cela signifie que vous ne pourrez pas envoyer d'e-mails à des adresses qui ne sont pas vérifiées auprès d'AWS. Cela peut provoquer une erreur plus tard dans le didacticiel, lorsque nous envoyons un e-mail à notre service d'assistance fictif. Pour éviter cela, vous pouvez vérifier l'adresse e-mail que vous prévoyez d'utiliser comme service d'assistance dans la console SES dans l'onglet Adresses e-mail.

Une fois que vous avez un domaine vérifié, nous pouvons créer un ensemble de règles. Accédez à l'onglet Ensembles de règles dans la console SES et créez une nouvelle règle de réception.

La première étape lors de la création d'une règle de réception consiste à définir un destinataire.

Les filtres de destinataires vous permettront de définir quels e-mails SES consommera et comment traiter chaque message entrant. Le destinataire que nous définissons ici doit correspondre au domaine et au modèle d'adresse à partir desquels les messages des utilisateurs de l'application sont envoyés par e-mail. Le cas le plus simple ici serait d'ajouter un destinataire pour le domaine que nous avons précédemment vérifié, dans notre cas example.com . Cela configurera SES pour appliquer notre règle à tous les e-mails envoyés à example.com . (par exemple [email protected], [email protected]).

Pour créer une règle pour l'ensemble de notre domaine, nous ajouterions un destinataire pour example.com .

Il est également possible de faire correspondre les modèles d'adresse. Ceci est utile si vous souhaitez acheminer les messages entrants vers différentes files d'attente SQS.

Supposons que nous ayons la file d'attente A et la file d'attente B. Nous pourrions ajouter deux destinataires : [email protected] et [email protected] . Si nous voulons insérer un message dans la file d'attente A, nous enverrons un e-mail à [email protected]. La partie de ceci correspondra à notre destinataire [email protected] . Tout ce qui se trouve entre + et @ est une donnée utilisateur arbitraire, cela n'affectera pas la correspondance d'adresse de SES. Pour insérer dans la file d'attente B, il suffit de remplacer a par b.

Après avoir défini vos destinataires, l'étape suivante consiste à configurer l'action que SES effectuera après avoir consommé un nouvel e-mail. Nous voulons qu'ils aboutissent à terme dans SQS, mais il n'est actuellement pas possible de passer directement de SES à SQS. Pour combler le fossé, nous devons utiliser SNS. Sélectionnez l'action SNS et créez un nouveau sujet. Nous allons éventuellement configurer cette rubrique pour insérer des messages dans SQS.

Sélectionnez créer un sujet SNS et donnez-lui un nom.

Une fois le sujet créé, nous devons sélectionner un encodage de message. Je vais utiliser Base64 afin de préserver les caractères spéciaux. L'encodage que vous choisissez affectera la façon dont les messages sont décodés lorsque nous les consommons dans notre service.

Une fois la règle définie, il suffit de la nommer.

La prochaine étape consistera à configurer SQS et SNS, pour cela nous devons nous diriger vers la console SQS et créer une nouvelle file d'attente.

Pour garder les choses simples, j'utilise le même nom que notre sujet SNS.

Après avoir défini notre file d'attente, nous allons devoir ajuster sa politique d'accès. Nous voulons seulement accorder à notre sujet SNS l'autorisation d'insérer. Nous pouvons y parvenir en ajoutant une condition qui correspond à notre sujet SNS arn.

Le champ de valeur doit être renseigné avec l'ARN de la rubrique SNS que SES notifie.

Une fois SQS configuré, il est temps de retourner à la console SNS pour configurer votre sujet afin d'insérer des notifications dans votre toute nouvelle file d'attente SQS.

Dans la console SNS, sélectionnez la rubrique que SES notifie. À partir de là, créez un nouvel abonnement. Le protocole d'abonnement doit être Amazon SQS et la destination doit être l'ARN de la file d'attente SQS que vous venez de générer.

Après tout cela, le côté AWS de l'équation devrait être entièrement configuré. Nous pouvons tester notre travail en nous envoyant un e-mail. Envoyez un e-mail au domaine configuré avec SES, puis rendez-vous sur la console SQS et sélectionnez votre file d'attente. Vous devriez pouvoir voir la charge utile contenant votre e-mail.

Service Java pour traiter les e-mails

Maintenant sur la partie amusante! Dans cette section, nous allons créer un microservice simple capable d'envoyer des messages et de traiter les e-mails entrants. La première étape consistera à définir une API qui enverra un e-mail à notre service d'assistance au nom d'un utilisateur.

Une note rapide. Nous allons nous concentrer sur les composants de logique métier de ce service et ne définirons pas de points de terminaison REST ni de couche de persistance.

Pour créer un service Spring, nous allons utiliser Spring Boot et Maven. Nous pouvons utiliser Spring Initializer pour générer un projet pour nous, start.spring.io.

Pour commencer, notre pom.xml devrait ressembler à ceci :

 <?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>

Envoi d'un e-mail d'assistance au nom d'un utilisateur

Tout d'abord, définissons un bean pour envoyer un e-mail à notre service d'assistance au nom d'un utilisateur. Le travail de ce bean sera de traiter un message entrant à partir d'un ID utilisateur et d'envoyer ce message par e-mail à notre adresse e-mail de support prédéfinie.

Commençons par définir une 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); }

Et une implémentation vide :

 @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 } }

Ajoutons également le SDK AWS à notre pom, nous allons utiliser le client SES pour envoyer nos emails :

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

La première chose que nous devons faire est de générer une adresse e-mail à partir de laquelle envoyer le message de notre utilisateur. L'adresse que nous générons jouera un rôle essentiel du côté consommateur de notre service. Il doit contenir suffisamment d'informations pour renvoyer la réponse du service d'assistance à l'utilisateur d'origine.

Pour ce faire, nous allons inclure l'ID utilisateur d'origine dans notre adresse e-mail générée. Pour garder les choses propres, nous allons créer un objet contenant l'ID utilisateur et utiliser sa chaîne JSON encodée en Base64 comme adresse e-mail.

Créons un nouveau bean chargé de transformer un ID utilisateur en adresse 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); }

Commençons notre implémentation en ajoutant les consentements requis et une simple classe interne qui nous aidera à sérialiser notre 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; } } }

La génération de notre adresse e-mail est simple, tout ce que nous avons à faire est de créer un objet UserDetails et Base64 encode la représentation JSON. La version finale de notre méthode createAddressForUserID devrait ressembler à ceci :

 @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; }

Nous pouvons maintenant revenir à SupportBeanSesImpl et le mettre à jour pour utiliser le nouveau bean de messagerie que nous venons de créer.

 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); }

Pour envoyer des e-mails, nous allons utiliser le client AWS SES inclus avec le SDK 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 );

Nous utilisons DefaultAWSCredentialsProviderChain pour gérer les informations d'identification pour nous, cette classe recherchera les informations d'identification AWS telles que définies ici.

Nous allons vers une clé d'accès AWS provisionnée avec un accès à SES et éventuellement à SQS. Pour plus d'informations, consultez la documentation d'Amazon.

La prochaine étape consistera à mettre à jour notre méthode messageSupport pour l'assistance par e-mail à l'aide du kit SDK AWS. Le SDK SES simplifie ce processus. La méthode finie devrait ressembler à ceci :

 @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); }

Pour l'essayer, créez une classe de test et injectez le SupportBean. Assurez-vous que SUPPORT_EMAIL_ADDRESS défini dans SupportBeanSesImpl pointe vers une adresse e-mail que vous possédez. Si votre compte SES est en bac à sable, cette adresse doit également être vérifiée. Les adresses e-mail peuvent être vérifiées dans la console SES sous la section Adresses e-mail.

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

Après avoir exécuté ceci, vous devriez voir un message apparaître dans votre boîte de réception. Mieux encore, répondez au message et vérifiez la file d'attente SQS que nous avons configurée plus tôt. Vous devriez voir une charge utile contenant votre réponse.

Consommer les réponses de SQS

La dernière étape consistera à lire les e-mails de SQS, à analyser le message électronique et à déterminer à quel ID utilisateur la réponse doit être transmise.

Les services de file d'attente de messages comme Amazon SQS jouent un rôle essentiel dans l'architecture orientée services en permettant aux services de communiquer entre eux sans avoir à compromettre la vitesse, la fiabilité ou l'évolutivité.

Pour écouter les nouveaux messages SQS, nous allons utiliser le SDK de messagerie Spring Cloud AWS. Cela nous permettra de configurer un écouteur de messages SQS via des annotations, et ainsi d'éviter pas mal de code passe-partout.

Tout d'abord, les dépendances requises.

Ajoutez la dépendance de messagerie Spring Cloud :

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

Et ajoutez Spring Cloud AWS à votre gestion des dépendances 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>

Actuellement, Spring Cloud AWS ne prend pas en charge la configuration basée sur les annotations, nous allons donc devoir définir un bean XML. Heureusement, nous n'avons pas besoin de beaucoup de configuration, donc notre définition de bean sera assez légère. Le point principal de ce fichier sera d'activer les écouteurs de file d'attente pilotés par annotation, cela nous permettra d'annoter une méthode en tant que SqsListener.

Créez un nouveau fichier XML nommé aws-config.xml dans votre dossier de ressources. Notre définition devrait ressembler à ceci :

 <?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 partie importante de ce fichier est <aws-messaging:annotation-driven-queue-listener /> . Nous définissons également une région par défaut. Ce n'est pas nécessaire, mais cela nous permettra de référencer notre file d'attente SQS par son nom au lieu de son URL. Nous ne définissons aucune information d'identification AWS, en les omettant, Spring utilisera par défaut DefaultAWSCredentialsProviderChain, le même fournisseur que nous avons utilisé précédemment dans notre bean SES. Plus d'informations peuvent être trouvées dans les documents Spring Cloud AWS.

Pour utiliser cette configuration XML dans notre application Spring Boot, nous devons l'importer explicitement. Rendez-vous sur votre classe @SpringBootApplication et importez-la.

 @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); } }

Définissons maintenant un bean qui gérera les messages SQS entrants. Spring Cloud AWS nous permet d'accomplir cela avec une seule annotation !

 /** * 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 magie réside ici dans l'annotation @SqsListener. Avec cela, Spring mettra en place un exécuteur et commencera à interroger SQS pour nous. Chaque fois qu'un nouveau message est trouvé, notre méthode annotée sera invoquée avec le contenu du message. En option, Spring Cloud peut être configuré pour rassembler les messages entrants, vous donnant la possibilité de travailler avec des objets fortement typés dans votre écouteur de file d'attente. De plus, vous avez la possibilité d'injecter un seul en-tête ou une carte de tous les en-têtes renvoyés par l'appel AWS sous-jacent.

Nous pouvons utiliser le nom de la file d'attente logique ici puisque nous avons précédemment défini la région dans aws-config.xml, si nous voulions omettre cela, nous serions en mesure de remplacer la valeur par notre URL SQS complète. Nous définissons également une politique de suppression, cela configurera Spring pour supprimer le message entrant de SQS si sa condition est remplie. Il existe plusieurs politiques définies dans SqsMessageDeletionPolicy, nous configurons Spring pour supprimer notre message si notre méthode consommeSqsMessage s'exécute avec succès.

Nous injectons également les en-têtes SQS renvoyés dans notre méthode à l'aide de @Headers, et la carte injectée contiendra des métadonnées liées à la file d'attente et à la charge utile reçues. Le corps du message est injecté à l'aide de @NotificationMessage. Spring prend en charge le marshalling à l'aide de Jackson ou via un convertisseur de corps de message personnalisé. Par souci de commodité, nous allons simplement injecter la chaîne JSON brute et l'utiliser à l'aide de la classe JSONObject incluse avec le kit AWS SDK.

La charge utile extraite de SQS contiendra beaucoup de données. Jetez un œil au JSONObject pour vous familiariser avec la charge utile renvoyée. Notre charge utile contient des données de chaque service AWS par lequel elle a été transmise, SES, SNS et enfin SQS. Pour les besoins de ce didacticiel, nous ne nous soucions vraiment que de deux choses : la liste des adresses e-mail auxquelles cela a été envoyé et le corps de l'e-mail. Commençons par analyser les 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); }

Étant donné que dans le monde réel, notre service d'assistance peut inclure plus que l'expéditeur d'origine dans sa réponse, nous allons vouloir vérifier l'adresse avant d'analyser l'ID utilisateur. Cela donnera à notre service d'assistance à la fois la possibilité d'envoyer des messages à plusieurs utilisateurs en même temps ainsi que la possibilité d'inclure des utilisateurs non applicatifs.

Revenons à notre interface UserEmailBean et ajoutons une autre méthode.

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

Dans UserEmailBeanJSONImpl, pour implémenter cette méthode, nous allons vouloir faire deux choses. Tout d'abord, vérifiez si l'adresse se termine par notre EMAIL_DOMAIN, puis vérifiez si nous pouvons la rassembler.

 @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("@")); }

Nous avons défini deux nouvelles méthodes, emailMatchesUserFormat que nous venons d'ajouter à notre interface, et une méthode utilitaire simple pour scinder une adresse email sur le @. Notre implémentation emailMatchesUserFormat fonctionne en tentant de décoder Base64 et de rassembler la partie adresse dans notre classe d'assistance UserDetails. Si cela réussit, nous vérifions ensuite que l'ID utilisateur requis est renseigné. Si tout cela fonctionne, nous pouvons supposer en toute sécurité un match.

Revenez à notre EmailSqsListener et injectez le UserEmailBean fraîchement mis à jour.

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

Nous allons maintenant mettre à jour le consommeSqsMethod. Commençons par analyser le corps de l'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); }

Créons maintenant une nouvelle méthode qui traitera l'adresse e-mail et le corps de l'e-mail.

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

Et enfin, mettez à jour la boucle de messagerie pour invoquer cette méthode si elle trouve une correspondance.

 //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); } }

Avant d'implémenter processEmail, nous devons ajouter une méthode supplémentaire à notre UserEmailBean. Nous avons besoin d'une méthode pour renvoyer l'ID utilisateur à partir d'un e-mail. Revenez à l'interface UserEmailBean pour ajouter sa dernière méthode.

 /** * 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);

Le but de cette méthode sera de retourner l'userID à partir d'une adresse formatée. La mise en œuvre sera similaire à notre méthode de vérification. Passons à UserEmailBeanJSONImpl et remplissons cette méthode.

 @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; } }

Revenez maintenant à notre EmailSqsListener et mettez à jour processEmail pour utiliser cette nouvelle méthode.

 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; } }

Génial! Maintenant, nous avons presque tout ce dont nous avons besoin. La dernière chose que nous devons faire est d'analyser la réponse du message brut.

Les clients de messagerie, tout comme les navigateurs Web d'il y a quelques années, sont en proie à des incohérences dans leurs implémentations.

Analyser les réponses des e-mails est en fait une tâche assez compliquée. Les formats des messages électroniques ne sont pas standardisés et les variations entre les différents clients de messagerie peuvent être énormes. La réponse brute va également inclure bien plus que la réponse et une signature. Le message d'origine sera très probablement inclus également. Les gens intelligents de Mailgun ont rédigé un excellent article de blog expliquant certains des défis. Ils ont également ouvert leur approche basée sur l'apprentissage automatique pour analyser les e-mails, consultez-la ici.

La bibliothèque Mailgun est écrite en Python, donc pour notre tutoriel, nous allons utiliser une solution basée sur Java plus simple. L'utilisateur de GitHub, edlio, a créé un analyseur de courrier électronique sous licence MIT en Java basé sur l'une des bibliothèques de GitHub. Nous allons utiliser cette grande bibliothèque.

Commençons par mettre à jour notre pom, nous allons utiliser https://jitpack.io pour extraire EmailReplyParser.

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

Ajoutez maintenant la dépendance GitHub.

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

Nous allons également utiliser le courrier électronique Apache Commons. Nous allons devoir analyser l'e-mail brut dans un MimeMessage javax.mail avant de le transmettre à EmailReplyParser. Ajoutez la dépendance commons.

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

Nous pouvons maintenant retourner à notre EmailSqsListener et terminer le processusEmail. À ce stade, nous avons l'ID utilisateur d'origine et le corps de l'e-mail brut. La seule chose qui reste à faire est d'analyser la réponse.

Pour ce faire, nous allons utiliser une combinaison de javax.mail et EmailReplyParser d'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); }

Emballer

Et c'est tout! Nous avons maintenant tout ce dont nous avons besoin pour fournir une réponse à l'utilisateur d'origine !

Voir? Je vous ai dit que le courrier électronique peut être amusant !

Dans cet article, nous avons vu comment Amazon Web Services peut être utilisé pour orchestrer des pipelines complexes. Bien que dans cet article, le pipeline ait été conçu autour des e-mails ; ces mêmes outils peuvent être exploités pour concevoir des systèmes encore plus complexes, où vous n'avez pas à vous soucier de la maintenance de l'infrastructure et pouvez vous concentrer sur les aspects amusants de l'ingénierie logicielle.

En relation: Boostez votre productivité avec Amazon Web Services