Eine neue Art der Verwendung von E-Mail für Support-Apps: Ein AWS-Tutorial

Veröffentlicht: 2022-03-11

E-Mail ist vielleicht nicht so cool wie andere Kommunikationsplattformen, aber die Arbeit damit kann trotzdem Spaß machen. Ich wurde kürzlich damit beauftragt, Messaging in einer mobilen App zu implementieren. Der einzige Haken war, dass die eigentliche Kommunikation per E-Mail erfolgen musste. Wir wollten, dass App-Benutzer mit einem Support-Team kommunizieren können, so wie Sie eine Textnachricht senden würden. Support-Teammitglieder mussten diese Nachrichten per E-Mail erhalten und auch in der Lage sein, dem ursprünglichen Benutzer zu antworten. Für den Endbenutzer sollte alles wie jede andere moderne Messaging-App aussehen und funktionieren.

In diesem Artikel werfen wir einen Blick darauf, wie man einen Service ähnlich dem oben beschriebenen mit Java und einer Handvoll Webservices von Amazon implementiert. Sie benötigen ein gültiges AWS-Konto, einen Domänennamen und Zugriff auf Ihre bevorzugte Java-IDE.

Die Infrastruktur

Bevor wir Code schreiben, richten wir die erforderlichen AWS-Services für das Routing und die Nutzung von E-Mails ein. Wir werden SES zum Senden und Konsumieren von E-Mails und SNS+SQS zum Weiterleiten eingehender Nachrichten verwenden.

Programmatisches Konsumieren von E-Mails mit AWS

Revitalisieren Sie E-Mail in Support-Anwendungen mit Amazon SES.
Twittern

Alles beginnt hier mit SES. Melden Sie sich zunächst bei Ihrem AWS-Konto an und navigieren Sie zur SES-Konsole.

Bevor wir beginnen, benötigen Sie einen verifizierten Domänennamen, von dem aus Sie E-Mails senden können.

Dies ist die Domain-App, von der Benutzer E-Mail-Nachrichten senden und auf die Support-Mitglieder antworten. Die Verifizierung einer Domain mit SES ist ein unkomplizierter Prozess, und weitere Informationen finden Sie hier.

Wenn Sie SES zum ersten Mal verwenden oder kein Sendelimit angefordert haben, wird Ihr Konto einer Sandbox unterzogen. Das bedeutet, dass Sie keine E-Mails an Adressen senden können, die nicht mit AWS verifiziert sind. Dies kann später im Tutorial zu einem Fehler führen, wenn wir eine E-Mail an unseren fiktiven Helpdesk senden. Um dies zu vermeiden, können Sie jede E-Mail-Adresse, die Sie als Helpdesk verwenden möchten, in der SES-Konsole auf der Registerkarte „E-Mail-Adressen“ überprüfen.

Sobald Sie eine verifizierte Domain haben, können wir einen Regelsatz erstellen. Navigieren Sie in der SES-Konsole zur Registerkarte Regelsätze und erstellen Sie eine neue Empfangsregel.

Der erste Schritt beim Erstellen einer Empfangsregel besteht darin, einen Empfänger zu definieren.

Mit Empfängerfiltern können Sie definieren, welche E-Mails SES verarbeitet und wie jede eingehende Nachricht verarbeitet wird. Der hier definierte Empfänger muss mit dem Domänen- und Adressmuster übereinstimmen, von dem Benutzernachrichten per E-Mail gesendet werden. Der einfachste Fall wäre hier, einen Empfänger für die zuvor verifizierte Domain hinzuzufügen, in unserem Fall example.com . Dadurch wird SES so konfiguriert, dass unsere Regel auf alle E-Mails angewendet wird, die an example.com gesendet werden. (z. B. [email protected], [email protected]).

Um eine Regel für unsere gesamte Domain zu erstellen, würden wir einen Empfänger für example.com hinzufügen.

Es ist auch möglich, Adressmuster abzugleichen. Dies ist nützlich, wenn Sie eingehende Nachrichten an verschiedene SQS-Warteschlangen weiterleiten möchten.

Angenommen, wir haben Warteschlange A und Warteschlange B. Wir könnten zwei Empfänger hinzufügen: [email protected] und [email protected] . Wenn wir eine Nachricht in die Warteschlange A einfügen möchten, senden wir eine E-Mail an [email protected]. Der a-Teil davon stimmt mit unserem [email protected]änger überein . Alles zwischen + und @ sind willkürliche Benutzerdaten, die den Adressabgleich von SES nicht beeinflussen. Um in Warteschlange B einzufügen, ersetzen Sie einfach a durch b.

Nachdem Sie Ihre Empfänger definiert haben, besteht der nächste Schritt darin, die Aktion zu konfigurieren, die SES nach dem Verbrauch einer neuen E-Mail ausführt. Wir möchten, dass diese letztendlich in SQS landen, jedoch ist es derzeit nicht möglich, direkt von SES zu SQS zu wechseln. Um die Lücke zu schließen, müssen wir SNS verwenden. Wählen Sie die SNS-Aktion aus und erstellen Sie ein neues Thema. Wir werden dieses Thema schließlich konfigurieren, um Nachrichten in SQS einzufügen.

Wählen Sie SNS-Thema erstellen und geben Sie ihm einen Namen.

Nachdem das Thema erstellt wurde, müssen wir eine Nachrichtencodierung auswählen. Ich werde Base64 verwenden, um Sonderzeichen beizubehalten. Die von Ihnen gewählte Codierung wirkt sich darauf aus, wie Nachrichten decodiert werden, wenn wir sie in unserem Dienst verwenden.

Sobald die Regel festgelegt ist, müssen wir sie nur noch benennen.

Der nächste Schritt wird die Konfiguration von SQS und SNS sein, dafür müssen wir zur SQS-Konsole gehen und eine neue Warteschlange erstellen.

Der Einfachheit halber verwende ich denselben Namen wie unser SNS-Thema.

Nachdem wir unsere Warteschlange definiert haben, müssen wir ihre Zugriffsrichtlinie anpassen. Wir möchten unserem SNS-Thema nur die Erlaubnis zum Einfügen erteilen. Wir können dies erreichen, indem wir eine Bedingung hinzufügen, die unserem SNS-Thema arn entspricht.

Das Wertfeld sollte mit dem ARN für das SNS-Thema ausgefüllt werden, das SES benachrichtigt.

Nachdem SQS eingerichtet ist, ist es Zeit für eine weitere Reise zurück zur SNS-Konsole, um Ihr Thema so zu konfigurieren, dass Benachrichtigungen in Ihre glänzende neue SQS-Warteschlange eingefügt werden.

Wählen Sie in der SNS-Konsole das Thema aus, das SES benachrichtigt. Erstellen Sie von dort aus ein neues Abonnement. Das Abonnementprotokoll sollte Amazon SQS sein, und das Ziel sollte der ARN der gerade generierten SQS-Warteschlange sein.

Nach all dem sollte die AWS-Seite der Gleichung eingerichtet sein. Wir können unsere Arbeit testen, indem wir uns selbst eine E-Mail senden. Senden Sie eine E-Mail an die mit SES konfigurierte Domäne, gehen Sie dann zur SQS-Konsole und wählen Sie Ihre Warteschlange aus. Sie sollten die Payload sehen können, die Ihre E-Mail enthält.

Java-Dienst zum Umgang mit E-Mails

Jetzt zum lustigen Teil! In diesem Abschnitt erstellen wir einen einfachen Microservice, der Nachrichten senden und eingehende E-Mails verarbeiten kann. Der erste Schritt besteht darin, eine API zu definieren, die unserem Support-Desk im Namen eines Benutzers eine E-Mail sendet.

Eine kurze Anmerkung. Wir werden uns auf die Geschäftslogikkomponenten dieses Dienstes konzentrieren und keine REST-Endpunkte oder eine Persistenzschicht definieren.

Um einen Spring-Dienst zu erstellen, verwenden wir Spring Boot und Maven. Wir können Spring Initializer verwenden, um ein Projekt für uns zu generieren, start.spring.io.

Zu Beginn sollte unsere pom.xml in etwa so aussehen:

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

E-Mail-Support im Namen eines Benutzers

Lassen Sie uns zunächst eine Bean definieren, um unserem Support-Desk im Namen eines Benutzers eine E-Mail zu senden. Die Aufgabe dieser Bean besteht darin, eine eingehende Nachricht von einer Benutzer-ID zu verarbeiten und diese Nachricht per E-Mail an unsere vordefinierte E-Mail-Adresse des Support-Desks zu senden.

Beginnen wir mit der Definition einer Schnittstelle.

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

Und eine leere Implementierung:

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

Lassen Sie uns auch das AWS SDK zu unserem Pom hinzufügen, wir werden den SES-Client verwenden, um unsere E-Mails zu senden:

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

Als erstes müssen wir eine E-Mail-Adresse generieren, von der aus die Nachricht unseres Benutzers gesendet werden kann. Die von uns generierte Adresse wird auf der Verbraucherseite unseres Dienstes eine entscheidende Rolle spielen. Es muss genügend Informationen enthalten, um die Antwort des Helpdesks an den ursprünglichen Benutzer zurückzuleiten.

Um dies zu erreichen, werden wir die ursprüngliche Benutzer-ID in unsere generierte E-Mail-Adresse aufnehmen. Um die Dinge sauber zu halten, erstellen wir ein Objekt, das die Benutzer-ID enthält, und verwenden die Base64-codierte JSON-Zeichenfolge davon als E-Mail-Adresse.

Lassen Sie uns eine neue Bean erstellen, die dafür verantwortlich ist, eine Benutzer-ID in eine E-Mail-Adresse umzuwandeln.

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

Beginnen wir mit unserer Implementierung, indem wir die erforderlichen Zustimmungen und eine einfache innere Klasse hinzufügen, die uns bei der Serialisierung unseres JSON helfen wird.

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

Das Generieren unserer E-Mail-Adresse ist unkompliziert, wir müssen lediglich ein UserDetails-Objekt erstellen und die JSON-Darstellung mit Base64 codieren. Die fertige Version unserer Methode createAddressForUserID sollte etwa so aussehen:

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

Jetzt können wir zu SupportBeanSesImpl zurückkehren und es aktualisieren, um die neue E-Mail-Bean zu verwenden, die wir gerade erstellt haben.

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

Zum Senden von E-Mails verwenden wir den im AWS SDK enthaltenen AWS SES-Client.

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

Wir verwenden DefaultAWSCredentialsProviderChain, um Anmeldeinformationen für uns zu verwalten. Diese Klasse sucht nach AWS-Anmeldeinformationen, wie hier definiert.

Wir gehen zu einem AWS-Zugriffsschlüssel, der mit Zugriff auf SES und schließlich SQS ausgestattet ist. Weitere Informationen finden Sie in der Dokumentation von Amazon.

Der nächste Schritt wird die Aktualisierung unserer messageSupport- Methode auf E-Mail-Support mit dem AWS SDK sein. Das SES SDK macht dies zu einem unkomplizierten Prozess. Die fertige Methode sollte in etwa so aussehen:

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

Erstellen Sie zum Ausprobieren eine Testklasse und fügen Sie die SupportBean ein. Stellen Sie sicher, dass die in SupportBeanSesImpl definierte SUPPORT_EMAIL_ADDRESS auf eine E-Mail-Adresse verweist, die Ihnen gehört. Wenn es sich bei Ihrem SES-Konto um ein Sandbox-Konto handelt, muss diese Adresse ebenfalls verifiziert werden. E-Mail-Adressen können in der SES-Konsole im Abschnitt E-Mail-Adressen verifiziert werden.

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

Nachdem Sie dies ausgeführt haben, sollten Sie eine Nachricht in Ihrem Posteingang sehen. Besser noch, antworten Sie auf die Nachricht und überprüfen Sie die SQS-Warteschlange, die wir zuvor eingerichtet haben. Sie sollten eine Nutzlast sehen, die Ihre Antwort enthält.

Konsumierende Antworten von SQS

Der letzte Schritt besteht darin, E-Mails von SQS einzulesen, die E-Mail-Nachricht zu parsen und herauszufinden, zu welcher Benutzer-ID die Antwort weitergeleitet werden soll.

Message-Queuing-Dienste wie Amazon SQS spielen eine entscheidende Rolle in der serviceorientierten Architektur, indem sie es Diensten ermöglichen, miteinander zu kommunizieren, ohne Kompromisse bei Geschwindigkeit, Zuverlässigkeit oder Skalierbarkeit eingehen zu müssen.

Um auf neue SQS-Nachrichten zu lauschen, verwenden wir das AWS-Messaging-SDK von Spring Cloud. Auf diese Weise können wir einen SQS-Nachrichten-Listener über Anmerkungen konfigurieren und so eine Menge Boilerplate-Code vermeiden.

Zuerst die erforderlichen Abhängigkeiten.

Fügen Sie die Spring Cloud-Messaging-Abhängigkeit hinzu:

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

Und fügen Sie Spring Cloud AWS zu Ihrem Pom-Abhängigkeitsmanagement hinzu:

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

Derzeit unterstützt Spring Cloud AWS keine annotationsgesteuerte Konfiguration, daher müssen wir eine XML-Bean definieren. Glücklicherweise brauchen wir überhaupt nicht viel Konfiguration, daher wird unsere Bean-Definition ziemlich leicht sein. Der Hauptzweck dieser Datei besteht darin, annotationsgesteuerte Warteschlangen-Listener zu aktivieren. Dadurch können wir eine Methode als SqsListener annotieren.

Erstellen Sie eine neue XML-Datei mit dem Namen aws-config.xml in Ihrem Ressourcenordner. Unsere Definition sollte in etwa so aussehen:

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

Der wichtige Teil dieser Datei ist <aws-messaging:annotation-driven-queue-listener /> . Wir definieren auch eine Standardregion. Dies ist nicht erforderlich, ermöglicht es uns jedoch, auf unsere SQS-Warteschlange anhand des Namens anstelle der URL zu verweisen. Wir definieren keine AWS-Anmeldeinformationen. Wenn wir sie weglassen, verwendet Spring standardmäßig DefaultAWSCredentialsProviderChain, denselben Anbieter, den wir zuvor in unserer SES-Bean verwendet haben. Weitere Informationen finden Sie in den Spring Cloud AWS-Dokumenten.

Um diese XML-Konfiguration in unserer Spring Boot-App zu verwenden, müssen wir sie explizit importieren. Gehen Sie zu Ihrer @SpringBootApplication-Klasse und importieren Sie sie.

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

Lassen Sie uns nun eine Bean definieren, die eingehende SQS-Nachrichten verarbeitet. Mit Spring Cloud AWS können wir dies mit einer einzigen Anmerkung erreichen!

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

Die Magie liegt hier in der Annotation @SqsListener. Damit richtet Spring einen Executor ein und beginnt mit der Abfrage von SQS für uns. Jedes Mal, wenn eine neue Nachricht gefunden wird, wird unsere kommentierte Methode mit dem Inhalt der Nachricht aufgerufen. Optional kann Spring Cloud so konfiguriert werden, dass eingehende Nachrichten gemarshallt werden, sodass Sie mit stark typisierten Objekten in Ihrem Warteschlangen-Listener arbeiten können. Darüber hinaus haben Sie die Möglichkeit, einen einzelnen Header oder eine Zuordnung aller Header einzufügen, die vom zugrunde liegenden AWS-Aufruf zurückgegeben werden.

Wir können hier den Namen der logischen Warteschlange verwenden, da wir die Region zuvor in aws-config.xml definiert haben. Wenn wir dies auslassen wollten, könnten wir den Wert durch unsere vollqualifizierte SQS-URL ersetzen. Wir definieren auch eine Löschrichtlinie, die Spring so konfiguriert, dass die eingehende Nachricht von SQS gelöscht wird, wenn ihre Bedingung erfüllt ist. In SqsMessageDeletionPolicy sind mehrere Richtlinien definiert. Wir konfigurieren Spring so, dass unsere Nachricht gelöscht wird, wenn unsere Methode „consumeSqsMessage“ erfolgreich ausgeführt wird.

Wir fügen auch die zurückgegebenen SQS-Header mit @Headers in unsere Methode ein, und die eingefügte Zuordnung enthält Metadaten in Bezug auf die Warteschlange und die empfangene Nutzlast. Der Nachrichtentext wird mit @NotificationMessage eingefügt. Spring unterstützt das Marshalling mit Jackson oder über einen benutzerdefinierten Nachrichtentextkonverter. Der Einfachheit halber fügen wir einfach die rohe JSON-Zeichenfolge ein und arbeiten damit, indem wir die JSONObject-Klasse verwenden, die im AWS SDK enthalten ist.

Die von SQS abgerufene Nutzlast enthält viele Daten. Sehen Sie sich das JSONObject an, um sich mit der zurückgegebenen Nutzlast vertraut zu machen. Unsere Payload enthält Daten von jedem AWS-Service, den sie durchlaufen hat, SES, SNS und schließlich SQS. Für dieses Tutorial interessieren uns wirklich nur zwei Dinge: die Liste der E-Mail-Adressen, an die dies gesendet wurde, und der E-Mail-Text. Beginnen wir mit dem Analysieren der 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); }

Da unser Helpdesk in der realen Welt möglicherweise mehr als nur den ursprünglichen Absender in seine Antwort aufnimmt, möchten wir die Adresse überprüfen, bevor wir die Benutzer-ID analysieren. Dadurch kann unser Support-Desk sowohl mehrere Benutzer gleichzeitig benachrichtigen als auch Nicht-App-Benutzer einbeziehen.

Kehren wir zu unserer UserEmailBean-Schnittstelle zurück und fügen eine weitere Methode hinzu.

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

Um diese Methode in UserEmailBeanJSONImpl zu implementieren, wollen wir zwei Dinge tun. Überprüfen Sie zuerst, ob die Adresse mit unserer EMAIL_DOMAIN endet, und überprüfen Sie dann, ob wir sie ordnen können.

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

Wir haben zwei neue Methoden definiert, emailMatchesUserFormat, die wir gerade zu unserer Schnittstelle hinzugefügt haben, und eine einfache Hilfsmethode zum Aufteilen einer E-Mail-Adresse auf dem @. Unsere emailMatchesUserFormat-Implementierung funktioniert, indem sie versucht, den Adressteil mit Base64 zu dekodieren und zurück in unsere UserDetails-Hilfsklasse zu marshallen. Wenn dies erfolgreich ist, überprüfen wir, ob die erforderliche Benutzer-ID ausgefüllt ist. Wenn das alles klappt, können wir getrost von einem Match ausgehen.

Gehen Sie zurück zu unserem EmailSqsListener und fügen Sie die frisch aktualisierte UserEmailBean ein.

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

Jetzt aktualisieren wir die convertSqsMethod. Analysieren wir zuerst den E-Mail-Text:

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

Lassen Sie uns nun eine neue Methode erstellen, die die E-Mail-Adresse und den E-Mail-Text verarbeitet.

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

Aktualisieren Sie schließlich die E-Mail-Schleife, um diese Methode aufzurufen, wenn sie eine Übereinstimmung findet.

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

Bevor wir processEmail implementieren, müssen wir unserer UserEmailBean eine weitere Methode hinzufügen. Wir benötigen eine Methode zur Rückgabe der Benutzer-ID aus einer E-Mail. Gehen Sie zurück zur UserEmailBean-Schnittstelle, um ihre letzte Methode hinzuzufügen.

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

Das Ziel dieser Methode besteht darin, die Benutzer-ID von einer formatierten Adresse zurückzugeben. Die Implementierung ähnelt unserer Überprüfungsmethode. Gehen wir zu UserEmailBeanJSONImpl und füllen diese Methode aus.

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

Gehen Sie jetzt zurück zu unserem EmailSqsListener und aktualisieren Sie processEmail, um diese neue Methode zu verwenden.

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

Toll! Jetzt haben wir fast alles, was wir brauchen. Das Letzte, was wir tun müssen, ist die Antwort aus der Rohnachricht zu analysieren.

E-Mail-Clients werden, genau wie Webbrowser vor ein paar Jahren, von Inkonsistenzen in ihren Implementierungen geplagt.

Das Analysieren von Antworten aus E-Mails ist eigentlich eine ziemlich komplizierte Aufgabe. E-Mail-Nachrichtenformate sind nicht standardisiert und die Unterschiede zwischen verschiedenen E-Mail-Clients können enorm sein. Die Rohantwort wird auch viel mehr als die Antwort und eine Signatur enthalten. Die ursprüngliche Nachricht wird höchstwahrscheinlich ebenfalls enthalten sein. Kluge Leute bei Mailgun haben einen großartigen Blogbeitrag zusammengestellt, in dem einige der Herausforderungen erklärt werden. Sie haben auch ihren auf maschinellem Lernen basierenden Ansatz zum Analysieren von E-Mails als Open Source veröffentlicht, sehen Sie sich das hier an.

Die Mailgun-Bibliothek ist in Python geschrieben, daher verwenden wir für unser Tutorial eine einfachere Java-basierte Lösung. GitHub-Benutzer edlio hat einen MIT-lizenzierten E-Mail-Parser in Java erstellt, der auf einer der Bibliotheken von GitHub basiert. Wir werden diese großartige Bibliothek verwenden.

Lassen Sie uns zuerst unseren Pom aktualisieren, wir werden https://jitpack.io verwenden, um EmailReplyParser einzufügen.

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

Fügen Sie nun die GitHub-Abhängigkeit hinzu.

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

Wir werden auch Apache Commons E-Mail verwenden. Wir müssen die rohe E-Mail in eine javax.mail MimeMessage parsen, bevor wir sie an den EmailReplyParser weitergeben. Fügen Sie die Commons-Abhängigkeit hinzu.

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

Jetzt können wir zu unserem EmailSqsListener zurückkehren und processEmail abschließen. An diesem Punkt haben wir die ursprüngliche Benutzer-ID und den Roh-E-Mail-Text. Das einzige, was noch zu tun ist, ist die Antwort zu analysieren.

Um dies zu erreichen, verwenden wir eine Kombination aus javax.mail und dem EmailReplyParser von 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); }

Einpacken

Und das ist es! Wir haben jetzt alles, was wir brauchen, um eine Antwort an den ursprünglichen Benutzer zu senden!

Sehen? Ich habe dir gesagt, E-Mail kann Spaß machen!

In diesem Artikel haben wir gesehen, wie Amazon Web Services verwendet werden können, um komplexe Pipelines zu orchestrieren. Obwohl die Pipeline in diesem Artikel um E-Mails herum entworfen wurde; Dieselben Tools können genutzt werden, um noch komplexere Systeme zu entwerfen, bei denen Sie sich nicht um die Wartung der Infrastruktur kümmern müssen und sich stattdessen auf die unterhaltsamen Aspekte der Softwareentwicklung konzentrieren können.

Siehe auch : Steigern Sie Ihre Produktivität mit Amazon Web Services