Trimiterea de e-mailuri asincron prin AWS SES
Publicat: 2022-03-10Majoritatea aplicațiilor trimit e-mailuri pentru a comunica cu utilizatorii lor. E-mailurile tranzacționale sunt cele declanșate de interacțiunea utilizatorului cu aplicația, cum ar fi atunci când întâmpinați un utilizator nou după înregistrarea pe site, oferind utilizatorului un link pentru a reseta parola sau atașarea unei facturi după ce utilizatorul face o achiziție. Toate aceste cazuri anterioare vor necesita de obicei trimiterea unui singur e-mail către utilizator. În alte cazuri, însă, aplicația trebuie să trimită mai multe e-mailuri, cum ar fi atunci când un utilizator postează conținut nou pe site, iar toți urmăritorii ei (care, într-o platformă precum Twitter, pot ajunge la milioane de utilizatori) vor primi un notificare. În această din urmă situație, nearhitectată corespunzător, trimiterea de e-mailuri poate deveni un blocaj în aplicație.
Așa s-a întâmplat în cazul meu. Am un site care poate avea nevoie să trimită 20 de e-mailuri după unele acțiuni declanșate de utilizator (cum ar fi notificările utilizatorilor către toți urmăritorii ei). Inițial, s-a bazat pe trimiterea e-mailurilor printr-un furnizor popular SMTP bazat pe cloud (cum ar fi SendGrid, Mandrill, Mailjet și Mailgun), dar răspunsul înapoi către utilizator ar dura câteva secunde. Evident, conectarea la serverul SMTP pentru a trimite acele 20 de e-mailuri a încetinit semnificativ procesul.
După inspecție, am aflat sursele problemei:
- Conexiune sincronă
Aplicația se conectează la serverul SMTP și așteaptă o confirmare, sincron, înainte de a continua execuția procesului. - Latență ridicată
În timp ce serverul meu este situat în Singapore, furnizorul SMTP pe care îl foloseam are serverele sale situate în SUA, ceea ce face ca conexiunea dus-întors să dureze considerabil. - Nicio reutilizare a conexiunii SMTP La apelarea funcției pentru a trimite un e-mail, funcția trimite imediat e-mailul, creând o nouă conexiune SMTP în acel moment (nu oferă să colectați toate e-mailurile și să le trimiteți pe toate împreună la sfârșitul solicitării, sub un singur SMTP conexiune).
Din cauza nr. 1, timpul pe care utilizatorul trebuie să aștepte răspunsul este legat de timpul necesar pentru a trimite e-mailurile. Din cauza nr. 2, timpul pentru a trimite un e-mail este relativ mare. Și din cauza nr. 3, timpul pentru a trimite 20 de e-mailuri este de 20 de ori mai mult decât timpul necesar pentru a trimite un e-mail. În timp ce trimiterea unui singur e-mail poate să nu facă aplicația teribil de lentă, trimiterea a 20 de e-mailuri cu siguranță o face, afectând experiența utilizatorului.
Să vedem cum putem rezolva această problemă.
Acordați atenție naturii e-mailurilor tranzacționale
Înainte de orice, trebuie să observăm că nu toate e-mailurile sunt egale ca importanță. Putem clasifica în linii mari e-mailurile în două grupuri: e-mailuri prioritare și neprioritare. De exemplu, dacă utilizatorul a uitat parola pentru a accesa contul, va aștepta imediat în căsuța de e-mail e-mailul cu linkul de resetare a parolei; acesta este un e-mail prioritar. În schimb, trimiterea unui e-mail prin care se anunță că cineva pe care îl urmărim a postat conținut nou nu trebuie să ajungă imediat în căsuța de e-mail a utilizatorului; acesta este un e-mail fără prioritate.
Soluția trebuie să optimizeze modul în care sunt trimise aceste două categorii de e-mailuri. Presupunând că vor fi doar câteva (poate 1 sau 2) e-mailuri prioritare care vor fi trimise în timpul procesului, iar cea mai mare parte a e-mail-urilor vor fi neprioritare, atunci proiectăm soluția după cum urmează:
- E-mailurile prioritare pot evita pur și simplu problema de latență ridicată folosind un furnizor SMTP situat în aceeași regiune în care este implementată aplicația. Pe lângă cercetarea bună, aceasta implică integrarea aplicației noastre cu API-ul furnizorului.
- E-mailurile fără prioritate pot fi trimise asincron și în loturi în care multe e-mailuri sunt trimise împreună. Implementat la nivel de aplicație, necesită o stivă de tehnologie adecvată.
Să definim în continuare stiva de tehnologie pentru a trimite e-mailuri asincron.
Definirea stivei de tehnologie
Notă: am decis să îmi bazez stiva pe serviciile AWS, deoarece site-ul meu este deja găzduit pe AWS EC2. Altfel, aș avea o suprasarcină de la mutarea datelor între rețelele mai multor companii. Cu toate acestea, putem implementa soluția noastră folosind și alți furnizori de servicii cloud.
Prima mea abordare a fost să înființez o coadă. Printr-o coadă, aș putea face ca aplicația să nu mai trimită e-mailurile, ci să publice un mesaj cu conținutul de e-mail și metadate într-o coadă, apoi să fac ca un alt proces să preia mesajele din coadă și să trimită e-mailurile.
Cu toate acestea, la verificarea serviciului de coadă de la AWS, numit SQS, am decis că nu este o soluție adecvată, deoarece:
- Este destul de complex de configurat;
- Un mesaj standard în coadă poate stoca doar 256 kb de informații, ceea ce poate să nu fie suficient dacă e-mailul are atașamente (o factură, de exemplu). Și chiar dacă este posibil să împărțiți un mesaj mare în mesaje mai mici, complexitatea crește și mai mult.
Apoi mi-am dat seama că aș putea imita perfect comportamentul unei cozi printr-o combinație de alte servicii AWS, S3 și Lambda, care sunt mult mai ușor de configurat. S3, o soluție de stocare a obiectelor în cloud pentru stocarea și preluarea datelor, poate acționa ca depozit pentru încărcarea mesajelor, iar Lambda, un serviciu de calcul care rulează cod ca răspuns la evenimente, poate alege un mesaj și executa o operațiune cu acesta.
Cu alte cuvinte, putem configura procesul nostru de trimitere a e-mailurilor astfel:
- Aplicația încarcă un fișier cu conținutul e-mailului + metadate într-o găleată S3.
- Ori de câte ori un fișier nou este încărcat în compartimentul S3, S3 declanșează un eveniment care conține calea către noul fișier.
- O funcție Lambda alege evenimentul, citește fișierul și trimite e-mailul.
În cele din urmă, trebuie să decidem cum să trimitem e-mailurile. Putem fie să folosim în continuare furnizorul SMTP pe care îl avem deja, având funcția Lambda să interacționeze cu API-urile lor, fie să folosim serviciul AWS pentru trimiterea de e-mailuri, numit SES. Utilizarea SES are atât avantaje, cât și dezavantaje:
Beneficii:
- Foarte simplu de utilizat din AWS Lambda (este nevoie doar de 2 linii de cod).
- Este mai ieftin: taxele Lambda sunt calculate în funcție de timpul necesar pentru a executa funcția, astfel încât conectarea la SES din cadrul rețelei AWS va dura mai puțin decât conectarea la un server extern, făcând ca funcția să se termine mai devreme și să coste mai puțin. . (Cu excepția cazului în care SES nu este disponibil în aceeași regiune în care este găzduită aplicația; în cazul meu, deoarece SES nu este oferit în regiunea Asia-Pacific (Singapore), unde se află serverul meu EC2, s-ar putea să mă conectez la unele Furnizor extern SMTP din Asia).
Dezavantaje:
- Nu sunt furnizate multe statistici pentru monitorizarea e-mailurilor noastre trimise, iar adăugarea unora mai puternice necesită un efort suplimentar (de exemplu: urmărirea procentului de e-mailuri deschise sau pe ce linkuri s-a făcut clic, trebuie configurată prin AWS CloudWatch).
- Dacă continuăm să folosim furnizorul SMTP pentru a trimite e-mailurile prioritare, atunci nu vom avea toate statisticile într-un singur loc.
Pentru simplitate, în codul de mai jos vom folosi SES.
Am definit apoi logica procesului și a stivei după cum urmează: Aplicația trimite e-mailuri prioritare ca de obicei, dar pentru cele neprioritare, încarcă un fișier cu conținut de e-mail și metadate pe S3; acest fișier este procesat asincron de o funcție Lambda, care se conectează la SES pentru a trimite e-mailul.
Să începem implementarea soluției.
Diferențierea dintre e-mailurile prioritare și cele fără prioritate
Pe scurt, totul depinde de aplicație, așa că trebuie să decidem cu privire la un e-mail prin e-mail. Voi descrie o soluție pe care am implementat-o pentru WordPress, care necesită câteva hack-uri în jurul constrângerilor de la funcția wp_mail
. Pentru alte platforme, strategia de mai jos va funcționa și ea, dar foarte posibil vor exista strategii mai bune, care nu necesită hack-uri pentru a funcționa.
Modul de a trimite un e-mail în WordPress este apelând funcția wp_mail
și nu vrem să o schimbăm (de exemplu: apelând fie funcția wp_mail_synchronous
, fie wp_mail_asynchronous
), așa că implementarea noastră a wp_mail
va trebui să gestioneze atât cazurile sincrone, cât și cele asincrone, și va trebui să știe cărui grup aparține e-mailul. Din păcate, wp_mail
nu oferă niciun parametru suplimentar din care să putem evalua aceste informații, așa cum se vede din semnătura sa:
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() )
Apoi, pentru a afla categoria e-mailului adăugăm o soluție hacky: implicit, facem ca un e-mail să aparțină grupului prioritar, iar dacă $to
conține un anumit e-mail (ex: [email protected]), sau dacă $subject
începe cu un șir special (ex.: „[Non-priority!]“), atunci aparține grupului non-priority (și eliminăm e-mailul sau șirul corespunzător din subiect). wp_mail
este o funcție conectabilă, așa că o putem suprascrie pur și simplu prin implementarea unei noi funcții cu aceeași semnătură în fișierul nostru functions.php. Inițial, conține același cod al funcției originale wp_mail
, aflat în fișierul wp-includes/pluggable.php, pentru a extrage toți parametrii:
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;
Și apoi verificăm dacă nu este prioritar, caz în care trecem la o logică separată sub funcția send_asynchronous_mail
sau, dacă nu este, continuăm să executăm același cod ca în funcția wp_mail
originală:
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 // ... }
În funcția noastră send_asynchronous_mail
, în loc să încărcăm e-mailul direct în S3, pur și simplu adăugăm e-mailul la o variabilă globală $emailqueue
, din care putem încărca toate e-mailurile împreună în S3 într-o singură conexiune la sfârșitul solicitării:
function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { global $emailqueue; if (!$emailqueue) { $emailqueue = array(); } // Add email to queue. Code continues below... }
Putem încărca un fișier pe e-mail, sau le putem grupa astfel încât într-un singur fișier să conțin mai multe e-mailuri. Deoarece $headers
conține meta e-mail (de la, tip conținut și set de caractere, CC, BCC și câmpuri de răspuns), putem grupa e-mailurile împreună ori de câte ori au aceleași $headers
. În acest fel, aceste e-mailuri pot fi încărcate toate în același fișier în S3, iar metainformațiile $headers
vor fi incluse o singură dată în fișier, în loc de o dată pe e-mail:
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 }
În cele din urmă, funcția send_asynchronous_mail
returnează true
. Vă rugăm să rețineți că acest cod este hacky: true
ar însemna în mod normal că e-mailul a fost trimis cu succes, dar în acest caz, nici măcar nu a fost trimis încă și ar putea eșua perfect. Din acest motiv, funcția care apelează wp_mail
nu trebuie să trateze un răspuns true
ca „e-mailul a fost trimis cu succes”, ci o confirmare că a fost pus în coadă. De aceea, este important să restricționați această tehnică la e-mailurile neprioritare, astfel încât, dacă nu reușește, procesul poate continua să reîncerce în fundal, iar utilizatorul nu se va aștepta ca e-mailul să fie deja în căsuța ei de e-mail:

function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { // Continued from above... // That's it! return true; }
Încărcarea e-mailurilor pe S3
În articolul meu anterior „Partajarea datelor între mai multe servere prin AWS S3”, am descris cum să creez un compartiment în S3 și cum să încărcați fișiere în compartiment prin SDK. Tot codul de mai jos continuă implementarea unei soluții pentru WordPress, prin urmare ne conectăm la AWS folosind SDK-ul pentru PHP.
Ne putem extinde de la clasa abstractă AWS_S3
(introdusă în articolul meu anterior) pentru a ne conecta la S3 și a încărca e-mailurile într-o găleată „e-mail-uri asincrone” la sfârșitul solicitării (declanșată prin cârligul wp_footer
). Vă rugăm să rețineți că trebuie să păstrăm ACL-ul ca „privat”, deoarece nu dorim ca e-mailurile să fie expuse internetului:
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();
Începem să repetăm perechile de anteturi => emaildate salvate în variabila globală $emailqueue
și obținem o configurație implicită din funcția get_default_email_meta
dacă anteturile sunt goale. În codul de mai jos, recuperez doar câmpul „de la” din anteturi (codul pentru extragerea tuturor antetelor poate fi copiat din funcția originală wp_mail
):
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... } } }
În cele din urmă, încărcăm e-mailurile pe S3. Noi decidem câte e-mailuri să încărcăm per fișier cu intenția de a economisi bani. Funcțiile Lambda se încarcă în funcție de timpul de execuție, calculat pe intervale de 100 ms. Cu cât o funcție necesită mai mult timp, cu atât devine mai scumpă.
Trimiterea tuturor e-mailurilor prin încărcarea a 1 fișier per e-mail este, prin urmare, mai costisitoare decât încărcarea a 1 fișier pentru mai multe e-mailuri, deoarece suprasarcina de la executarea funcției este calculată o dată pe e-mail, în loc de o singură dată pentru multe e-mail-uri și, de asemenea, pentru că trimiterea multor e-mail-uri împreună umple intervalele de 100 ms mai bine.
Deci încărcăm multe e-mailuri per fișier. Câte e-mailuri? Funcțiile Lambda au un timp maxim de execuție (3 secunde în mod implicit), iar dacă operația eșuează, va continua să reîncerce de la început, nu de unde a eșuat. Deci, dacă fișierul conține 100 de e-mailuri, iar Lambda reușește să trimită 50 de e-mailuri înainte de expirarea timpului maxim, atunci eșuează și reîncearcă să execute din nou operația, trimițând din nou primele 50 de e-mailuri. Pentru a evita acest lucru, trebuie să alegem un număr de e-mailuri pe fișier pe care suntem siguri că este suficient să le procesăm înainte de expirarea timpului maxim. În situația noastră, am putea alege să trimitem 25 de e-mailuri per fișier. Numărul de e-mailuri depinde de aplicație (e-mailurile mai mari vor dura mai mult pentru a fi trimise, iar timpul de trimitere a unui e-mail va depinde de infrastructură), așa că ar trebui să facem câteva teste pentru a găsi numărul potrivit.
Conținutul fișierului este pur și simplu un obiect JSON, care conține meta e-mail sub proprietatea „meta” și bucata de e-mailuri sub proprietatea „e-mailuri”:
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), ]); } } } }
Pentru simplitate, în codul de mai sus, nu încarc atașamentele pe S3. Dacă e-mailurile noastre trebuie să includă atașamente, atunci trebuie să folosim funcția SES SendRawEmail
în loc de SendEmail
(care este folosită în scriptul Lambda de mai jos).
După ce a adăugat logica de a încărca fișierele cu e-mailuri pe S3, putem trece la codificarea funcției Lambda.
Codarea Scriptului Lambda
Funcțiile Lambda sunt numite și funcții fără server, nu pentru că nu rulează pe un server, ci pentru că dezvoltatorul nu trebuie să-și facă griji în privința serverului: dezvoltatorul pur și simplu furnizează scriptul, iar cloud-ul se ocupă de aprovizionarea serverului, de implementare și rulează scriptul. Prin urmare, așa cum am menționat mai devreme, funcțiile Lambda sunt taxate în funcție de timpul de execuție a funcției.
Următorul script Node.js face treaba necesară. Invocată de evenimentul „Put” S3, care indică faptul că un nou obiect a fost creat pe găleată, funcția:
- Obține calea noului obiect (sub variabila
srcKey
) și bucket (sub variabilasrcBucket
). - Descarcă obiectul, prin
s3.getObject
. - Analizează conținutul obiectului, prin
JSON.parse(response.Body.toString())
și extrage e-mailurile și meta-ul de e-mail. - Iterează prin toate e-mailurile și le trimite prin
ses.sendEmail
.
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); }); };
În continuare, trebuie să încărcăm și să configuram funcția Lambda în AWS, care implică:
- Crearea unui rol de execuție care acordă permisiuni Lambda pentru a accesa S3.
- Crearea unui pachet .zip care să conțină tot codul, adică funcția Lambda pe care o creăm + toate modulele Node.js necesare.
- Încărcarea acestui pachet în AWS utilizând un instrument CLI.
Cum să faceți aceste lucruri este explicat corect pe site-ul AWS, în Tutorial on Using AWS Lambda with Amazon S3.
Conectarea S3 cu funcția Lambda
În cele din urmă, având găleată și funcția Lambda create, trebuie să le conectăm pe ambele împreună, astfel încât ori de câte ori există un nou obiect creat pe găleată, acesta va declanșa un eveniment pentru a executa funcția Lambda. Pentru a face acest lucru, mergem la tabloul de bord S3 și facem clic pe rândul de găleți, care va afișa proprietățile acestuia:

Apoi, făcând clic pe Proprietăți, derulăm în jos la articolul „Evenimente” și acolo facem clic pe Adăugați o notificare și introducem următoarele câmpuri:
- Nume: numele notificării, de exemplu: „EmailSender”;
- Evenimente: „Put”, care este evenimentul declanșat atunci când un nou obiect este creat pe găleată;
- Trimite la: „Funcția Lambda”;
- Lambda: numele Lambda nou creat, de exemplu: „LambdaEmailSender”.

În cele din urmă, putem, de asemenea, seta găleata S3 pentru a șterge automat fișierele care conțin datele de e-mail după ceva timp. Pentru aceasta, mergem la fila Management a găleții și creăm o nouă regulă Lifecycle, definind după câte zile trebuie să expire e-mailurile:

Asta e. Din acest moment, la adăugarea unui nou obiect pe bucket-ul S3 cu conținutul și meta pentru e-mailuri, va declanșa funcția Lambda, care va citi fișierul și se va conecta la SES pentru a trimite e-mailurile.
Am implementat această soluție pe site-ul meu și a devenit rapid din nou: prin descărcarea trimiterii de e-mailuri către un proces extern, dacă aplicațiile trimit 20 sau 5000 de e-mailuri nu are nicio diferență, răspunsul către utilizatorul care a declanșat acțiunea va fi imediat.
Concluzie
În acest articol am analizat de ce trimiterea mai multor e-mailuri tranzacționale într-o singură solicitare poate deveni un blocaj în aplicație și am creat o soluție pentru a rezolva problema: în loc să ne conectăm la serverul SMTP din interiorul aplicației (sincron), putem trimite e-mailurile de la o funcție externă, asincron, pe baza unui stivă de AWS S3 + Lambda + SES.
Prin trimiterea de e-mailuri asincron, aplicația poate reuși să trimită mii de e-mailuri, dar răspunsul către utilizatorul care a declanșat acțiunea nu va fi afectat. Cu toate acestea, pentru a ne asigura că utilizatorul nu așteaptă să ajungă e-mailul în căsuța de e-mail, am decis, de asemenea, să împărțim e-mailurile în două grupuri, prioritare și neprioritare, și să trimitem numai e-mailurile neprioritare în mod asincron. Am furnizat o implementare pentru WordPress, care este destul de hackeră din cauza limitărilor funcției wp_mail
pentru trimiterea de e-mailuri.
O lecție din acest articol este că funcționalitățile fără server ale unei aplicații bazate pe server funcționează destul de bine: site-urile care rulează pe un CMS precum WordPress își pot îmbunătăți performanța prin implementarea doar a unor caracteristici specifice pe cloud și evită o mare complexitate care vine din migrare. site-uri foarte dinamice la o arhitectură complet fără server.