Invio di e-mail in modo asincrono tramite AWS SES
Pubblicato: 2022-03-10La maggior parte delle applicazioni invia e-mail per comunicare con i propri utenti. Le e-mail transazionali sono quelle attivate dall'interazione dell'utente con l'applicazione, ad esempio quando si dà il benvenuto a un nuovo utente dopo la registrazione al sito, si fornisce all'utente un collegamento per reimpostare la password o si allega una fattura dopo che l'utente ha effettuato un acquisto. Tutti questi casi precedenti richiedono in genere l'invio di una sola e-mail all'utente. In altri casi, invece, l'applicazione ha bisogno di inviare molte più email, come quando un utente pubblica nuovi contenuti sul sito, e tutti i suoi follower (che, in una piattaforma come Twitter, possono ammontare a milioni di utenti) riceveranno un notifica. In quest'ultima situazione, non progettata correttamente, l'invio di e-mail può diventare un collo di bottiglia nell'applicazione.
Questo è quello che è successo nel mio caso. Ho un sito che potrebbe dover inviare 20 e-mail dopo alcune azioni attivate dall'utente (come le notifiche dell'utente a tutti i suoi follower). Inizialmente, si basava sull'invio delle e-mail tramite un popolare provider SMTP basato su cloud (come SendGrid, Mandrill, Mailjet e Mailgun), tuttavia la risposta all'utente richiedeva pochi secondi. Evidentemente, la connessione al server SMTP per inviare quelle 20 e-mail stava rallentando notevolmente il processo.
Dopo l'ispezione, ho scoperto le cause del problema:
- Connessione sincrona
L'applicazione si connette al server SMTP e attende un riconoscimento, in modo sincrono, prima di continuare l'esecuzione del processo. - Alta latenza
Mentre il mio server si trova a Singapore, il provider SMTP che stavo utilizzando ha i suoi server negli Stati Uniti, rendendo la connessione di andata e ritorno che richiede molto tempo. - Nessuna riutilizzabilità della connessione SMTP Quando si chiama la funzione per inviare un'e-mail, la funzione invia l'e-mail immediatamente, creando in quel momento una nuova connessione SMTP (non offre di raccogliere tutte le e-mail e inviarle tutte insieme alla fine della richiesta, sotto un unico SMTP connessione).
A causa del numero 1, il tempo che l'utente deve attendere per la risposta è legato al tempo necessario per inviare le e-mail. A causa del numero 2, il tempo per inviare un'e-mail è relativamente alto. E grazie al numero 3, il tempo per inviare 20 e-mail è 20 volte il tempo necessario per inviare un'e-mail. Mentre l'invio di una sola e-mail potrebbe non rendere l'applicazione terribilmente più lenta, l'invio di 20 e-mail lo fa sicuramente, influendo sull'esperienza dell'utente.
Vediamo come possiamo risolvere questo problema.
Prestare attenzione alla natura delle e-mail transazionali
Prima di tutto, dobbiamo notare che non tutte le email hanno la stessa importanza. Possiamo classificare ampiamente le e-mail in due gruppi: e-mail prioritarie e non prioritarie. Ad esempio, se l'utente ha dimenticato la password per accedere all'account, si aspetterà l'e-mail con il link per reimpostare la password immediatamente nella sua casella di posta; questa è un'e-mail prioritaria. Al contrario, l'invio di un'e-mail di notifica che qualcuno che seguiamo ha pubblicato nuovi contenuti non deve arrivare immediatamente nella casella di posta dell'utente; questa è un'e-mail non prioritaria.
La soluzione deve ottimizzare il modo in cui queste due categorie di email vengono inviate. Supponendo che ci saranno solo poche (forse 1 o 2) e-mail prioritarie da inviare durante il processo e la maggior parte delle e-mail saranno non prioritarie, progettiamo la soluzione come segue:
- Le e-mail prioritarie possono semplicemente evitare il problema dell'elevata latenza utilizzando un provider SMTP situato nella stessa regione in cui viene distribuita l'applicazione. Oltre a una buona ricerca, ciò comporta l'integrazione della nostra applicazione con l'API del provider.
- Le e-mail non prioritarie possono essere inviate in modo asincrono e in batch in cui molte e-mail vengono inviate insieme. Implementato a livello di applicazione, richiede uno stack tecnologico appropriato.
Definiamo successivamente lo stack tecnologico per inviare e-mail in modo asincrono.
Definire lo stack tecnologico
Nota: ho deciso di basare il mio stack sui servizi AWS perché il mio sito Web è già ospitato su AWS EC2. Altrimenti, avrei un sovraccarico dovuto allo spostamento dei dati tra le reti di diverse aziende. Tuttavia, possiamo implementare la nostra soluzione anche utilizzando altri fornitori di servizi cloud.
Il mio primo approccio è stato quello di creare una coda. Attraverso una coda, potrei fare in modo che l'applicazione non invii più le e-mail, ma pubblichi invece un messaggio con il contenuto e i metadati dell'e-mail in una coda, quindi un altro processo raccolga i messaggi dalla coda e invii le e-mail.
Tuttavia, durante il controllo del servizio di coda da AWS, chiamato SQS, ho deciso che non era una soluzione adeguata, perché:
- È piuttosto complesso da configurare;
- Un messaggio di coda standard può memorizzare solo fino a 256 kb di informazioni, il che potrebbe non essere sufficiente se l'e-mail ha allegati (una fattura per esempio). E anche se è possibile dividere un messaggio grande in messaggi più piccoli, la complessità cresce ancora di più.
Poi ho capito che potevo imitare perfettamente il comportamento di una coda attraverso una combinazione di altri servizi AWS, S3 e Lambda, che sono molto più facili da configurare. S3, una soluzione di archiviazione di oggetti cloud per archiviare e recuperare dati, può fungere da repository per il caricamento dei messaggi e Lambda, un servizio di elaborazione che esegue codice in risposta a eventi, può selezionare un messaggio ed eseguire un'operazione con esso.
In altre parole, possiamo impostare il nostro processo di invio e-mail in questo modo:
- L'applicazione carica un file con il contenuto dell'e-mail + i metadati in un bucket S3.
- Ogni volta che un nuovo file viene caricato nel bucket S3, S3 attiva un evento contenente il percorso del nuovo file.
- Una funzione Lambda seleziona l'evento, legge il file e invia l'e-mail.
Infine, dobbiamo decidere come inviare le email. Possiamo continuare a utilizzare il provider SMTP di cui disponiamo già, facendo interagire la funzione Lambda con le loro API, oppure utilizzare il servizio AWS per l'invio di e-mail, chiamato SES. L'uso di SES ha sia vantaggi che svantaggi:
Benefici:
- Molto semplice da usare da AWS Lambda (richiede solo 2 righe di codice).
- È più economico: le tariffe Lambda vengono calcolate in base al tempo necessario per eseguire la funzione, quindi la connessione a SES dall'interno della rete AWS richiederà meno tempo rispetto alla connessione a un server esterno, facendo terminare la funzione prima e costando meno . (A meno che SES non sia disponibile nella stessa regione in cui è ospitata l'applicazione; nel mio caso, poiché SES non è offerto nella regione del Pacifico asiatico (Singapore), dove si trova il mio server EC2, allora è meglio connettermi ad alcuni provider SMTP esterno con sede in Asia).
Svantaggi:
- Non vengono fornite molte statistiche per il monitoraggio delle nostre e-mail inviate e l'aggiunta di altre più potenti richiede uno sforzo extra (ad es. il monitoraggio della percentuale di e-mail aperte o dei collegamenti su cui si è fatto clic deve essere impostato tramite AWS CloudWatch).
- Se continuiamo a utilizzare il provider SMTP per inviare le e-mail prioritarie, non avremo le nostre statistiche tutte insieme in 1 posto.
Per semplicità, nel codice seguente useremo SES.
Abbiamo quindi definito la logica del processo e dello stack come segue: L'applicazione invia come di consueto le email prioritarie, ma per quelle non prioritarie carica un file con contenuto email e metadati su S3; questo file viene elaborato in modo asincrono da una funzione Lambda, che si collega a SES per inviare l'e-mail.
Iniziamo ad implementare la soluzione.
Differenziazione tra e-mail prioritarie e non prioritarie
In breve, tutto dipende dall'applicazione, quindi dobbiamo decidere via e-mail per e-mail. Descriverò una soluzione che ho implementato per WordPress, che richiede alcuni hack attorno ai vincoli della funzione wp_mail
. Per altre piattaforme, la strategia seguente funzionerà anche, ma molto probabilmente ci saranno strategie migliori, che non richiedono hack per funzionare.
Il modo per inviare un'e-mail in WordPress è chiamando la funzione wp_mail
e non vogliamo cambiarla (es: chiamando la funzione wp_mail_synchronous
o wp_mail_asynchronous
), quindi la nostra implementazione di wp_mail
dovrà gestire sia i casi sincroni che quelli asincroni, e dovrà sapere a quale gruppo appartiene l'e-mail. Sfortunatamente, wp_mail
non offre alcun parametro aggiuntivo da cui potremmo valutare queste informazioni, come si può vedere dalla sua firma:
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() )
Quindi, per scoprire la categoria dell'e-mail, aggiungiamo una soluzione hacky: per impostazione predefinita, facciamo appartenere un'e-mail al gruppo di priorità, e se $to
contiene un'e-mail particolare (es: [email protected]), oppure se $subject
inizia con una stringa speciale (es: “[Non-priority!]“), allora appartiene al gruppo non prioritario (e rimuoviamo l'e-mail o la stringa corrispondente dall'oggetto). wp_mail
è una funzione collegabile, quindi possiamo sovrascriverla semplicemente implementando una nuova funzione con la stessa firma sul nostro file functions.php. Inizialmente contiene lo stesso codice della funzione wp_mail
originale, che si trova nel file wp-includes/pluggable.php, per estrarre tutti i parametri:
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;
E quindi controlliamo se non è prioritario, nel qual caso eseguiamo il fork in una logica separata nella funzione send_asynchronous_mail
o, in caso contrario, continuiamo a eseguire lo stesso codice della funzione wp_mail
originale:
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 // ... }
Nella nostra funzione send_asynchronous_mail
, invece di caricare l'email direttamente su S3, aggiungiamo semplicemente l'email a una variabile globale $emailqueue
, da cui possiamo caricare tutte le email insieme su S3 in un'unica connessione alla fine della richiesta:
function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { global $emailqueue; if (!$emailqueue) { $emailqueue = array(); } // Add email to queue. Code continues below... }
Possiamo caricare un file per e-mail, oppure possiamo raggrupparli in modo che in 1 file contenga molte e-mail. Poiché $headers
contiene meta email (da, tipo di contenuto e set di caratteri, CC, BCC e campi di risposta), possiamo raggruppare le email ogni volta che hanno le stesse $headers
. In questo modo, queste e-mail possono essere caricate tutte nello stesso file su S3 e le metainformazioni $headers
verranno incluse solo una volta nel file, invece di una per 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 }
Infine, la funzione send_asynchronous_mail
restituisce true
. Si prega di notare che questo codice è hacky: true
normalmente significherebbe che l'e-mail è stata inviata correttamente, ma in questo caso non è nemmeno stata inviata e potrebbe fallire perfettamente. Per questo motivo, la funzione che chiama wp_mail
non deve trattare una true
risposta come "l'e-mail è stata inviata correttamente", ma un riconoscimento che è stata accodata. Ecco perché è importante limitare questa tecnica alle e-mail non prioritarie in modo che, in caso di errore, il processo possa continuare a riprovare in background e l'utente non si aspetti che l'e-mail sia già nella sua casella di posta:

function send_asynchronous_mail($to, $subject, $message, $headers, $attachments) { // Continued from above... // That's it! return true; }
Caricamento di e-mail su S3
Nel mio precedente articolo "Condivisione di dati tra più server tramite AWS S3", ho descritto come creare un bucket in S3 e come caricare file nel bucket tramite l'SDK. Tutto il codice seguente continua l'implementazione di una soluzione per WordPress, quindi ci colleghiamo ad AWS utilizzando l'SDK per PHP.
Possiamo estendere dalla classe astratta AWS_S3
(introdotta nel mio articolo precedente) per connetterci a S3 e caricare le email in un bucket "async-emails" alla fine della richiesta (attivata tramite wp_footer
hook). Tieni presente che dobbiamo mantenere l'ACL come "privato" poiché non vogliamo che le e-mail siano esposte a Internet:
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();
Iniziamo a scorrere le coppie di intestazioni => emaildata salvati nella variabile globale $emailqueue
e otteniamo una configurazione predefinita dalla funzione get_default_email_meta
per se le intestazioni sono vuote. Nel codice seguente, recupero solo il campo "da" dalle intestazioni (il codice per estrarre tutte le intestazioni può essere copiato dalla funzione originale 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... } } }
Infine, carichiamo le email su S3. Decidiamo quante email caricare per file con l'intenzione di risparmiare denaro. Le funzioni Lambda vengono addebitate in base al tempo necessario per l'esecuzione, calcolato su intervalli di 100 ms. Più tempo richiede una funzione, più diventa costosa.
L'invio di tutte le e-mail caricando 1 file per e-mail, quindi, è più costoso che caricare 1 file per molte e-mail, poiché l'overhead derivante dall'esecuzione della funzione viene calcolato una volta per e-mail, anziché solo una volta per molte e-mail, e anche perché l'invio di molte e-mail insieme riempie più a fondo gli intervalli di 100 ms.
Quindi carichiamo molte email per file. Quante email? Le funzioni Lambda hanno un tempo di esecuzione massimo (3 secondi per impostazione predefinita) e se l'operazione non riesce, continuerà a riprovare dall'inizio, non da dove ha avuto esito negativo. Quindi, se il file contiene 100 e-mail e Lambda riesce a inviare 50 e-mail prima che il tempo massimo sia scaduto, non riesce e riprova a eseguire nuovamente l'operazione, inviando nuovamente le prime 50 e-mail. Per evitare ciò, dobbiamo scegliere un numero di e-mail per file che siamo certi sia sufficiente per elaborare prima che scada il tempo massimo. Nella nostra situazione, potremmo scegliere di inviare 25 e-mail per file. Il numero di e-mail dipende dall'applicazione (le e-mail più grandi impiegheranno più tempo per essere inviate e il tempo per inviare un'e-mail dipenderà dall'infrastruttura), quindi dovremmo fare alcuni test per trovare il numero giusto.
Il contenuto del file è semplicemente un oggetto JSON, contenente il meta e-mail nella proprietà "meta" e il blocco di e-mail nella proprietà "emails":
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), ]); } } } }
Per semplicità, nel codice sopra, non sto caricando gli allegati su S3. Se le nostre e-mail devono includere allegati, dobbiamo utilizzare la funzione SES SendRawEmail
invece di SendEmail
(che viene utilizzata nello script Lambda di seguito).
Dopo aver aggiunto la logica per caricare i file con le email su S3, possiamo passare alla codifica della funzione Lambda.
Codificare lo script Lambda
Le funzioni Lambda sono anche chiamate funzioni serverless, non perché non vengano eseguite su un server, ma perché lo sviluppatore non deve preoccuparsi del server: lo sviluppatore fornisce semplicemente lo script e il cloud si occupa del provisioning del server, della distribuzione e eseguendo lo script. Pertanto, come accennato in precedenza, le funzioni Lambda vengono addebitate in base al tempo di esecuzione della funzione.
Il seguente script Node.js esegue il lavoro richiesto. Richiamata dall'evento S3 “Put”, che indica che sul bucket è stato creato un nuovo oggetto, la funzione:
- Ottiene il percorso del nuovo oggetto (sotto la variabile
srcKey
) e il bucket (sotto la variabilesrcBucket
). - Scarica l'oggetto, tramite
s3.getObject
. - Analizza il contenuto dell'oggetto, tramite
JSON.parse(response.Body.toString())
ed estrae le email e il meta email. - Scorre tutte le e-mail e le invia tramite
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); }); };
Successivamente, dobbiamo caricare e configurare la funzione Lambda su AWS, che prevede:
- Creazione di un ruolo di esecuzione che concede le autorizzazioni Lambda per accedere a S3.
- Creando un pacchetto .zip contenente tutto il codice, ovvero la funzione Lambda che stiamo creando + tutti i moduli Node.js richiesti.
- Caricamento di questo pacchetto su AWS utilizzando uno strumento CLI.
Come eseguire queste operazioni è spiegato correttamente sul sito AWS, nel Tutorial sull'utilizzo di AWS Lambda con Amazon S3.
Collegamento di S3 con la funzione Lambda
Infine, dopo aver creato il bucket e la funzione Lambda, è necessario collegarli entrambi insieme, in modo che ogni volta che viene creato un nuovo oggetto sul bucket, venga attivato un evento per eseguire la funzione Lambda. Per fare ciò, andiamo alla dashboard di S3 e facciamo clic sulla riga del bucket, che mostrerà le sue proprietà:

Quindi facendo clic su Proprietà, scorriamo fino alla voce "Eventi", e lì facciamo clic su Aggiungi una notifica e inseriamo i seguenti campi:
- Nome: nome della notifica, es: “EmailSender”;
- Eventi: “Put”, che è l'evento attivato quando viene creato un nuovo oggetto sul bucket;
- Invia a: “Funzione Lambda”;
- Lambda: nome della nostra nuova Lambda, es: “LambdaEmailSender”.

Infine, possiamo anche impostare il bucket S3 in modo che elimini automaticamente i file contenenti i dati dell'e-mail dopo un po' di tempo. Per questo, andiamo nella scheda Gestione del bucket e creiamo una nuova regola del ciclo di vita, definendo dopo quanti giorni devono scadere le email:

Questo è tutto. Da questo momento, quando si aggiunge un nuovo oggetto al bucket S3 con il contenuto e il meta per le e-mail, attiverà la funzione Lambda, che leggerà il file e si connetterà a SES per inviare le e-mail.
Ho implementato questa soluzione sul mio sito, ed è diventata di nuovo veloce: scaricando l'invio di email a un processo esterno, se le applicazioni inviano 20 o 5000 email non fa differenza, la risposta all'utente che ha attivato l'azione sarà immediato.
Conclusione
In questo articolo abbiamo analizzato perché l'invio di molte email transazionali in un'unica richiesta può diventare un collo di bottiglia nell'applicazione e abbiamo creato una soluzione per affrontare il problema: invece di connetterci al server SMTP dall'interno dell'applicazione (in modo sincrono), possiamo inviare le email da una funzione esterna, in modo asincrono, basato su uno stack di AWS S3 + Lambda + SES.
Inviando e-mail in modo asincrono, l'applicazione può riuscire a inviare migliaia di e-mail, ma la risposta all'utente che ha attivato l'azione non ne risentirà. Tuttavia, per garantire che l'utente non attenda l'arrivo dell'e-mail nella posta in arrivo, abbiamo anche deciso di dividere le e-mail in due gruppi, prioritarie e non prioritarie, e inviare solo le e-mail non prioritarie in modo asincrono. Abbiamo fornito un'implementazione per WordPress, che è piuttosto hacky a causa delle limitazioni della funzione wp_mail
per l'invio di e-mail.
Una lezione tratta da questo articolo è che le funzionalità serverless su un'applicazione basata su server funzionano abbastanza bene: i siti in esecuzione su un CMS come WordPress possono migliorare le proprie prestazioni implementando solo funzionalità specifiche sul cloud ed evitare una grande quantità di complessità derivante dalla migrazione siti altamente dinamici a un'architettura completamente serverless.