Новый способ использования электронной почты для приложений поддержки: учебное пособие по AWS

Опубликовано: 2022-03-11

Электронная почта может быть не такой крутой, как другие коммуникационные платформы, но работать с ней все же может быть весело. Недавно мне поручили внедрить обмен сообщениями в мобильное приложение. Единственная загвоздка заключалась в том, что фактическое общение должно было осуществляться по электронной почте. Мы хотели, чтобы пользователи приложения могли общаться со службой поддержки так же, как вы отправляете текстовое сообщение. Члены группы поддержки должны были получать эти сообщения по электронной почте, а также иметь возможность ответить исходному пользователю. Для конечного пользователя все должно было выглядеть и функционировать так же, как любое другое современное приложение для обмена сообщениями.

В этой статье мы рассмотрим, как реализовать сервис, аналогичный описанному выше, используя Java и несколько веб-сервисов Amazon. Вам потребуется действующая учетная запись AWS, доменное имя и доступ к вашей любимой Java IDE.

Инфраструктура

Прежде чем мы напишем какой-либо код, мы настроим необходимые сервисы AWS для маршрутизации и использования электронной почты. Мы собираемся использовать SES для отправки и потребления электронной почты и SNS+SQS для маршрутизации входящих сообщений.

Программное использование электронной почты с помощью AWS

Оживите электронную почту в приложениях поддержки с помощью Amazon SES.
Твитнуть

Здесь все начинается с СЭС. Начните с входа в свою учетную запись AWS и перехода к консоли SES.

Прежде чем мы начнем, вам понадобится подтвержденное доменное имя, с которого вы сможете отправлять электронные письма.

Это будет доменное приложение, из которого пользователи будут отправлять сообщения электронной почты, а члены службы поддержки будут отвечать на них. Проверка домена с помощью SES — это простой процесс, и дополнительную информацию можно найти здесь.

Если вы впервые используете SES или не запрашивали ограничение на отправку, ваша учетная запись будет изолирована. Это означает, что вы не сможете отправлять электронную почту на адреса, которые не проверены с помощью AWS. Это может вызвать ошибку позже в руководстве, когда мы отправим электронное письмо в нашу вымышленную службу поддержки. Чтобы избежать этого, вы можете проверить любой адрес электронной почты, который вы планируете использовать в качестве службы поддержки, в консоли SES на вкладке «Адреса электронной почты».

Когда у вас есть подтвержденный домен, мы можем создать набор правил. Перейдите на вкладку «Наборы правил» в консоли SES и создайте новое правило получения.

Первым шагом при создании правила получения будет определение получателя.

Фильтры получателей позволят вам определить, какие электронные письма будет использовать SES, и как обрабатывать каждое входящее сообщение. Получатель, который мы определяем здесь, должен соответствовать домену и шаблону адреса, с которого отправляются сообщения пользователя приложения. Простейшим случаем здесь было бы добавить получателя для домена, который мы ранее проверили, в нашем случае example.com . Это настроит SES для применения нашего правила ко всем электронным письмам, отправляемым на example.com . (например, [email protected], [email protected]).

Чтобы создать правило для всего нашего домена, мы добавим получателя, например example.com .

Также возможно сопоставление шаблонов адресов. Это полезно, если вы хотите направлять входящие сообщения в разные очереди SQS.

Допустим, у нас есть очередь A и очередь B. Мы можем добавить двух получателей: [email protected] и [email protected] . Если мы хотим вставить сообщение в очередь A, мы должны написать по электронной почте [email protected]. Часть a будет соответствовать нашему получателю [email protected] . Все, что находится между + и @, является произвольными данными пользователя, это не повлияет на сопоставление адресов SES. Чтобы вставить в очередь B, просто замените a на b.

После определения получателей следующим шагом будет настройка действия, которое SES будет выполнять после получения нового электронного письма. В конечном итоге мы хотим, чтобы они попали в SQS, однако в настоящее время невозможно напрямую перейти от SES к SQS. Чтобы преодолеть разрыв, нам нужно использовать SNS. Выберите действие SNS и создайте новую тему. Со временем мы настроим этот раздел для вставки сообщений в SQS.

Выберите создать тему SNS и дайте ей имя.

После того, как тема создана, нам нужно выбрать кодировку сообщения. Я собираюсь использовать Base64, чтобы сохранить специальные символы. Выбранная вами кодировка повлияет на то, как сообщения декодируются, когда мы используем их в нашем сервисе.

Как только правило установлено, нам просто нужно назвать его.

Следующим шагом будет настройка SQS и SNS, для этого нам нужно перейти в консоль SQS и создать новую очередь.

Для простоты я использую то же имя, что и наша тема в социальных сетях.

После того, как мы определим нашу очередь, нам нужно будет настроить ее политику доступа. Мы только хотим предоставить нашей теме SNS разрешение на вставку. Мы можем добиться этого, добавив условие, которое соответствует нашей теме SNS arn.

Поле значения должно быть заполнено ARN для темы SNS, о которой SES уведомляет.

После того, как SQS настроен, пришло время еще раз вернуться к консоли SNS, чтобы настроить тему для вставки уведомлений в вашу новую блестящую очередь SQS.

В консоли SNS выберите тему, о которой уведомляет SES. Оттуда создайте новую подписку. Протоколом подписки должен быть Amazon SQS, а пунктом назначения должен быть ARN только что созданной очереди SQS.

После всего этого сторона уравнения AWS должна быть настроена. Мы можем проверить нашу работу, написав нам по электронной почте. Отправьте электронное письмо на домен, настроенный с помощью SES, затем перейдите в консоль SQS и выберите свою очередь. Вы должны увидеть полезную нагрузку, содержащую вашу электронную почту.

Служба Java для работы с электронной почтой

Теперь самое интересное! В этом разделе мы собираемся создать простой микросервис, способный отправлять сообщения и обрабатывать входящие электронные письма. Первым шагом будет определение API, который будет отправлять электронное письмо в нашу службу поддержки от имени пользователя.

Быстрая заметка. Мы собираемся сосредоточиться на компонентах бизнес-логики этой службы и не будем определять конечные точки REST или уровень сохраняемости.

Для создания службы Spring мы будем использовать Spring Boot и Maven. Мы можем использовать Spring Initializer для создания проекта start.spring.io.

Для начала наш pom.xml должен выглядеть примерно так:

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

Электронная почта поддержки от имени пользователя

Во-первых, давайте определим bean-компонент для отправки электронной почты в нашу службу поддержки от имени пользователя. Задача этого bean-компонента будет заключаться в обработке входящего сообщения от идентификатора пользователя и отправке этого сообщения по электронной почте на заранее определенный адрес электронной почты службы поддержки.

Начнем с определения интерфейса.

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

И пустая реализация:

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

Давайте также добавим AWS SDK в наш pom, мы собираемся использовать клиент SES для отправки наших электронных писем:

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

Первое, что нам нужно сделать, это создать адрес электронной почты, с которого будет отправляться сообщение нашего пользователя. Генерируемый нами адрес будет играть решающую роль на стороне потребителя нашего сервиса. Он должен содержать достаточно информации, чтобы направить ответ службы поддержки исходному пользователю.

Для этого мы включим исходный идентификатор пользователя в наш сгенерированный адрес электронной почты. Чтобы все было чисто, мы собираемся создать объект, содержащий идентификатор пользователя, и использовать его строку JSON в кодировке Base64 в качестве адреса электронной почты.

Давайте создадим новый компонент, отвечающий за преобразование идентификатора пользователя в адрес электронной почты.

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

Давайте начнем нашу реализацию с добавления необходимых разрешений и простого внутреннего класса, который поможет нам сериализовать наш 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; } } }

Генерация нашего адреса электронной почты проста, все, что нам нужно сделать, это создать объект UserDetails и Base64 закодировать представление JSON. Готовая версия нашего метода createAddressForUserID должна выглядеть примерно так:

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

Теперь мы можем вернуться к SupportBeanSesImpl и обновить его, чтобы использовать новый компонент электронной почты, который мы только что создали.

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

Для отправки электронных писем мы будем использовать клиент AWS SES, входящий в состав AWS SDK.

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

Мы используем DefaultAWSCredentialsProviderChain для управления учетными данными для нас, этот класс будет искать учетные данные AWS, как определено здесь.

Мы собираемся использовать ключ доступа AWS, предоставляющий доступ к SES и, в конечном итоге, к SQS. Для получения дополнительной информации ознакомьтесь с документацией от Amazon.

Следующим шагом будет обновление нашего метода messageSupport для поддержки по электронной почте с помощью AWS SDK. SES SDK упрощает этот процесс. Готовый метод должен выглядеть примерно так:

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

Чтобы попробовать, создайте тестовый класс и внедрите SupportBean. Убедитесь, что SUPPORT_EMAIL_ADDRESS, определенный в SupportBeanSesImpl, указывает на ваш адрес электронной почты. Если ваша учетная запись SES изолирована, этот адрес также необходимо подтвердить. Адреса электронной почты можно проверить в консоли SES в разделе «Адреса электронной почты».

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

После запуска вы должны увидеть сообщение в своем почтовом ящике. А еще лучше ответьте на сообщение и проверьте очередь SQS, которую мы создали ранее. Вы должны увидеть полезную нагрузку, содержащую ваш ответ.

Использование ответов от SQS

Последним шагом будет чтение электронных писем от SQS, анализ сообщения электронной почты и определение идентификатора пользователя, которому должен быть перенаправлен ответ.

Сервисы очередей сообщений, такие как Amazon SQS, играют жизненно важную роль в сервис-ориентированной архитектуре, позволяя сервисам взаимодействовать друг с другом без ущерба для скорости, надежности или масштабируемости.

Чтобы прослушивать новые сообщения SQS, мы будем использовать SDK для обмена сообщениями Spring Cloud AWS. Это позволит нам настроить прослушиватель сообщений SQS с помощью аннотаций и, таким образом, избежать большого количества шаблонного кода.

Во-первых, необходимые зависимости.

Добавьте зависимость обмена сообщениями Spring Cloud:

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

И добавьте Spring Cloud AWS в управление зависимостями 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>

В настоящее время Spring Cloud AWS не поддерживает конфигурацию на основе аннотаций, поэтому нам нужно будет определить компонент XML. К счастью, нам вообще не нужно много настроек, поэтому наше определение bean-компонента будет довольно легким. Основная цель этого файла будет заключаться в том, чтобы включить прослушиватели очереди, управляемые аннотациями, это позволит нам аннотировать метод как SqsListener.

Создайте новый XML-файл с именем aws-config.xml в папке ресурсов. Наше определение должно выглядеть примерно так:

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

Важная часть этого файла — <aws-messaging:annotation-driven-queue-listener /> . Мы также определяем регион по умолчанию. Это не обязательно, но это позволит нам ссылаться на нашу очередь SQS по имени, а не по URL-адресу. Мы не определяем никаких учетных данных AWS, опуская их, Spring по умолчанию будет использовать DefaultAWSCredentialsProviderChain, того же поставщика, который мы использовали ранее в нашем компоненте SES. Дополнительную информацию можно найти в документации Spring Cloud AWS.

Чтобы использовать эту конфигурацию XML в нашем приложении Spring Boot, нам нужно явно импортировать ее. Перейдите к своему классу @SpringBootApplication и импортируйте его.

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

Теперь давайте определим bean-компонент, который будет обрабатывать входящие сообщения SQS. Spring Cloud AWS позволяет нам сделать это с помощью одной аннотации!

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

Магия здесь заключается в аннотации @SqsListener. При этом Spring настроит Executor и начнет опрашивать SQS для нас. Каждый раз, когда будет найдено новое сообщение, будет вызываться наш аннотированный метод с содержимым сообщения. При желании Spring Cloud можно настроить для сортировки входящих сообщений, что дает вам возможность работать со строго типизированными объектами внутри прослушивателя очереди. Кроме того, у вас есть возможность внедрить один заголовок или карту всех заголовков, возвращенных базовым вызовом AWS.

Здесь мы можем использовать имя логической очереди, так как мы ранее определили регион в aws-config.xml, и если мы хотим опустить это, мы сможем заменить значение нашим полным URL-адресом SQS. Мы также определяем политику удаления, это настроит Spring на удаление входящего сообщения из SQS, если его условие выполнено. В SqsMessageDeletionPolicy определено несколько политик, мы настраиваем Spring для удаления нашего сообщения, если наш метод ConsumerSqsMessage выполняется успешно.

Мы также внедряем возвращенные заголовки SQS в наш метод с помощью @Headers, и внедренная карта будет содержать метаданные, связанные с очередью и полученными полезными данными. Тело сообщения вводится с помощью @NotificationMessage. Spring поддерживает сортировку с использованием Jackson или через пользовательский преобразователь тела сообщения. Для удобства мы просто вставим необработанную строку JSON и будем работать с ней, используя класс JSONObject, включенный в AWS SDK.

Полезная нагрузка, полученная от SQS, будет содержать много данных. Взгляните на JSONObject, чтобы ознакомиться с возвращенными полезными данными. Наша полезная нагрузка содержит данные от каждого сервиса AWS, через который она была передана, SES, SNS и, наконец, SQS. Для этого урока нас действительно интересуют только две вещи: список адресов электронной почты, на которые это было отправлено, и тело письма. Начнем с разбора писем.

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

Поскольку в реальном мире наша служба поддержки может включать в свой ответ не только исходного отправителя, мы собираемся проверить адрес, прежде чем анализировать идентификатор пользователя. Это даст нашей службе поддержки возможность отправлять сообщения нескольким пользователям одновременно, а также возможность включать пользователей, не являющихся пользователями приложения.

Вернемся к нашему интерфейсу UserEmailBean и добавим еще один метод.

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

В UserEmailBeanJSONImpl для реализации этого метода нам нужно сделать две вещи. Сначала проверьте, заканчивается ли адрес нашим EMAIL_DOMAIN, а затем проверьте, можем ли мы его упорядочить.

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

Мы определили два новых метода: emailMatchesUserFormat, который мы только что добавили в наш интерфейс, и простой служебный метод для разделения адреса электронной почты на @. Наша реализация emailMatchesUserFormat работает, пытаясь декодировать Base64 и сортировать адресную часть обратно в наш вспомогательный класс UserDetails. Если это удается, мы затем проверяем, заполнен ли требуемый идентификатор пользователя. Если все это получится, можно смело считать матч.

Вернитесь к нашему EmailSqsListener и внедрите только что обновленный UserEmailBean.

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

Теперь мы собираемся обновить ConsumerSqsMethod. Сначала давайте разберем тело письма:

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

Теперь давайте создадим новый метод, который будет обрабатывать адрес электронной почты и тело письма.

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

И, наконец, обновите цикл электронной почты, чтобы вызывать этот метод, если он находит совпадение.

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

Прежде чем мы реализуем processEmail, нам нужно добавить еще один метод в наш UserEmailBean. Нам нужен метод для возврата идентификатора пользователя из электронной почты. Вернитесь к интерфейсу UserEmailBean, чтобы добавить его последний метод.

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

Целью этого метода будет возврат идентификатора пользователя из отформатированного адреса. Реализация будет аналогична нашему методу проверки. Давайте перейдем к UserEmailBeanJSONImpl и заполним этот метод.

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

Теперь вернитесь к нашему EmailSqsListener и обновите processEmail, чтобы использовать этот новый метод.

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

Здорово! Теперь у нас есть почти все, что нужно. Последнее, что нам нужно сделать, это разобрать ответ из необработанного сообщения.

Почтовые клиенты, как и веб-браузеры несколько лет назад, страдают от несоответствий в своих реализациях.

Анализ ответов из электронных писем на самом деле является довольно сложной задачей. Форматы сообщений электронной почты не стандартизированы, и различия между различными почтовыми клиентами могут быть огромными. Необработанный ответ также будет включать в себя гораздо больше, чем просто ответ и подпись. Исходное сообщение, скорее всего, также будет включено. Умные люди из Mailgun написали отличный пост в блоге, объясняющий некоторые проблемы. Они также открыли исходный код своего подхода к анализу электронной почты, основанного на машинном обучении, ознакомьтесь с ним здесь.

Библиотека Mailgun написана на Python, поэтому в нашем руководстве мы будем использовать более простое решение на основе Java. Пользователь GitHub edlio собрал анализатор электронной почты под лицензией MIT на Java на основе одной из библиотек GitHub. Мы собираемся использовать эту замечательную библиотеку.

Сначала давайте обновим наш pom, мы собираемся использовать https://jitpack.io для получения EmailReplyParser.

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

Теперь добавьте зависимость GitHub.

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

Мы также собираемся использовать общедоступную электронную почту Apache. Нам нужно будет разобрать необработанное электронное письмо в javax.mail MimeMessage перед передачей его в EmailReplyParser. Добавьте общую зависимость.

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

Теперь мы можем вернуться к нашему EmailSqsListener и закончить processEmail. На данный момент у нас есть исходный идентификатор пользователя и необработанное тело электронного письма. Осталось только разобрать ответ.

Для этого мы будем использовать комбинацию javax.mail и EmailReplyParser от 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); }

Заворачивать

Вот и все! Теперь у нас есть все необходимое, чтобы доставить ответ исходному пользователю!

Видеть? Я говорил вам, что электронная почта может быть веселой!

В этой статье мы увидели, как Amazon Web Services можно использовать для организации сложных конвейеров. Хотя в этой статье конвейер был разработан для электронной почты; эти же инструменты можно использовать для разработки еще более сложных систем, где вам не нужно беспокоиться об обслуживании инфраструктуры, а вместо этого вы можете сосредоточиться на интересных аспектах разработки программного обеспечения.

Связанный: Повысьте свою производительность с помощью Amazon Web Services