Come gestire le e-mail automatiche inviate da un'applicazione Web


12

Sto progettando un'applicazione Web e mi chiedo come progettare l'architettura per gestire l'invio di e-mail automatizzate.

Attualmente ho questa funzione integrata nella mia app Web e le e-mail vengono inviate in base all'input / interazione dell'utente (come la creazione di un nuovo utente). Il problema è che la connessione diretta a un server di posta richiede un paio di secondi. Aumentando la mia applicazione, questo sarà un collo di bottiglia significativo in futuro.

Qual è il modo migliore per gestire l'invio di una grande quantità di e-mail automatizzate all'interno della mia architettura di sistema?

Non ci sarà una grande quantità di e-mail inviate (massimo 2000 al giorno). Le e-mail non devono essere inviate immediatamente, fino a 10 minuti di ritardo va bene.

Aggiornamento: l'accodamento dei messaggi è stato fornito come risposta, ma come sarebbe progettato? Questo verrebbe gestito nell'app e elaborato durante un periodo di inattività o devo creare una nuova "app di posta" o un servizio Web per gestire la coda?


Puoi darci un senso approssimativo della scala? Centinaia, migliaia o milioni di mail? Inoltre, le e-mail devono essere inviate immediatamente o è accettabile un piccolo ritardo?
yannis,

L'invio di e-mail implica la consegna di un messaggio SMTP a un host di posta ricevente, ma ciò non significa che il messaggio sia stato effettivamente recapitato. In modo così efficace, tutto l'invio di e-mail è asincrono e non ha senso fingere di "aspettare il successo".
Kilian Foth,

1
Non sto "aspettando il successo", ma devo aspettare che il server SMTP accetti la mia richiesta. @YannisRizos vedi aggiorna RE il tuo commento
Gaz_Edge

Per il 2000 (che è il tuo massimo descritto) funzionerà. Quando accadono in 10 ore lavorative sono 3 mail al minuto, il che è molto fattibile. Assicurati solo di aver impostato bene il tuo record DNS e che il provider accetti di inviarli in questi importi. Pensa anche a: "cos'è il mailserver inattivo?". Il carico di invio di 2000 mail non è qualcosa di cui preoccuparsi.
Luc Franken,

La risposta a dove è CRONTAB
Tulains Córdova,

Risposte:


15

L'approccio comune, come già menzionato Ozz , è una coda di messaggi . Dal punto di vista del design, una coda di messaggi è essenzialmente una coda FIFO , che è un tipo di dati piuttosto fondamentale:

Coda FIFO

Ciò che rende speciale una coda di messaggi è che mentre l'applicazione è responsabile della messa in coda, un processo diverso sarebbe responsabile della messa in coda. Nel gergo di accodamento, l'applicazione è il mittente dei messaggi e il processo di decodifica è il destinatario. L'ovvio vantaggio è che l'intero processo è asincrono, il destinatario funziona indipendentemente dal mittente, purché ci siano messaggi da elaborare. L'ovvio svantaggio è che hai bisogno di un componente aggiuntivo, il mittente, affinché tutto funzioni.

Poiché ora la tua architettura si basa su due componenti che scambiano messaggi, puoi usare il termine fantasia comunicazione tra processi .

In che modo l'introduzione di una coda influisce sul design dell'applicazione?

Alcune azioni nella tua applicazione generano email. L'introduzione di una coda di messaggi significherebbe che tali azioni dovrebbero ora inviare i messaggi alla coda (e nient'altro). Quei messaggi dovrebbero contenere la quantità minima assoluta di informazioni necessarie per costruire le e-mail quando il destinatario può elaborarle.

Formato e contenuto dei messaggi

Il formato e il contenuto dei tuoi messaggi dipende completamente da te, ma dovresti tenere a mente quanto più piccolo è il migliore. La tua coda dovrebbe essere la più veloce su cui scrivere ed elaborare il più possibile, lanciando una grande quantità di dati probabilmente creerà un collo di bottiglia.

Inoltre, diversi servizi di accodamento basati su cloud hanno restrizioni sulle dimensioni dei messaggi e possono dividere i messaggi più grandi. Non ti accorgerai, i messaggi divisi verranno serviti come uno quando li chiedi, ma ti verranno addebitati più messaggi (supponendo ovviamente che tu stia utilizzando un servizio che richiede una tariffa).

Design del ricevitore

Dato che stiamo parlando di un'applicazione web, un approccio comune per il tuo ricevitore sarebbe un semplice cron script. Funzionerebbe ogni xminuto (o secondi) e dovrebbe:

  • Pop nquantità di messaggi dalla coda,
  • Elaborare i messaggi (ovvero inviare le e-mail).

Nota che sto dicendo pop invece di get o fetch, perché il tuo destinatario non sta semplicemente recuperando gli elementi dalla coda, ma li sta anche cancellando (cioè rimuovendoli dalla coda o contrassegnandoli come elaborati). Come esattamente ciò accadrà dipende dall'implementazione della coda dei messaggi e dalle esigenze specifiche dell'applicazione.

Naturalmente quello che sto descrivendo è essenzialmente un'operazione batch , il modo più semplice di elaborare una coda. A seconda delle esigenze, potresti voler elaborare i messaggi in modo più complicato (ciò richiederebbe anche una coda più complicata).

Traffico

Il destinatario potrebbe prendere in considerazione il traffico e regolare il numero di messaggi che elabora in base al traffico nel momento in cui viene eseguito. Un approccio semplicistico sarebbe quello di prevedere le tue elevate ore di traffico in base ai dati sul traffico passato e supponendo che tu sia andato con uno script cron che viene eseguito ogni xminuto, potresti fare qualcosa del genere:

if( 
    now() > 2pm && now() < 7pm
) {
    process(10);
} else {
    process(100);
}

function process(count) {
    for(i=0; i<=count; i++) {
        message = dequeue();
        mail(message)
    }
}

Un approccio molto ingenuo e sporco, ma funziona. In caso contrario, l'altro approccio sarebbe quello di scoprire il traffico corrente del tuo server ad ogni iterazione e adattare di conseguenza il numero di elementi del processo. Per favore non ottimizzare il micro se non è assolutamente necessario, sprecheresti il ​​tuo tempo.

Memoria della coda

Se l'applicazione utilizza già un database, una sola tabella su di essa sarebbe la soluzione più semplice:

CREATE TABLE message_queue (
  id int(11) NOT NULL AUTO_INCREMENT,
  timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  processed enum('0','1') NOT NULL DEFAULT '0',
  message varchar(255) NOT NULL,
  PRIMARY KEY (id),
  KEY timestamp (timestamp),
  KEY processed (processed)
) 

Non è davvero più complicato di così. Ovviamente puoi renderlo complicato di cui hai bisogno, ad esempio puoi aggiungere un campo prioritario (il che significherebbe che questa non è più una coda FIFO, ma se davvero ne hai bisogno, chi se ne frega?). Potresti anche renderlo più semplice, saltando il campo elaborato (ma poi dovresti eliminare le righe dopo averle elaborate).

Una tabella di database sarebbe l'ideale per 2000 messaggi al giorno, ma probabilmente non si ridimensionerebbe bene per milioni di messaggi al giorno. Ci sono un milione di fattori da considerare, tutto nella tua infrastruttura gioca un ruolo nella scalabilità generale della tua applicazione.

In ogni caso, supponendo che tu abbia già identificato la coda basata su database come un collo di bottiglia, il passo successivo sarebbe quello di esaminare un servizio basato su cloud. Amazon SQS è l'unico servizio che ho usato e fatto quello che promette. Sono sicuro che ci sono alcuni servizi simili là fuori.

Anche le code basate sulla memoria sono qualcosa da considerare, specialmente per le code di breve durata. memcached è eccellente come memoria per la coda dei messaggi.

Qualunque sia lo spazio di archiviazione su cui decidi di costruire la tua coda, sii intelligente e astratto. Né il mittente né il destinatario devono essere collegati a una memoria specifica, altrimenti passare a una memoria diversa in un secondo momento sarebbe una PITA completa.

Approccio alla vita reale

Ho creato una coda di messaggi per e-mail molto simile a quella che stai facendo. Era su un progetto PHP e l'ho costruito attorno a Zend Queue , un componente di Zend Framework che offre diversi adattatori per diversi archivi. I miei depositi dove:

  • Array PHP per test unitari,
  • Amazon SQS in produzione,
  • MySQL su sviluppatori e ambienti di test.

I miei messaggi erano semplici come potevano essere, la mia applicazione ha creato piccoli array con le informazioni essenziali ( [user_id, reason]). L'archivio messaggi era una versione serializzata di quell'array (prima era il formato di serializzazione interno di PHP, poi JSON, non ricordo perché ho cambiato). La reasonè una costante e, naturalmente, ho un grande tavolo da qualche parte che le mappe reasondi spiegazioni più complete (sono riuscito a trasmettere circa 500 email ai clienti con la criptica reasoninvece del messaggio più piena una volta).

Ulteriori letture

standard:

Utensili:

Letture interessanti:


Wow. Quasi la migliore risposta che abbia mai ricevuto qui! Non posso ringraziarti abbastanza!
Gaz_Edge

Io e sono sicuro che milioni di altri usano questo FIFO con Gmail e Google Apps Script. un filtro Gmail identifica qualsiasi posta in arrivo in base a un criterio, e questo è tutto, li mette in coda. Uno script di Google Apps esegue ogni durata X, riceve i primi messaggi y, li invia, liquefa. Risciacqua e ripeti.
DavChana,

6

Hai bisogno di un qualche tipo di sistema di accodamento.

Un modo semplice potrebbe essere quello di scrivere su una tabella del database e disporre di un'altra riga di processo dell'applicazione esterna in questa tabella, ma ci sono molte altre tecnologie di accodamento che è possibile utilizzare.

Potresti avere un'importanza sulle e-mail in modo che alcune vengano attivate quasi immediatamente (ad esempio reimpostazione della password) e quelle di minore importanza potrebbero essere raggruppate per essere inviate in seguito.


hai un diagramma di architettura o un esempio che mostra come funziona? Ad esempio, la coda si trova in un'altra 'app', ad esempio un'app di posta, oppure viene elaborata dall'applicazione Web durante un periodo di inattività. O dovrei creare una sorta di servizio web per elaborarli?
Gaz_Edge

1
@Gaz_Edge La tua applicazione invia gli elementi alla coda. Un processo in background (molto probabilmente uno script cron) estrae x elementi dalla coda ogni n secondi e li elabora (nel tuo caso, invia l'e-mail). Una singola tabella di database funziona bene come archivio di coda per piccole quantità di elementi, ma in generale le operazioni di scrittura su un database sono costose e per quantità maggiori potresti voler cercare servizi come Amazon SQS .
yannis,

1
@Gaz_Edge Non sono sicuro di poterlo rappresentare in modo più semplice di quello che ho scritto "... scrivere su una tabella di database e avere un'altra riga di processo dell'applicazione esterna in questa tabella ...." e per la tabella, leggere "qualsiasi coda "qualunque tecnologia possa essere.
ozz

1
(cont ...) Puoi costruire il processo in background che cancella la coda in modo da tenere conto del tuo traffico, ad esempio puoi istruirlo per elaborare meno elementi (o nessuno) in momenti in cui il tuo server è sotto stress . Dovrai o prevedere quei tempi stressanti guardando i tuoi dati sul traffico passato (più facile di quanto sembri, ma con un ampio margine di errore) o facendo in modo che il tuo processo in background controlli lo stato del traffico ogni volta che viene eseguito (più accurato, ma il sovraccarico aggiunto è raramente necessario).
yannis,

@YannisRizos vuoi combinare i tuoi commenti in una risposta? Inoltre, i diagrammi e i progetti di architettura sarebbero utili (questa volta sono determinato a
risolverli

2

Non ci sarà una grande quantità di e-mail inviate (massimo 2000 al giorno).

Oltre alla coda, la seconda cosa da considerare è l'invio di e-mail tramite servizi specializzati: MailChimp, ad esempio (non sono affiliato a questo servizio). Altrimenti molti servizi di posta, come Gmail, invieranno presto le tue lettere in una cartella spam.


2

Ho modellato il mio sistema di coda in diverse 2 tabelle come;

CREATE TABLE [dbo].[wMessages](
  [Id] [uniqueidentifier]  NOT NULL,
  [FromAddress] [nvarchar](255) NOT NULL,
  [FromDisplayName] [nvarchar](255) NULL,
  [ToAddress] [nvarchar](255) NOT NULL,
  [ToDisplayName] [nvarchar](255) NULL,
  [Graph] [xml] NOT NULL,
  [Priority] [int] NOT NULL,
  PRIMARY KEY CLUSTERED ( [Id] ASC ))

CREATE TABLE [dbo].[wMessageStates](
  [MessageId] [uniqueidentifier] NOT NULL,
  [Status] [int] NOT NULL,
  [LastChange] [datetimeoffset](7) NOT NULL,
  [SendAfter] [datetimeoffset](7) NULL,
  [SendBefore] [datetimeoffset](7) NULL,
  [DeleteAfter] [datetimeoffset](7) NULL,
  [SendDate] [datetimeoffset](7) NULL,
  PRIMARY KEY CLUSTERED ( [MessageId] ASC )) ON [PRIMARY]
) ON [PRIMARY]

C'è una relazione 1-1 tra queste tabelle.

Tabella dei messaggi per la memorizzazione del contenuto del messaggio. Il contenuto effettivo (To, CC, BCC, Subject, Body ecc.) È serializzato nel campo Graph in formato XML. Altre informazioni da, a vengono utilizzate solo per segnalare problemi senza deserializzare il grafico. Separare questa tabella consente di partizionare il contenuto della tabella su un diverso spazio su disco. Una volta pronto per inviare un messaggio, è necessario leggere tutte le informazioni, pertanto non c'è nulla di sbagliato nel serializzare tutto il contenuto in una colonna con l'indice della chiave primaria.

Tabella MessageState per la memorizzazione dello stato del contenuto del messaggio con informazioni aggiuntive basate sulla data. Separare questa tabella consente di accedere rapidamente al meccanismo con indici aggiuntivi su memoria IO veloce. Altre colonne sono già autoesplicative.

È possibile utilizzare un pool di thread separato che analizza queste tabelle. Se l'applicazione e il pool risiedono nello stesso computer, è possibile utilizzare una classe EventWaitHandle per segnalare al pool dall'applicazione qualcosa che ha inserito queste tabelle, altrimenti la scansione periodica con un timeout è la migliore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.