Un nuovo modo di utilizzare l'e-mail per le app di supporto: un tutorial AWS
Pubblicato: 2022-03-11L'e-mail potrebbe non essere interessante come altre piattaforme di comunicazione, ma lavorare con essa può comunque essere divertente. Recentemente sono stato incaricato di implementare la messaggistica in un'app mobile. L'unico problema era che la comunicazione effettiva doveva avvenire tramite e-mail. Volevamo che gli utenti dell'app potessero comunicare con un team di supporto proprio come faresti con un messaggio di testo. I membri del team di supporto dovevano ricevere questi messaggi via e-mail e dovevano anche essere in grado di rispondere all'utente di origine. Per l'utente finale, tutto il necessario per apparire e funzionare come qualsiasi altra moderna app di messaggistica.
In questo articolo, daremo un'occhiata a come implementare un servizio simile a quello sopra descritto utilizzando Java e una manciata di servizi web di Amazon. Avrai bisogno di un account AWS valido, un nome di dominio e l'accesso al tuo IDE Java preferito.
L'infrastruttura
Prima di scrivere qualsiasi codice, configureremo i servizi AWS necessari per l'instradamento e l'utilizzo della posta elettronica. Utilizzeremo SES per inviare e consumare e-mail e SNS+SQS per instradare i messaggi in arrivo.
Tutto inizia qui con SES. Inizia accedendo al tuo account AWS e navigando nella console SES.
Prima di iniziare, avrai bisogno di un nome di dominio verificato da cui puoi inviare e-mail.
Questa sarà l'app di dominio da cui gli utenti invieranno messaggi di posta elettronica ea cui risponderanno i membri dell'assistenza. La verifica di un dominio con SES è un processo semplice e maggiori informazioni sono disponibili qui.
Se è la prima volta che utilizzi SES, o non hai richiesto un limite di invio, il tuo account sarà in modalità sandbox. Ciò significa che non sarai in grado di inviare e-mail a indirizzi che non sono stati verificati con AWS. Ciò potrebbe causare un errore più avanti nel tutorial, quando inviamo un'e-mail al nostro help desk immaginario. Per evitare ciò, puoi verificare l'indirizzo e-mail che intendi utilizzare come help desk nella console SES nella scheda Indirizzi e-mail.
Una volta che hai un dominio verificato, possiamo creare un set di regole. Passare alla scheda Set di regole nella console SES e creare una nuova regola di ricevuta.
Il primo passaggio durante la creazione di una regola di ricezione sarà la definizione di un destinatario.
I filtri dei destinatari ti permetteranno di definire quali email utilizzerà SES e come elaborare ogni messaggio in arrivo. Il destinatario che definiamo qui deve corrispondere al dominio e al modello di indirizzo da cui vengono inviati i messaggi degli utenti dell'app. Il caso più semplice qui sarebbe aggiungere un destinatario per il dominio che abbiamo verificato in precedenza, nel nostro caso example.com . Questo configurerà SES per applicare la nostra regola a tutte le email inviate a example.com . (es. [email protected], [email protected]).
Per creare una regola per l'intero dominio, aggiungeremo un destinatario per esempio.com .
È anche possibile abbinare i modelli di indirizzo. Ciò è utile se si desidera instradare i messaggi in arrivo a diverse code SQS.
Supponiamo di avere la coda A e la coda B. Potremmo aggiungere due destinatari: [email protected] e [email protected] . Se vogliamo inserire un messaggio nella coda A, invieremo un'e-mail [email protected]. La parte di questo corrisponderà al nostro destinatario [email protected] . Tutto ciò che è compreso tra + e @ è un dato utente arbitrario, non influirà sulla corrispondenza degli indirizzi di SES. Per inserire nella coda B è sufficiente sostituire a con b.
Dopo aver definito i destinatari, il passaggio successivo consiste nel configurare l'azione che SES eseguirà dopo aver consumato una nuova e-mail. Alla fine vogliamo che questi finiscano in SQS, tuttavia al momento non è possibile passare direttamente da SES a SQS. Per colmare il divario, dobbiamo usare SNS. Seleziona l'azione SNS e crea un nuovo argomento. Alla fine configureremo questo argomento per inserire messaggi in SQS.
Seleziona Crea argomento SNS e assegnagli un nome.
Dopo aver creato l'argomento, è necessario selezionare una codifica del messaggio. Userò Base64 per preservare i caratteri speciali. La codifica che scegli influenzerà il modo in cui i messaggi vengono decodificati quando li consumiamo nel nostro servizio.
Una volta impostata la regola, dobbiamo solo nominarla.
Il passaggio successivo sarà la configurazione di SQS e SNS, per questo dobbiamo andare alla console SQS e creare una nuova coda.
Per semplificare le cose, sto usando lo stesso nome del nostro argomento SNS.
Dopo aver definito la nostra coda, dovremo adattarne la politica di accesso. Vogliamo solo concedere al nostro argomento SNS l'autorizzazione per l'inserimento. Possiamo raggiungere questo obiettivo aggiungendo una condizione che corrisponda al nostro argomento SNS arn.
Il campo del valore deve essere compilato con l'ARN per l'argomento SNS che SES sta notificando.
Dopo aver configurato SQS, è ora di tornare alla console SNS per configurare l'argomento in modo che inserisca le notifiche nella tua nuova coda SQS.
Nella console SNS, seleziona l'argomento che SES sta notificando. Da lì, crea un nuovo abbonamento. Il protocollo di sottoscrizione dovrebbe essere Amazon SQS e la destinazione dovrebbe essere l'ARN della coda SQS appena generata.
Dopo tutto ciò, il lato AWS dell'equazione dovrebbe essere tutto impostato. Possiamo testare il nostro lavoro inviandoci un'e-mail. Invia un'e-mail al dominio configurato con SES, quindi vai alla console SQS e seleziona la tua coda. Dovresti essere in grado di vedere il payload contenente la tua email.
Servizio Java per la gestione delle e-mail
Ora passiamo alla parte divertente! In questa sezione creeremo un semplice microservizio in grado di inviare messaggi ed elaborare le email in arrivo. Il primo passo sarà la definizione di un'API che invierà un'e-mail al nostro supporto tecnico per conto di un utente.
Una breve nota. Ci concentreremo sui componenti della logica aziendale di questo servizio e non definiremo endpoint REST o un livello di persistenza.
Per creare un servizio Spring, utilizzeremo Spring Boot e Maven. Possiamo usare Spring Initializer per generare un progetto per noi, start.spring.io.
Per iniziare, il nostro pom.xml dovrebbe assomigliare a questo:
<?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>
Assistenza tramite e-mail per conto di un utente
Innanzitutto, definiamo un bean per inviare un'e-mail al nostro supporto tecnico per conto di un utente. Il compito di questo bean sarà elaborare un messaggio in arrivo da un ID utente e inviare quel messaggio tramite e-mail al nostro indirizzo e-mail predefinito del supporto.
Iniziamo definendo un'interfaccia.
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 un'implementazione vuota:
@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 } }
Aggiungiamo anche l'SDK AWS al nostro pom, utilizzeremo il client SES per inviare le nostre e-mail:
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>
La prima cosa che dobbiamo fare è generare un indirizzo email da cui inviare il messaggio del nostro utente. L'indirizzo che generiamo giocherà un ruolo fondamentale dal lato del consumo del nostro servizio. Deve contenere informazioni sufficienti per reindirizzare la risposta dell'help desk all'utente di origine.
Per raggiungere questo obiettivo, includeremo l'ID utente di origine nel nostro indirizzo e-mail generato. Per mantenere le cose pulite, creeremo un oggetto contenente l'ID utente e utilizzeremo la stringa JSON codificata Base64 come indirizzo e-mail.
Creiamo un nuovo bean responsabile della trasformazione di un ID utente in un indirizzo email.
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); }
Iniziamo la nostra implementazione aggiungendo i consensi richiesti e una semplice classe interna che ci aiuterà a serializzare il nostro 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 generazione del nostro indirizzo e-mail è semplice, tutto ciò che dobbiamo fare è creare un oggetto UserDetails e codificare Base64 la rappresentazione JSON. La versione finale del nostro metodo createAddressForUserID dovrebbe assomigliare a questa:
@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; }
Ora possiamo tornare a SupportBeanSesImpl e aggiornarlo per utilizzare il nuovo bean di posta elettronica che abbiamo appena creato.
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); }
Per inviare e-mail, utilizzeremo il client AWS SES incluso con l'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 );
Stiamo utilizzando DefaultAWSCredentialsProviderChain per gestire le credenziali per noi, questa classe cercherà le credenziali AWS come definito qui.
Passeremo a una chiave di accesso AWS fornita con accesso a SES ed eventualmente a SQS. Per maggiori informazioni consulta la documentazione di Amazon.
Il passaggio successivo sarà l'aggiornamento del nostro metodo messageSupport al supporto tramite e-mail utilizzando l'SDK AWS. L'SDK SES rende questo un processo semplice. Il metodo finito dovrebbe assomigliare a questo:
@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); }
Per provarlo, crea una classe di test e inietta SupportBean. Assicurati che SUPPORT_EMAIL_ADDRESS definito in SupportBeanSesImpl punti a un indirizzo email di tua proprietà. Se il tuo account SES è in modalità sandbox, anche questo indirizzo deve essere verificato. Gli indirizzi e-mail possono essere verificati nella console SES nella sezione Indirizzi e-mail.
@Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }
Dopo averlo eseguito, dovresti vedere un messaggio nella tua casella di posta. Meglio ancora, rispondi al messaggio e controlla la coda SQS che abbiamo impostato in precedenza. Dovresti vedere un payload contenente la tua risposta.
Consumo di risposte da SQS
L'ultimo passaggio sarà leggere le e-mail da SQS, analizzare il messaggio e-mail e capire a quale ID utente appartiene la risposta da inoltrare.
Per ascoltare i nuovi messaggi SQS, utilizzeremo l'SDK di messaggistica di Spring Cloud AWS. Questo ci consentirà di configurare un listener di messaggi SQS tramite annotazioni, evitando così un bel po' di codice standard.

Innanzitutto, le dipendenze richieste.
Aggiungi la dipendenza di messaggistica di Spring Cloud:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>
E aggiungi Spring Cloud AWS alla gestione delle dipendenze da 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>
Attualmente, Spring Cloud AWS non supporta la configurazione basata su annotazioni, quindi dovremo definire un bean XML. Fortunatamente non abbiamo bisogno di molta configurazione, quindi la nostra definizione di bean sarà piuttosto leggera. Il punto principale di questo file sarà abilitare i listener di coda guidati dall'annotazione, questo ci consentirà di annotare un metodo come SqsListener.
Crea un nuovo file XML denominato aws-config.xml nella cartella delle risorse. La nostra definizione dovrebbe assomigliare a questa:
<?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 di questo file è <aws-messaging:annotation-driven-queue-listener />
. Stiamo anche definendo una regione predefinita. Questo non è necessario, ma così facendo ci consentirà di fare riferimento alla nostra coda SQS per nome anziché per URL. Non stiamo definendo alcuna credenziale AWS, omettendole Spring passerà a DefaultAWSCredentialsProviderChain, lo stesso provider che abbiamo utilizzato in precedenza nel nostro bean SES. Maggiori informazioni possono essere trovate nei documenti di Spring Cloud AWS.
Per utilizzare questa configurazione XML nella nostra app Spring Boot, dobbiamo importarla esplicitamente. Vai alla tua classe @SpringBootApplication e importala.
@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); } }
Ora definiamo un bean che gestirà i messaggi SQS in arrivo. Spring Cloud AWS ci consente di ottenere questo risultato con una singola annotazione!
/** * 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 qui sta nell'annotazione @SqsListener. Con questo, Spring creerà un Executor e inizierà a sondare SQS per noi. Ogni volta che viene trovato un nuovo messaggio, il nostro metodo annotato verrà invocato con il contenuto del messaggio. Facoltativamente, Spring Cloud può essere configurato per effettuare il marshalling dei messaggi in arrivo, offrendoti la possibilità di lavorare con oggetti tipizzati forti all'interno del tuo listener di coda. Inoltre, hai la possibilità di inserire una singola intestazione o una mappa di tutte le intestazioni restituite dalla chiamata AWS sottostante.
Siamo in grado di utilizzare il nome della coda logica qui poiché abbiamo precedentemente definito la regione in aws-config.xml, se volessimo omettere potremmo sostituire il valore con il nostro URL SQS completo. Stiamo anche definendo un criterio di eliminazione, questo configurerà Spring per eliminare il messaggio in arrivo da SQS se la sua condizione è soddisfatta. Esistono più criteri definiti in SqsMessageDeletionPolicy, stiamo configurando Spring per eliminare il nostro messaggio se il nostro metodo consumeSqsMessage viene eseguito correttamente.
Stiamo anche iniettando le intestazioni SQS restituite nel nostro metodo utilizzando @Headers e la mappa iniettata conterrà i metadati relativi alla coda e al payload ricevuto. Il corpo del messaggio viene iniettato utilizzando @NotificationMessage. Spring supporta il marshalling utilizzando Jackson o tramite un convertitore del corpo del messaggio personalizzato. Per comodità, inietteremo semplicemente la stringa JSON grezza e lavoreremo con essa utilizzando la classe JSONObject inclusa con l'SDK AWS.
Il carico utile recuperato da SQS conterrà molti dati. Dai un'occhiata a JSONObject per familiarizzare con il payload restituito. Il nostro payload contiene i dati di ogni servizio AWS attraverso il quale è stato passato, SES, SNS e infine SQS. Per il bene di questo tutorial, ci preoccupiamo solo di due cose: l'elenco di indirizzi e-mail a cui è stato inviato e il corpo dell'e-mail. Iniziamo analizzando le email.
//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); }
Poiché nel mondo reale, il nostro helpdesk può includere più del semplice mittente originale nella sua risposta, vorremo verificare l'indirizzo prima di analizzare l'ID utente. Ciò darà al nostro supporto tecnico sia la possibilità di inviare messaggi a più utenti contemporaneamente, sia la possibilità di includere utenti non app.
Torniamo alla nostra interfaccia UserEmailBean e aggiungiamo un altro metodo.
/** * Returns true if the input email address matches our template * @param emailAddress Email to check * @return true if it matches */ boolean emailMatchesUserFormat(String emailAddress);
In UserEmailBeanJSONImpl, per implementare questo metodo vorremo fare due cose. Innanzitutto, controlla se l'indirizzo termina con il nostro EMAIL_DOMAIN, quindi controlla se possiamo eseguirne il marshalling.
@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("@")); }
Abbiamo definito due nuovi metodi, emailMatchesUserFormat che abbiamo appena aggiunto alla nostra interfaccia e un semplice metodo di utilità per dividere un indirizzo email su @. La nostra implementazione emailMatchesUserFormat funziona tentando di decodificare Base64 e riorganizzare la parte dell'indirizzo nella nostra classe helper UserDetails. Se ciò riesce, controlliamo per assicurarci che l'ID utente richiesto sia compilato. Se tutto questo funziona, possiamo tranquillamente presumere una corrispondenza.
Torna al nostro EmailSqsListener e inietta UserEmailBean appena aggiornato.
private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }
Ora aggiorneremo consumeSqsMethod. Per prima cosa analizziamo il corpo dell'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); }
Ora creiamo un nuovo metodo che elaborerà l'indirizzo e-mail e il corpo dell'e-mail.
private void processEmail(String emailAddress, String emailBody){ }
E infine, aggiorna il ciclo di posta elettronica per invocare questo metodo se trova una corrispondenza.
//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); } }
Prima di implementare processEmail, dobbiamo aggiungere un altro metodo al nostro UserEmailBean. Abbiamo bisogno di un metodo per restituire l'ID utente da un'e-mail. Torna all'interfaccia UserEmailBean per aggiungere il suo ultimo metodo.
/** * 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);
L'obiettivo di questo metodo sarà restituire l'ID utente da un indirizzo formattato. L'implementazione sarà simile al nostro metodo di verifica. Andiamo su UserEmailBeanJSONImpl e riempiamo questo metodo.
@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; } }
Ora torna al nostro EmailSqsListener e aggiorna il processoEmail per utilizzare questo nuovo metodo.
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; } }
Grande! Ora abbiamo quasi tutto ciò di cui abbiamo bisogno. L'ultima cosa che dobbiamo fare è analizzare la risposta dal messaggio non elaborato.
Analizzare le risposte dalle e-mail è in realtà un compito abbastanza complicato. I formati dei messaggi di posta elettronica non sono standardizzati e le variazioni tra i diversi client di posta elettronica possono essere enormi. La risposta grezza includerà anche molto di più della risposta e di una firma. Molto probabilmente verrà incluso anche il messaggio originale. Le persone intelligenti di Mailgun hanno messo insieme un ottimo post sul blog che spiega alcune delle sfide. Hanno anche aperto il loro approccio basato sull'apprendimento automatico per l'analisi delle e-mail, dai un'occhiata qui.
La libreria Mailgun è scritta in Python, quindi per il nostro tutorial utilizzeremo una soluzione basata su Java più semplice. L'utente di GitHub edlio ha messo insieme un parser di posta elettronica con licenza MIT in Java basato su una delle librerie di GitHub. Utilizzeremo questa fantastica libreria.
Per prima cosa aggiorniamo il nostro pom, useremo https://jitpack.io per inserire EmailReplyParser.
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
Ora aggiungi la dipendenza GitHub.
<dependency> <groupId>com.github.edlio</groupId> <artifactId>EmailReplyParser</artifactId> <version>v1.0</version> </dependency>
Utilizzeremo anche l'e-mail di Apache commons. Avremo bisogno di analizzare l'e-mail grezza in un MimeMessage javax.mail prima di passarla a EmailReplyParser. Aggiungi la dipendenza dai commons.
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
Ora possiamo tornare al nostro EmailSqsListener e completare il processoEmail. A questo punto, abbiamo l'ID utente di origine e il corpo dell'e-mail non elaborato. L'unica cosa che resta da fare è analizzare la risposta.
Per ottenere ciò, utilizzeremo una combinazione di javax.mail e EmailReplyParser di 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); }
Incartare
E questo è tutto! Ora abbiamo tutto ciò di cui abbiamo bisogno per fornire una risposta all'utente di origine!
In questo articolo, abbiamo visto come Amazon Web Services può essere utilizzato per orchestrare pipeline complesse. Sebbene in questo articolo, la pipeline sia stata progettata attorno alle e-mail; questi stessi strumenti possono essere sfruttati per progettare sistemi ancora più complessi, dove non devi preoccuparti della manutenzione dell'infrastruttura e puoi invece concentrarti sugli aspetti divertenti dell'ingegneria del software.