Cara Baru Menggunakan Email untuk Aplikasi Dukungan: Tutorial AWS
Diterbitkan: 2022-03-11Email mungkin tidak sekeren platform komunikasi lain tetapi bekerja dengannya tetap menyenangkan. Saya baru-baru ini ditugaskan untuk mengimplementasikan perpesanan di aplikasi seluler. Satu-satunya tangkapan adalah bahwa komunikasi yang sebenarnya harus melalui email. Kami ingin pengguna aplikasi dapat berkomunikasi dengan tim dukungan seperti Anda mengirim pesan teks. Anggota tim pendukung perlu menerima pesan ini melalui email, dan juga harus dapat menanggapi pengguna asal. Bagi pengguna akhir, semua yang dibutuhkan untuk terlihat dan berfungsi sama seperti aplikasi perpesanan modern lainnya.
Pada artikel ini, kita akan melihat bagaimana menerapkan layanan yang serupa dengan yang dijelaskan di atas menggunakan Java dan beberapa layanan web Amazon. Anda memerlukan akun AWS yang valid, nama domain, dan akses ke Java IDE favorit Anda.
Infrastruktur
Sebelum kami menulis kode apa pun, kami akan menyiapkan layanan AWS yang diperlukan untuk perutean dan penggunaan email. Kami akan menggunakan SES untuk mengirim dan menggunakan email dan SNS+SQS untuk merutekan pesan masuk.
Semuanya dimulai di sini dengan SES. Mulailah dengan masuk ke akun AWS Anda dan menavigasi ke konsol SES.
Sebelum kita mulai, Anda memerlukan nama domain terverifikasi yang dapat Anda gunakan untuk mengirim email.
Ini akan menjadi pengguna aplikasi domain yang akan mengirim pesan email dan anggota dukungan akan membalas. Memverifikasi domain dengan SES adalah proses yang mudah, dan info lebih lanjut dapat ditemukan di sini.
Jika ini adalah pertama kalinya Anda menggunakan SES, atau Anda belum meminta batas pengiriman, akun Anda akan di-sandbox. Ini berarti Anda tidak akan dapat mengirim email ke alamat yang tidak diverifikasi dengan AWS. Ini dapat menyebabkan kesalahan nanti dalam tutorial, ketika kami mengirim email ke meja bantuan fiktif kami. Untuk menghindari hal ini, Anda dapat memverifikasi alamat email apa pun yang Anda rencanakan untuk digunakan sebagai meja bantuan di konsol SES di tab Alamat Email.
Setelah Anda memiliki domain terverifikasi, kami dapat membuat kumpulan aturan. Arahkan ke tab Rule Sets di konsol SES dan buat Receipt Rule baru.
Langkah pertama saat membuat aturan tanda terima adalah menentukan penerima.
Filter penerima akan memungkinkan Anda menentukan email apa yang akan digunakan SES, dan cara memproses setiap pesan masuk. Penerima yang kami tentukan di sini harus cocok dengan domain dan pola alamat dari pesan pengguna aplikasi yang dikirimi email. Kasus paling sederhana di sini adalah menambahkan penerima untuk domain yang sebelumnya kami verifikasi, dalam kasus kami example.com . Ini akan mengonfigurasi SES untuk menerapkan aturan kami ke semua email yang dikirim ke example.com . (misalnya [email protected], [email protected]).
Untuk membuat aturan untuk seluruh domain kami, kami akan menambahkan penerima untuk example.com .
Dimungkinkan juga untuk mencocokkan pola alamat. Ini berguna jika Anda ingin merutekan pesan masuk ke antrian SQS yang berbeda.
Katakanlah kita memiliki antrian A dan antrian B. Kita dapat menambahkan dua penerima: [email protected] dan [email protected] . Jika kita ingin menyisipkan pesan ke antrian A, kita akan mengirim email ke [email protected]. Bagian dari ini akan cocok dengan penerima [email protected] kami. Segala sesuatu antara + dan @ adalah data pengguna arbitrer, tidak akan mempengaruhi pencocokan alamat SES. Untuk memasukkan ke dalam antrian B, cukup ganti a dengan b.
Setelah Anda menentukan penerima, langkah selanjutnya adalah mengonfigurasi tindakan yang akan dilakukan SES setelah menggunakan email baru. Kami akhirnya ingin ini berakhir di SQS, namun saat ini tidak mungkin untuk langsung dari SES ke SQS. Untuk menjembatani kesenjangan, kita perlu menggunakan SNS. Pilih tindakan SNS dan buat topik baru. Kami akhirnya akan mengonfigurasi topik ini untuk memasukkan pesan ke dalam SQS.
Pilih buat topik SNS dan beri nama.
Setelah topik dibuat, kita perlu memilih penyandian pesan. Saya akan menggunakan Base64 untuk mempertahankan karakter khusus. Encoding yang Anda pilih akan mempengaruhi bagaimana pesan didekode ketika kami menggunakannya dalam layanan kami.
Setelah aturan ditetapkan, kita hanya perlu menamainya.
Langkah selanjutnya adalah mengkonfigurasi SQS dan SNS, untuk itu kita perlu menuju ke konsol SQS dan membuat antrian baru.
Untuk mempermudah, saya menggunakan nama yang sama dengan topik SNS kami.
Setelah kita mendefinisikan antrian kita, kita perlu menyesuaikan kebijakan aksesnya. Kami hanya ingin memberikan izin topik SNS kami untuk dimasukkan. Kami dapat mencapai ini dengan menambahkan kondisi yang cocok dengan topik SNS kami arn.
Bidang nilai harus diisi dengan ARN untuk topik SNS yang diberitahukan oleh SES.
Setelah SQS diatur, saatnya untuk satu perjalanan lagi kembali ke konsol SNS untuk mengonfigurasi topik Anda untuk memasukkan pemberitahuan ke dalam antrean SQS baru Anda yang mengkilap.
Di konsol SNS, pilih topik yang diberitahukan oleh SES. Dari sana, buat langganan baru. Protokol berlangganan harus Amazon SQS, dan tujuannya harus ARN dari antrean SQS yang baru saja Anda buat.
Setelah semua itu, sisi persamaan AWS harus sudah siap. Kami dapat menguji pekerjaan kami dengan mengirim email kepada diri kami sendiri. Kirim email ke domain yang dikonfigurasi dengan SES, lalu buka konsol SQS dan pilih antrian Anda. Anda seharusnya dapat melihat payload yang berisi email Anda.
Layanan Java untuk Menangani Email
Sekarang ke bagian yang menyenangkan! Di bagian ini, kita akan membuat layanan mikro sederhana yang mampu mengirim pesan dan memproses email masuk. Langkah pertama adalah menentukan API yang akan mengirim email ke meja dukungan kami atas nama pengguna.
Catatan singkat. Kami akan fokus pada komponen logika bisnis dari layanan ini, dan tidak akan mendefinisikan titik akhir REST atau lapisan persistensi.
Untuk membangun layanan Spring, kita akan menggunakan Spring Boot dan Maven. Kita dapat menggunakan Spring Initializer untuk menghasilkan proyek untuk kita, start.spring.io.
Untuk memulai, pom.xml kita akan terlihat seperti ini:
<?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>
Mengirim Email Dukungan Atas Nama Pengguna
Pertama, mari kita definisikan kacang untuk mengirim email ke meja dukungan kami atas nama pengguna. Tugas kacang ini adalah memproses pesan masuk dari ID pengguna, dan mengirim pesan itu ke alamat email meja dukungan yang telah ditentukan sebelumnya.
Mari kita mulai dengan mendefinisikan sebuah antarmuka.
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); }
Dan implementasi kosong:
@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 } }
Mari tambahkan juga AWS SDK ke pom kita, kita akan menggunakan klien SES untuk mengirim email kita:
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>
Hal pertama yang perlu kita lakukan adalah membuat alamat email untuk mengirim pesan pengguna kita. Alamat yang kami hasilkan akan memainkan peran penting di sisi konsumsi layanan kami. Itu perlu berisi informasi yang cukup untuk mengarahkan balasan meja bantuan kembali ke pengguna asal.
Untuk mencapai ini, kami akan menyertakan ID pengguna asal di alamat email yang kami buat. Untuk menjaga semuanya tetap bersih, kita akan membuat objek yang berisi ID pengguna dan menggunakan string JSON yang disandikan Base64 sebagai alamat email.
Mari buat kacang baru yang bertanggung jawab untuk mengubah ID pengguna menjadi alamat 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); }
Mari kita mulai implementasi kita dengan menambahkan persetujuan yang diperlukan dan kelas dalam sederhana yang akan membantu kita membuat serial JSON kita.
@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; } } }
Membuat alamat email kita sangatlah mudah, yang perlu kita lakukan hanyalah membuat objek UserDetails dan Base64 menyandikan representasi JSON. Versi selesai dari metode createAddressForUserID kami akan terlihat seperti ini:
@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; }
Sekarang kita dapat kembali ke SupportBeanSesImpl dan memperbaruinya untuk menggunakan kacang email baru yang baru saja kita buat.
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); }
Untuk mengirim email, kita akan menggunakan klien AWS SES yang disertakan dengan 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 );
Kami menggunakan DefaultAWSCredentialsProviderChain untuk mengelola kredensial bagi kami, kelas ini akan mencari kredensial AWS seperti yang didefinisikan di sini.
Kami akan membuka kunci akses AWS yang dilengkapi dengan akses ke SES dan akhirnya SQS. Untuk info lebih lanjut, lihat dokumentasi dari Amazon.
Langkah selanjutnya adalah memperbarui metode messageSupport kami ke dukungan email menggunakan AWS SDK. SES SDK menjadikan ini proses yang mudah. Metode yang sudah selesai akan terlihat seperti ini:
@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); }
Untuk mencobanya, buat kelas uji dan masukkan file SupportBean. Pastikan SUPPORT_EMAIL_ADDRESS yang ditentukan di SupportBeanSesImpl menunjuk ke alamat email yang Anda miliki. Jika akun SES Anda di-sandbox, alamat ini juga perlu diverifikasi. Alamat email dapat diverifikasi di konsol SES di bawah bagian Alamat Email.
@Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }
Setelah menjalankan ini, Anda akan melihat pesan muncul di kotak masuk Anda. Lebih baik lagi, balas pesan dan periksa antrian SQS yang kami siapkan sebelumnya. Anda akan melihat muatan yang berisi balasan Anda.
Mengkonsumsi Balasan dari SQS
Langkah terakhir adalah membaca email dari SQS, menguraikan pesan email, dan mencari tahu ID pengguna apa yang harus dimiliki oleh balasan yang diteruskan.
Untuk mendengarkan pesan SQS baru, kami akan menggunakan SDK perpesanan Spring Cloud AWS. Ini akan memungkinkan kita untuk mengonfigurasi pendengar pesan SQS melalui anotasi, dan dengan demikian menghindari sedikit kode boilerplate.
Pertama, dependensi yang diperlukan.

Tambahkan ketergantungan perpesanan Spring Cloud:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>
Dan tambahkan Spring Cloud AWS ke manajemen ketergantungan pom Anda:
<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>
Saat ini, Spring Cloud AWS tidak mendukung konfigurasi berbasis anotasi, jadi kita harus mendefinisikan kacang XML. Untungnya kita tidak memerlukan banyak konfigurasi sama sekali, jadi definisi bean kita akan cukup ringan. Poin utama dari file ini adalah untuk mengaktifkan pendengar antrian yang didorong anotasi, ini akan memungkinkan kita untuk membubuhi keterangan metode sebagai SqsListener.
Buat file XML baru bernama aws-config.xml di folder sumber daya Anda. Definisi kita akan terlihat seperti ini:
<?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>
Bagian penting dari file ini adalah <aws-messaging:annotation-driven-queue-listener />
. Kami juga mendefinisikan wilayah default. Ini tidak perlu, tetapi melakukannya akan memungkinkan kami untuk mereferensikan antrian SQS kami berdasarkan nama, bukan URL. Kami tidak mendefinisikan kredensial AWS apa pun, dengan menghilangkannya, Spring akan menjadi default ke DefaultAWSCredentialsProviderChain, penyedia yang sama yang kami gunakan sebelumnya di kacang SES kami. Info lebih lanjut dapat ditemukan di dokumen Spring Cloud AWS.
Untuk menggunakan konfigurasi XML ini di aplikasi Spring Boot, kita perlu mengimpornya secara eksplisit. Buka kelas @SpringBootApplication Anda dan impor.
@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); } }
Sekarang mari kita definisikan kacang yang akan menangani pesan SQS yang masuk. Spring Cloud AWS memungkinkan kami mencapai ini dengan satu anotasi!
/** * 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); } }
Keajaiban di sini terletak pada anotasi @SqsListener. Dengan ini, Spring akan menyiapkan Pelaksana dan mulai mengumpulkan SQS untuk kami. Setiap kali pesan baru ditemukan, metode beranotasi kami akan dipanggil dengan isi pesan. Opsional, Spring Cloud dapat dikonfigurasi untuk menyusun pesan masuk, memberi Anda kemampuan untuk bekerja dengan objek yang diketik kuat di dalam pendengar antrian Anda. Selain itu, Anda memiliki kemampuan untuk memasukkan satu tajuk atau peta semua tajuk yang dikembalikan dari panggilan AWS yang mendasarinya.
Kami dapat menggunakan nama antrian logis di sini karena kami sebelumnya mendefinisikan wilayah di aws-config.xml, jika kami ingin menghilangkannya, kami dapat mengganti nilainya dengan URL SQS kami yang sepenuhnya memenuhi syarat. Kami juga mendefinisikan kebijakan penghapusan, ini akan mengonfigurasi Spring untuk menghapus pesan masuk dari SQS jika kondisinya terpenuhi. Ada beberapa kebijakan yang ditentukan dalam SqsMessageDeletionPolicy, kami mengonfigurasi Spring untuk menghapus pesan kami jika metode konsumsiSqsMessage kami berhasil dijalankan.
Kami juga menyuntikkan tajuk SQS yang dikembalikan ke metode kami menggunakan @Headers, dan peta yang disuntikkan akan berisi metadata yang terkait dengan antrean dan muatan yang diterima. Badan pesan disuntikkan menggunakan @NotificationMessage. Spring mendukung marshalling menggunakan Jackson, atau melalui konverter isi pesan khusus. Demi kenyamanan, kami hanya akan menyuntikkan string JSON mentah dan bekerja dengannya menggunakan kelas JSONObject yang disertakan dengan AWS SDK.
Payload yang diambil dari SQS akan berisi banyak data. Lihatlah JSONObject untuk membiasakan diri Anda dengan payload yang dikembalikan. Payload kami berisi data dari setiap layanan AWS yang dilaluinya, SES, SNS, dan terakhir SQS. Demi tutorial ini, kami benar-benar hanya peduli pada dua hal: daftar alamat email yang dikirimi ini dan badan email. Mari kita mulai dengan menguraikan 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); }
Karena di dunia nyata, meja bantuan kami mungkin menyertakan lebih dari sekadar pengirim asli dalam balasannya, kami ingin memverifikasi alamat sebelum kami menguraikan ID pengguna. Ini akan memberi meja dukungan kami kemampuan untuk mengirim pesan ke banyak pengguna secara bersamaan serta kemampuan untuk menyertakan pengguna non aplikasi .
Mari kembali ke antarmuka UserEmailBean dan menambahkan metode lain.
/** * Returns true if the input email address matches our template * @param emailAddress Email to check * @return true if it matches */ boolean emailMatchesUserFormat(String emailAddress);
Di UserEmailBeanJSONImpl, untuk menerapkan metode ini kita akan ingin melakukan dua hal. Pertama, periksa apakah alamatnya diakhiri dengan EMAIL_DOMAIN kami, lalu periksa apakah kami dapat menyusunnya.
@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("@")); }
Kami mendefinisikan dua metode baru, emailMatchesUserFormat yang baru saja kami tambahkan ke antarmuka kami, dan metode utilitas sederhana untuk memisahkan alamat email di @. Implementasi emailMatchesUserFormat kami bekerja dengan mencoba mendekode Base64 dan menyusun kembali bagian alamat ke kelas pembantu UserDetails kami. Jika ini berhasil, kami kemudian memeriksa untuk memastikan ID pengguna yang diperlukan telah diisi. Jika semua ini berhasil, kita dapat mengasumsikan kecocokan dengan aman.
Kembali ke EmailSqsListener kami dan masukkan UserEmailBean yang baru diperbarui.
private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }
Sekarang kita akan memperbarui consumeSqsMethod. Pertama mari kita urai badan email:
//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); }
Sekarang mari kita buat metode baru yang akan memproses alamat email dan badan email.
private void processEmail(String emailAddress, String emailBody){ }
Dan terakhir, perbarui loop email untuk memanggil metode ini jika menemukan kecocokan.
//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); } }
Sebelum kita mengimplementasikan processEmail, kita perlu menambahkan satu metode lagi ke UserEmailBean kita. Kami membutuhkan metode untuk mengembalikan ID pengguna dari email. Kembali ke antarmuka UserEmailBean untuk menambahkan metode terakhirnya.
/** * 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);
Tujuan metode ini adalah mengembalikan ID pengguna dari alamat yang diformat. Implementasinya akan mirip dengan metode verifikasi kami. Mari menuju ke UserEmailBeanJSONImpl dan isi metode ini.
@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; } }
Sekarang kembali ke EmailSqsListener kami dan perbarui processEmail untuk menggunakan metode baru ini.
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; } }
Besar! Sekarang kami memiliki hampir semua yang kami butuhkan. Hal terakhir yang perlu kita lakukan adalah menguraikan balasan dari pesan mentah.
Mengurai balasan dari email sebenarnya adalah tugas yang cukup rumit. Format pesan email tidak standar, dan variasi antara klien email yang berbeda bisa sangat besar. Tanggapan mentah juga akan mencakup lebih dari sekadar balasan dan tanda tangan. Pesan asli kemungkinan besar akan disertakan juga. Orang-orang pintar di Mailgun menyusun posting blog hebat yang menjelaskan beberapa tantangan. Mereka juga membuka sumber pendekatan berbasis pembelajaran mesin mereka untuk mem-parsing email, lihat di sini.
Pustaka Mailgun ditulis dengan Python, jadi untuk tutorial kita, kita akan menggunakan solusi berbasis Java yang lebih sederhana. Pengguna GitHub edlio menyusun parser email berlisensi MIT di Java berdasarkan salah satu perpustakaan GitHub. Kami akan menggunakan perpustakaan besar ini.
Pertama mari kita perbarui pom kita, kita akan menggunakan https://jitpack.io untuk menarik EmailReplyParser.
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
Sekarang tambahkan ketergantungan GitHub.
<dependency> <groupId>com.github.edlio</groupId> <artifactId>EmailReplyParser</artifactId> <version>v1.0</version> </dependency>
Kami juga akan menggunakan email Apache commons. Kita perlu mengurai email mentah ke dalam javax.mail MimeMessage sebelum meneruskannya ke EmailReplyParser. Tambahkan ketergantungan commons.
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
Sekarang kita dapat kembali ke EmailSqsListener dan menyelesaikan processEmail. Pada titik ini, kami memiliki ID pengguna asli dan badan email mentah. Satu-satunya hal yang tersisa untuk dilakukan adalah menguraikan balasan.
Untuk mencapai ini, kita akan menggunakan kombinasi javax.mail dan 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); }
Bungkus
Dan itu saja! Kami sekarang memiliki semua yang kami butuhkan untuk memberikan respons kepada pengguna asal!
Dalam artikel ini, kami melihat bagaimana Amazon Web Services dapat digunakan untuk mengatur saluran pipa yang kompleks. Meskipun dalam artikel ini, alur dirancang seputar email; alat yang sama ini dapat dimanfaatkan untuk merancang sistem yang lebih kompleks, di mana Anda tidak perlu khawatir tentang pemeliharaan infrastruktur dan sebagai gantinya dapat fokus pada aspek menyenangkan dari rekayasa perangkat lunak.