AWS SES Üzerinden E-postaları Eşzamansız Olarak Gönderme

Yayınlanan: 2022-03-10
Kısa özet ↬ Düzgün bir şekilde tasarlanmadıysa, aynı anda birçok işlem e-postası göndermek, uygulama için bir darboğaz haline gelebilir ve kullanıcı deneyimini bozabilir. Sorunun bir kısmı, uygulama içinden SMTP sunucusuna eşzamanlı olarak bağlanmaktır. Bu makalede, AWS S3, Lambda ve SES'in bir kombinasyonunu kullanarak uygulama dışından asenkron olarak nasıl e-posta gönderileceğini keşfedeceğiz.

Çoğu uygulama, kullanıcılarıyla iletişim kurmak için e-posta gönderir. İşlemsel e-postalar, örneğin siteye kaydolduktan sonra yeni bir kullanıcıyı karşılarken, kullanıcıya şifreyi sıfırlamak için bir bağlantı verirken veya kullanıcı bir satın alma yaptıktan sonra bir fatura eklerken olduğu gibi, kullanıcının uygulama ile etkileşimi tarafından tetiklenen e-postalardır. Tüm bu önceki durumlar, genellikle kullanıcıya yalnızca bir e-posta gönderilmesini gerektirir. Ancak bazı diğer durumlarda, örneğin bir kullanıcı sitede yeni içerik yayınladığında ve onun tüm takipçileri (Twitter gibi bir platformda milyonlarca kullanıcıya varabilir) gibi durumlarda uygulamanın çok daha fazla e-posta göndermesi gerekir. bildirim. Bu son durumda, düzgün bir şekilde tasarlanmadıysa, e-posta göndermek uygulamada bir darboğaz haline gelebilir.

Benim durumumda böyle oldu. Kullanıcı tarafından tetiklenen bazı işlemlerden sonra (tüm takipçilerine kullanıcı bildirimleri gibi) 20 e-posta göndermesi gerekebilecek bir sitem var. Başlangıçta, e-postaları popüler bir bulut tabanlı SMTP sağlayıcısı (SendGrid, Mandrill, Mailjet ve Mailgun gibi) aracılığıyla göndermeye dayanıyordu, ancak kullanıcıya geri dönüş saniyeler alacaktı. Açıkça görülüyor ki, bu 20 e-postayı göndermek için SMTP sunucusuna bağlanmak, süreci önemli ölçüde yavaşlatıyordu.

İncelemeden sonra, sorunun kaynaklarını öğrendim:

  1. senkron bağlantı
    Uygulama, SMTP sunucusuna bağlanır ve işlemin yürütülmesine devam etmeden önce eşzamanlı olarak bir onay bekler.
  2. Yüksek gecikme
    Sunucum Singapur'dayken, kullandığım SMTP sağlayıcısının sunucuları ABD'de olduğundan gidiş-dönüş bağlantısı oldukça zaman alıyor.
  3. SMTP bağlantısının yeniden kullanılabilirliği yok
    E-posta göndermek için işlevi çağırırken, işlev e-postayı hemen gönderir, o anda yeni bir SMTP bağlantısı oluşturur (tüm e-postaları toplayıp isteğin sonunda hepsini tek bir SMTP altında birlikte göndermeyi önermez). bağ).

#1 nedeniyle, kullanıcının yanıt için beklemesi gereken süre, e-postaları göndermek için geçen süreye bağlıdır. #2 nedeniyle, bir e-posta gönderme süresi nispeten yüksektir. Ve #3 nedeniyle, 20 e-posta gönderme süresi, bir e-posta gönderme süresinin 20 katıdır. Yalnızca bir e-posta göndermek uygulamayı çok yavaşlatmayabilir, ancak 20 e-posta göndermek kesinlikle kullanıcı deneyimini etkiler.

Bakalım bu sorunu nasıl çözebiliriz.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

İşlem E-postalarının Doğasına Dikkat Edilmesi

Her şeyden önce, tüm e-postaların önem açısından eşit olmadığını fark etmeliyiz. E-postaları genel olarak iki gruba ayırabiliriz: öncelikli ve öncelikli olmayan e-postalar. Örneğin, kullanıcı hesaba erişmek için parolayı unutursa, parola sıfırlama bağlantısını içeren e-postayı hemen gelen kutusunda bekler; bu öncelikli bir e-postadır. Buna karşılık, takip ettiğimiz birinin yeni içerik paylaştığını bildiren bir e-posta göndermek, kullanıcının gelen kutusuna hemen ulaşması gerekmez; bu öncelikli olmayan bir e-postadır.

Çözüm, bu iki e-posta kategorisinin nasıl gönderildiğini optimize etmelidir. İşlem sırasında gönderilecek yalnızca birkaç (belki 1 veya 2) öncelikli e-posta olacağını ve e-postaların büyük kısmının öncelikli olmayan e-postalar olacağını varsayarsak, çözümü aşağıdaki gibi tasarlarız:

  • Öncelikli e-postalar , uygulamanın dağıtıldığı bölgede bulunan bir SMTP sağlayıcısını kullanarak yüksek gecikme sorununu kolayca önleyebilir. İyi araştırmaya ek olarak, bu, uygulamamızın sağlayıcının API'si ile entegre edilmesini içerir.
  • Öncelikli olmayan e-postalar eşzamansız olarak ve birçok e-postanın birlikte gönderildiği gruplar halinde gönderilebilir. Uygulama düzeyinde uygulanan, uygun bir teknoloji yığını gerektirir.

Şimdi asenkron olarak e-posta göndermek için teknoloji yığınını tanımlayalım.

Teknoloji Yığınını Tanımlama

Not: Web sitem zaten AWS EC2'de barındırıldığından, yığınımı AWS hizmetlerine dayandırmaya karar verdim. Aksi takdirde, birkaç şirketin ağları arasında veri taşımanın bir yükü olurdu. Ancak çözümümüzü diğer bulut hizmeti sağlayıcılarını kullanarak da uygulayabiliriz.

İlk yaklaşımım bir kuyruk oluşturmaktı. Bir kuyruk aracılığıyla, uygulamanın artık e-postaları göndermemesini, bunun yerine e-posta içeriği ve meta verileriyle bir kuyrukta bir mesaj yayınlamasını ve ardından başka bir işlemin mesajları kuyruktan almasını ve e-postaları göndermesini sağlayabilirim.

Ancak AWS'den SQS adlı kuyruk hizmetini kontrol ederken bunun uygun bir çözüm olmadığına karar verdim, çünkü:

  • Kurulumu oldukça karmaşıktır;
  • Standart bir kuyruk mesajı, yalnızca en fazla 256 kb bilgi depolayabilir; bu, e-postanın ekleri varsa (örneğin bir fatura) yeterli olmayabilir. Ve büyük bir mesajı daha küçük mesajlara bölmek mümkün olsa da, karmaşıklık daha da artıyor.

Ardından, kurulumu çok daha kolay olan diğer AWS hizmetleri olan S3 ve Lambda'nın bir kombinasyonu aracılığıyla bir kuyruğun davranışını mükemmel bir şekilde taklit edebileceğimi fark ettim. Verileri depolamak ve almak için bir bulut nesnesi depolama çözümü olan S3, mesajların yüklenmesi için depo görevi görebilir ve olaylara yanıt olarak kod çalıştıran bir bilgi işlem hizmeti olan Lambda, bir mesaj seçip onunla bir işlem yürütebilir.

Başka bir deyişle, e-posta gönderme sürecimizi şu şekilde ayarlayabiliriz:

  1. Uygulama, e-posta içeriği + meta verileri içeren bir dosyayı bir S3 klasörüne yükler.
  2. S3 klasörüne yeni bir dosya yüklendiğinde, S3 yeni dosyanın yolunu içeren bir olayı tetikler.
  3. Bir Lambda işlevi olayı seçer, dosyayı okur ve e-postayı gönderir.

Son olarak, nasıl e-posta göndereceğimize karar vermeliyiz. Lambda işlevinin API'leriyle etkileşime girmesini sağlayarak halihazırda sahip olduğumuz SMTP sağlayıcısını kullanmaya devam edebilir veya e-posta göndermek için SES adı verilen AWS hizmetini kullanabiliriz. SES kullanmanın hem avantajları hem de dezavantajları vardır:

Faydalar:

  • AWS Lambda içinden kullanımı çok basittir (sadece 2 satır kod alır).
  • Daha ucuzdur: Lambda ücretleri, işlevi yürütmek için geçen süreye göre hesaplanır, bu nedenle AWS ağı içinden SES'e bağlanmak, harici bir sunucuya bağlanmaktan daha kısa sürer, bu da işlevin daha erken bitmesini ve daha az maliyetli olmasını sağlar. . (SES, uygulamanın barındırıldığı bölgede mevcut değilse; benim durumumda, SES, EC2 sunucumun bulunduğu Asya Pasifik (Singapur) bölgesinde sunulmadığı için, bazılarına bağlanmam daha iyi olabilir. Asya merkezli harici SMTP sağlayıcısı).

Dezavantajları:

  • Gönderilen e-postalarımızı izlemek için pek fazla istatistik sağlanmamıştır ve daha güçlü olanları eklemek için ekstra çaba gerekir (örneğin: e-postaların yüzde kaçının açıldığını veya hangi bağlantıların tıklandığını izlemek, AWS CloudWatch aracılığıyla ayarlanmalıdır).
  • Öncelikli e-postaları göndermek için SMTP sağlayıcısını kullanmaya devam edersek, istatistiklerimizi tek bir yerde toplamayacağız.

Basit olması için aşağıdaki kodda SES kullanacağız.

Daha sonra işlemin ve yığının mantığını şu şekilde tanımladık: Uygulama her zamanki gibi öncelikli e-postalar gönderir, ancak öncelikli olmayanlar için e-posta içeriği ve meta verileri içeren bir dosyayı S3'e yükler; bu dosya, e-postayı göndermek için SES'e bağlanan bir Lambda işlevi tarafından eşzamansız olarak işlenir.

Çözümü uygulamaya başlayalım.

Öncelikli ve Öncelikli Olmayan E-postalar Arasındaki Fark

Kısacası, bunların hepsi uygulamaya bağlıdır, bu nedenle e-posta bazında bir e-postaya karar vermemiz gerekiyor. WordPress için uyguladığım, wp_mail işlevindeki kısıtlamalar etrafında bazı wp_mail gerektiren bir çözümü anlatacağım. Diğer platformlar için aşağıdaki strateji de işe yarayacaktır, ancak büyük olasılıkla çalışmak için hack gerektirmeyen daha iyi stratejiler olacaktır.

WordPress'te bir e-posta göndermenin yolu wp_mail işlevini çağırmaktır ve bunu değiştirmek istemiyoruz (örneğin: wp_mail_synchronous veya wp_mail_asynchronous işlevini çağırarak), bu nedenle wp_mail uygulamamızın hem eşzamanlı hem de eşzamansız durumları ele alması gerekecek, ve e-postanın hangi gruba ait olduğunu bilmesi gerekir. Ne yazık ki, wp_mail , imzasından da anlaşılacağı gibi, bu bilgiyi değerlendirebileceğimiz herhangi bir ekstra parametre sunmuyor:

 function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() )

Ardından, e-postanın kategorisini bulmak için sahte bir çözüm ekliyoruz: varsayılan olarak, bir e-postayı öncelik grubuna ait yapıyoruz ve $to belirli bir e-posta içeriyorsa (örneğin: [email protected]) veya $subject özel bir dizeyle başlıyorsa (örneğin: “[Öncelikli değil!]“), o zaman öncelikli olmayan gruba aittir (ve ilgili e-postayı veya dizeyi konudan kaldırırız). wp_mail takılabilir bir fonksiyondur, bu yüzden function.php dosyamıza aynı imzaya sahip yeni bir fonksiyon uygulayarak basitçe onu geçersiz kılabiliriz. Başlangıçta, tüm parametreleri çıkarmak için wp-includes/pluggable.php dosyasında bulunan orijinal wp_mail işlevinin aynı kodunu içerir:

 if ( !function_exists( 'wp_mail' ) ) : function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) { $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) ); if ( isset( $atts['to'] ) ) { $to = $atts['to']; } if ( !is_array( $to ) ) { $to = explode( ',', $to ); } if ( isset( $atts['subject'] ) ) { $subject = $atts['subject']; } if ( isset( $atts['message'] ) ) { $message = $atts['message']; } if ( isset( $atts['headers'] ) ) { $headers = $atts['headers']; } if ( isset( $atts['attachments'] ) ) { $attachments = $atts['attachments']; } if ( ! is_array( $attachments ) ) { $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) ); } // Continue below... } endif;

Ve sonra bunun öncelikli olup olmadığını kontrol ederiz, bu durumda send_asynchronous_mail işlevi altında ayrı bir mantığa çatallanırız veya değilse, orijinal wp_mail işlevindekiyle aynı kodu yürütmeye devam ederiz:

 function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) { // Continued from above... $hacky_email = "[email protected]"; if (in_array($hacky_email, $to)) { // Remove the hacky email from $to array_splice($to, array_search($hacky_email, $to), 1); // Fork to asynchronous logic return send_asynchronous_mail($to, $subject, $message, $headers, $attachments); } // Continue all code from original function in wp-includes/pluggable.php // ... }

send_asynchronous_mail , e-postayı doğrudan S3'e yüklemek yerine, e-postayı küresel bir değişkene $emailqueue , buradan tüm e-postaları birlikte S3'e, isteğin sonunda tek bir bağlantıda yükleyebiliriz:

 function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { global $emailqueue; if (!$emailqueue) { $emailqueue = array(); } // Add email to queue. Code continues below... }

E-posta başına bir dosya yükleyebiliriz veya bunları bir dosyada birçok e-posta içerecek şekilde gruplayabiliriz. $headers e-posta metaları (içerik türü ve karakter kümesi, CC, BCC ve yanıt alanları) içerdiğinden, aynı $headers sahip olduklarında e-postaları birlikte gruplayabiliriz. Bu şekilde, bu e-postaların tümü aynı dosyada S3'e yüklenebilir ve $headers meta bilgileri e-posta başına bir kez yerine dosyaya yalnızca bir kez dahil edilir:

 function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { // Continued from above... // Add email to the queue $emailqueue[$headers] = $emailqueue[$headers] ?? array(); $emailqueue[$headers][] = array( 'to' => $to, 'subject' => $subject, 'message' => $message, 'attachments' => $attachments, ); // Code continues below }

Son olarak, send_asynchronous_mail işlevi true değerini döndürür. Lütfen bu kodun hacky olduğuna dikkat edin: true normalde e-postanın başarıyla gönderildiği anlamına gelir, ancak bu durumda, henüz gönderilmedi bile ve tamamen başarısız olabilir. Bu nedenle, wp_mail çağıran işlev, true bir yanıtı "e-posta başarıyla gönderildi" olarak değil, kuyruğa alındığının bir bildirimi olarak değerlendirmelidir. Bu nedenle, bu tekniği öncelikli olmayan e-postalarla sınırlamak önemlidir, böylece başarısız olursa, süreç arka planda yeniden denenebilir ve kullanıcı e-postanın zaten gelen kutusunda olmasını beklemez:

 function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { // Continued from above... // That's it! return true; }

E-postaları S3'e Yükleme

Bir önceki makalem olan “AWS S3 Üzerinden Birden Çok Sunucu Arasında Veri Paylaşımı” başlıklı yazımda S3'te nasıl kova oluşturulacağını ve SDK üzerinden kovaya nasıl dosya yükleneceğini anlatmıştım. Aşağıdaki tüm kodlar WordPress için bir çözümün uygulanmasına devam ediyor, bu nedenle PHP için SDK kullanarak AWS'ye bağlanıyoruz.

S3'e bağlanmak ve e-postaları isteğin sonunda ( wp_footer kancasıyla tetiklenen) bir "async-emails" kovasına yüklemek için soyut AWS_S3 sınıfından (önceki makalemde tanıtılmıştı) genişletebiliriz. E-postaların internete maruz kalmasını istemediğimiz için EKL'yi "gizli" tutmamız gerektiğine lütfen dikkat edin:

 class AsyncEmails_AWS_S3 extends AWS_S3 { function __construct() { // Send all emails at the end of the execution add_action("wp_footer", array($this, "upload_emails_to_s3"), PHP_INT_MAX); } protected function get_acl() { return "private"; } protected function get_bucket() { return "async-emails"; } function upload_emails_to_s3() { $s3Client = $this->get_s3_client(); // Code continued below... } } new AsyncEmails_AWS_S3();

$emailqueue global değişkeninde kaydedilen başlıklar => emaildata çiftlerini yinelemeye başlarız ve başlıklar boşsa get_default_email_meta işlevinden varsayılan bir yapılandırma alırız. Aşağıdaki kodda, başlıklardan yalnızca “from” alanını alıyorum (tüm başlıkları çıkaracak kod, wp_mail orijinal işlevinden kopyalanabilir):

 class AsyncEmails_AWS_S3 extends AWS_S3 { public function get_default_email_meta() { // Code continued from above... return array( 'from' => sprintf( '%s <%s>', get_bloginfo('name'), get_bloginfo('admin_email') ), 'contentType' => 'text/html', 'charset' => strtolower(get_option('blog_charset')) ); } public function upload_emails_to_s3() { // Code continued from above... global $emailqueue; foreach ($emailqueue as $headers => $emails) { $meta = $this->get_default_email_meta(); // Retrieve the "from" from the headers $regexp = '/From:\s*(([^\<]*?) <)? ?\s*\n/i'; if(preg_match($regexp, $headers, $matches)) { $meta['from'] = sprintf( '%s <%s>', $matches[2], $matches[3] ); } // Code continued below... } } } class AsyncEmails_AWS_S3 extends AWS_S3 { public function get_default_email_meta() { // Code continued from above... return array( 'from' => sprintf( '%s <%s>', get_bloginfo('name'), get_bloginfo('admin_email') ), 'contentType' => 'text/html', 'charset' => strtolower(get_option('blog_charset')) ); } public function upload_emails_to_s3() { // Code continued from above... global $emailqueue; foreach ($emailqueue as $headers => $emails) { $meta = $this->get_default_email_meta(); // Retrieve the "from" from the headers $regexp = '/From:\s*(([^\<]*?) <)? ?\s*\n/i'; if(preg_match($regexp, $headers, $matches)) { $meta['from'] = sprintf( '%s <%s>', $matches[2], $matches[3] ); } // Code continued below... } } }

Son olarak, e-postaları S3'e yüklüyoruz. Paradan tasarruf etmek amacıyla dosya başına kaç e-posta yükleneceğine biz karar veririz. Lambda işlevleri, yürütmeleri gereken süreye göre ücretlendirilir ve bu, 100 ms'lik aralıklar üzerinden hesaplanır. Bir fonksiyon ne kadar çok zaman gerektiriyorsa, o kadar pahalı hale gelir.

Tüm e-postaları e-posta başına 1 dosya yükleyerek göndermek, birçok e-posta başına 1 dosya yüklemekten daha pahalıdır, çünkü işlevin yürütülmesinden kaynaklanan ek yük, birçok e-posta için yalnızca bir kez değil, e-posta başına bir kez hesaplanır ve ayrıca birçok e-posta gönderilmesi nedeniyle birlikte 100ms'lik aralıkları daha kapsamlı bir şekilde doldurur.

Bu yüzden dosya başına birçok e-posta yüklüyoruz. Kaç e-posta? Lambda işlevlerinin maksimum yürütme süresi vardır (varsayılan olarak 3 saniye) ve işlem başarısız olursa, başarısız olduğu yerden değil baştan yeniden denemeye devam eder. Bu nedenle, dosya 100 e-posta içeriyorsa ve Lambda maksimum süre dolmadan 50 e-posta göndermeyi başarırsa, başarısız olur ve işlemi yeniden yürütmeyi deneyerek ilk 50 e-postayı bir kez daha gönderir. Bunu önlemek için, maksimum süre dolmadan önce işlemek için yeterli olduğuna emin olduğumuz dosya başına bir dizi e-posta seçmeliyiz. Bizim durumumuzda, dosya başına 25 e-posta göndermeyi seçebiliriz. E-postaların sayısı uygulamaya bağlıdır (daha büyük e-postaların gönderilmesi daha uzun sürer ve bir e-postanın gönderilme süresi altyapıya bağlıdır), bu nedenle doğru sayıyı bulmak için bazı testler yapmalıyız.

Dosyanın içeriği, yalnızca "meta" özelliği altındaki e-posta metasını ve "e-postalar" özelliği altındaki e-posta yığınını içeren bir JSON nesnesidir:

 class AsyncEmails_AWS_S3 extends AWS_S3 { public function upload_emails_to_s3() { // Code continued from above... foreach ($emailqueue as $headers => $emails) { // Code continued from above... // Split the emails into chunks of no more than the value of constant EMAILS_PER_FILE: $chunks = array_chunk($emails, EMAILS_PER_FILE); $filename = time().rand(); for ($chunk_count = 0; $chunk_count < count($chunks); $chunk_count++) { $body = array( 'meta' => $meta, 'emails' => $chunks[$chunk_count], ); // Upload to S3 $s3Client->putObject([ 'ACL' => $this->get_acl(), 'Bucket' => $this->get_bucket(), 'Key' => $filename.$chunk_count.'.json', 'Body' => json_encode($body), ]); } } } }

Basit olması için yukarıdaki kodda ekleri S3'e yüklemiyorum. E-postalarımızın ek içermesi gerekiyorsa, SendEmail (aşağıdaki Lambda komut dosyasında kullanılır) yerine SendRawEmail SES işlevini kullanmalıyız.

E-postalı dosyaları S3'e yüklemek için mantığı ekledikten sonra Lambda işlevini kodlamaya geçebiliriz.

Lambda Komut Dosyasını Kodlama

Lambda işlevleri, bir sunucuda çalışmadıkları için değil, geliştiricinin sunucu hakkında endişelenmesine gerek olmadığı için sunucusuz işlevler olarak da adlandırılır: geliştirici yalnızca komut dosyasını sağlar ve bulut, sunucunun sağlanması, dağıtılması ve komut dosyasını çalıştırıyor. Bu nedenle, daha önce belirtildiği gibi, Lambda işlevleri, işlev yürütme süresine göre ücretlendirilir.

Aşağıdaki Node.js betiği gerekli işi yapar. Kovada yeni bir nesnenin oluşturulduğunu gösteren S3 “Put” olayı tarafından çağrılan fonksiyon:

  1. Yeni nesnenin yolunu ( srcKey değişkeni altında) ve kovayı ( srcBucket değişkeni altında) alır.
  2. Nesneyi s3.getObject aracılığıyla indirir.
  3. Nesnenin içeriğini JSON.parse(response.Body.toString()) aracılığıyla ayrıştırır ve e-postaları ve e-posta metasını ayıklar.
  4. Tüm e-postaları yineler ve bunları ses.sendEmail aracılığıyla gönderir.
 var async = require('async'); var aws = require('aws-sdk'); var s3 = new aws.S3(); exports.handler = function(event, context, callback) { var srcBucket = event.Records[0].s3.bucket.name; var srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " ")); // Download the file from S3, parse it, and send the emails async.waterfall([ function download(next) { // Download the file from S3 into a buffer. s3.getObject({ Bucket: srcBucket, Key: srcKey }, next); }, function process(response, next) { var file = JSON.parse(response.Body.toString()); var emails = file.emails; var emailsMeta = file.meta; // Check required parameters if (emails === null || emailsMeta === null) { callback('Bad Request: Missing required data: ' + response.Body.toString()); return; } if (emails.length === 0) { callback('Bad Request: No emails provided: ' + response.Body.toString()); return; } var totalEmails = emails.length; var sentEmails = 0; for (var i = 0; i < totalEmails; i++) { var email = emails[i]; var params = { Destination: { ToAddresses: email.to }, Message: { Subject: { Data: email.subject, Charset: emailsMeta.charset } }, Source: emailsMeta.from }; if (emailsMeta.contentType == 'text/html') { params.Message.Body = { Html: { Data: email.message, Charset: emailsMeta.charset } }; } else { params.Message.Body = { Text: { Data: email.message, Charset: emailsMeta.charset } }; } // Send the email var ses = new aws.SES({ "region": "us-east-1" }); ses.sendEmail(params, function(err, data) { if (err) { console.error('Unable to send email due to an error: ' + err); callback(err); } sentEmails++; if (sentEmails == totalEmails) { next(); } }); } } ], function (err) { if (err) { console.error('Unable to send emails due to an error: ' + err); callback(err); } // Success callback(null); }); };

Ardından, aşağıdakileri içeren Lambda işlevini AWS'ye yüklemeli ve yapılandırmalıyız:

  1. S3'e erişmek için Lambda izinleri veren bir yürütme rolü oluşturma.
  2. Tüm kodu içeren bir .zip paketi oluşturma, yani oluşturduğumuz Lambda işlevi + gerekli tüm Node.js modülleri.
  3. Bu paketi bir CLI aracı kullanarak AWS'ye yükleme.

Bunların nasıl yapılacağı, AWS sitesinde, Amazon S3 ile AWS Lambda'yı Kullanma Eğitimi'nde düzgün bir şekilde açıklanmaktadır.

S3'ü Lambda İşleviyle Bağlama

Son olarak, kova ve Lambda işlevi oluşturulduktan sonra, her ikisini de birbirine bağlamamız gerekir, böylece kovada oluşturulan her yeni nesne, Lambda işlevini yürütmek için bir olayı tetikler. Bunu yapmak için S3 panosuna gidip özelliklerini gösterecek olan kova satırını tıklıyoruz:

S3 panosunda kova özelliklerini görüntüleme
Kovanın satırına tıklamak kovanın özelliklerini görüntüler. (Büyük önizleme)

Ardından Özellikler'e tıklayarak, “Etkinlikler” öğesine iniyoruz ve orada Bildirim ekle'ye tıklayıp aşağıdaki alanları giriyoruz:

  • Ad: bildirimin adı, örneğin: “EmailSender”;
  • Olaylar: Kova üzerinde yeni bir nesne oluşturulduğunda tetiklenen olay olan “Put”;
  • Gönder: “Lambda Fonksiyonu”;
  • Lambda: yeni oluşturulan Lambda'mızın adı, örneğin: “LambdaEmailSender”.
S3'ü Lambda ile kurma
Lambda için bir olayı tetiklemek için S3'te bir bildirim ekleme. (Büyük önizleme)

Son olarak, bir süre sonra e-posta verilerini içeren dosyaları otomatik olarak silmek için S3 kovasını da ayarlayabiliriz. Bunun için, paketin Yönetim sekmesine gidiyoruz ve e-postaların süresinin kaç gün sonra sona ereceğini tanımlayan yeni bir Yaşam Döngüsü kuralı oluşturuyoruz:

yaşam döngüsü kuralı
Dosyaları kovadan otomatik olarak silmek için bir Yaşam Döngüsü kuralı ayarlama. (Büyük önizleme)

Bu kadar. Bu andan itibaren, e-postalar için içerik ve meta ile S3 kovasına yeni bir nesne eklerken, dosyayı okuyacak ve e-postaları göndermek için SES'e bağlanacak olan Lambda işlevini tetikleyecektir.

Bu çözümü siteme uyguladım ve bir kez daha hızlandı: E-posta göndermeyi harici bir işleme devrederek, uygulamaların 20 veya 5000 e-posta göndermesi fark etmez, eylemi tetikleyen kullanıcıya verilecek yanıt şöyle olacaktır: acil.

Çözüm

Bu yazıda, tek bir istekte çok sayıda işlem e-postası göndermenin neden uygulamada darboğaz haline gelebileceğini analiz ettik ve sorunla başa çıkmak için bir çözüm oluşturduk: SMTP sunucusuna uygulama içinden (eşzamanlı olarak) bağlanmak yerine, e-postaları, bir AWS S3 + Lambda + SES yığınına dayalı olarak, harici bir işlevden eşzamansız olarak gönderin.

Uygulama eşzamansız olarak e-posta göndererek binlerce e-posta göndermeyi başarabilir, ancak eylemi tetikleyen kullanıcıya verilen yanıt etkilenmeyecektir. Ancak, kullanıcının e-postanın gelen kutusuna gelmesini beklememesini sağlamak için e-postaları öncelikli ve öncelikli olmayan iki gruba ayırmaya ve yalnızca öncelikli olmayan e-postaları eşzamansız olarak göndermeye karar verdik. E-posta göndermek için wp_mail işlevinin sınırlamaları nedeniyle oldukça zor olan WordPress için bir uygulama sağladık.

Bu makaleden bir ders, sunucu tabanlı bir uygulamadaki sunucusuz işlevlerin oldukça iyi çalıştığıdır: WordPress gibi bir CMS üzerinde çalışan siteler, bulutta yalnızca belirli özellikleri uygulayarak performanslarını artırabilir ve geçişten kaynaklanan büyük bir karmaşıklığı önleyebilir. tamamen sunucusuz bir mimariye son derece dinamik siteler.