Email in entrata · 22 settembre 2026

Come ricevere email con Amazon SES: S3 e Lambda

Costruisci una pipeline di email in entrata pronta per la produzione con Amazon SES, S3 e Lambda, con sicurezza MIME, instradamento per tenant, idempotenza, nuovi tentativi e thread.

Il percorso in entrata affidabile più breve con Amazon SES è: verifica un dominio, punta un record MX a un endpoint di ricezione SES, salva in S3 ogni messaggio accettato, poi invoca Lambda in modo asincrono per analizzarlo e salvarlo. Metti l'azione S3 prima dell'azione Lambda. Tratta l'ID del messaggio SES come chiave di idempotenza, usa il destinatario della busta SMTP per l'instradamento e metti in quarantena i contenuti non sicuri invece di fidarti di intestazioni o allegati.

L'architettura da costruire

Usa un sottodominio dedicato come inbound.example.com, a meno che SES non debba ricevere tutta la posta del tuo dominio principale. Così la posta dell'applicazione resta separata dalle caselle dei dipendenti, e un rollback diventa una modifica DNS invece di una migrazione della posta.

Il flusso in produzione è:

sender -> SES inbound SMTP endpoint -> active SES receipt rule -> S3 raw-message object -> asynchronous Lambda action -> MIME parser and policy checks -> application database and private attachment storage

Le receipt rule di Amazon SES eseguono le loro azioni in ordine. AWS documenta esplicitamente il pattern S3 prima e Lambda dopo quando il codice ha bisogno del corpo del messaggio. Un'azione Lambda diretta riceve i metadati e alcune intestazioni, non il corpo completo. Il corpo resta l'oggetto MIME grezzo in S3 (concetti di ricezione di AWS SES).

Questa separazione è utile. Il passaggio rivolto a SMTP archivia rapidamente il messaggio originale, mentre parsing, indicizzazione, notifiche e logica di business avvengono dopo l'accettazione. Un errore temporaneo del database non dovrebbe costringere il server di posta del mittente a ripetere la transazione SMTP.

1. Scegli una regione supportata e verifica il dominio

La ricezione di email con SES è disponibile solo in alcune regioni AWS. Scegline una dall'attuale elenco degli endpoint di ricezione SES, poi mantieni le risorse SES, Lambda, SNS e KMS in quella regione, a meno che la documentazione AWS pertinente non consenta esplicitamente altrimenti.

Crea un'identità di dominio SES per il dominio principale o il sottodominio esatto che riceverà la posta. La verifica del dominio richiede di pubblicare i record DNS forniti da SES. La verifica per la ricezione dimostra il controllo del dominio; è separata dalla configurazione della route MX che invia il traffico in entrata a SES (guida AWS alla verifica del dominio).

Per un sottodominio dedicato, i record DNS hanno concettualmente questo aspetto:

inbound.example.com. MX 10 inbound-smtp.us-east-1.amazonaws.com.

Sostituisci us-east-1 con la regione che hai scelto. AWS documenta il valore MX come 10 inbound-smtp.<region>.amazonaws.com (guida AWS al record MX). Non puntare il tuo dominio principale a SES se qualcuno riceve ancora posta lì tramite Google Workspace, Microsoft 365 o un altro provider di posta.

Prima di testare, verifica il record pubblicato da più di un resolver:

dig MX inbound.example.com +short

La visibilità nel DNS dimostra solo che la route è pubblicata. Invia un messaggio controllato a un indirizzo di test e verifica che SES lo abbia archiviato prima di considerare conclusa la configurazione.

2. Archivia il messaggio grezzo prima di elaborarlo

Crea un bucket S3 privato con l'accesso pubblico bloccato, una lifecycle policy e i permessi IAM più ristretti possibile. Poi crea una receipt rule SES la cui condizione sui destinatari corrisponda al tuo dominio in entrata o a indirizzi specifici.

La prima azione dovrebbe consegnare il messaggio grezzo a S3. Un prefisso degli oggetti come inbound/ semplifica la definizione dell'ambito delle regole di conservazione e delle policy di accesso. SES archivia il contenuto MIME grezzo e non modificato. AWS documenta attualmente un massimo predefinito di 40 MB quando i messaggi vengono salvati in S3, mentre l'azione SNS che include il messaggio completo ha un massimo molto più basso, di 150 KB (azione di ricezione S3 di AWS). Questa differenza di dimensione è il motivo per cui S3 è la scelta predefinita più sicura per risposte e allegati reali.

Se abiliti l'impostazione facoltativa KMS di SES sull'azione di ricezione, leggi con attenzione i dettagli sulla cifratura. Per quella funzionalità SES usa la cifratura lato client, non la normale cifratura lato server di S3, quindi il tuo lettore deve decifrare l'oggetto con un client compatibile. Non attivarla alla leggera per poi scoprire durante un incidente che il tuo parser Node non riesce a leggere i byte archiviati.

Concedi a SES il permesso di scrivere solo nel bucket e nel prefisso previsti. Concedi a Lambda s3:GetObject solo per quella stessa posizione. La funzione non ha bisogno di permessi di amministrazione del bucket.

3. Aggiungi un'azione Lambda asincrona

Posiziona l'azione Lambda dopo l'azione S3 nella receipt rule e usa l'invocazione asincrona, a meno che la funzione non debba decidere se SES deve continuare a valutare la regola. AWS consiglia l'esecuzione asincrona per l'elaborazione normale e riserva quella sincrona alle decisioni sul flusso della posta (azione di ricezione Lambda di AWS).

Il mail.messageId assegnato da SES è anche la chiave dell'oggetto S3 quando non è configurato alcun prefisso. Con un prefisso, anteponilo. Lo scheletro Node.js che segue recupera il messaggio grezzo e lo analizza. Includi nell'artefatto di deployment @aws-sdk/client-s3 e un parser MIME mantenuto come mailparser, fissandone le versioni.

import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { simpleParser } from "mailparser"; const s3 = new S3Client({}); const bucket = process.env.INBOUND_BUCKET; const prefix = process.env.INBOUND_PREFIX || "inbound/"; export async function handler(event) { for (const record of event.Records || []) { const ses = record.ses; const messageId = ses?.mail?.messageId; const recipients = ses?.receipt?.recipients || []; if (!messageId || !/^[A-Za-z0-9._-]+$/.test(messageId)) { throw new Error("Missing or invalid SES message ID"); } // claimOnce must be an atomic insert with a unique constraint. if (!(await claimOnce(messageId))) continue; try { const object = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: `${prefix}${messageId}`, })); const raw = Buffer.from(await object.Body.transformToByteArray()); const parsed = await simpleParser(raw, { skipHtmlToText: true, skipTextToHtml: true, }); await saveInboundMessage({ providerMessageId: messageId, envelopeRecipients: recipients, envelopeFrom: ses.mail.source, headerMessageId: parsed.messageId || null, inReplyTo: parsed.inReplyTo || null, references: parsed.references || [], subject: parsed.subject || "", text: parsed.text || "", html: parsed.html || null, attachments: parsed.attachments, receivedAt: ses.mail.timestamp, }); await markComplete(messageId); } catch (error) { await releaseOrMarkFailed(messageId, String(error)); throw error; } } }

Le funzioni segnaposto rappresentano l'archiviazione specifica dell'applicazione, ma il loro contratto è importante. claimOnce deve usare un vincolo di unicità del database o una scrittura condizionale sull'ID del messaggio del provider. Una lettura seguita da un inserimento è soggetta a race condition. Memorizza lo stato di elaborazione, così un operatore può distinguere processing, complete, quarantined e failed.

Instrada con il destinatario della busta, non con l'intestazione To

I campi visibili To e Cc sono contenuto del messaggio fornito dal mittente. Possono omettere la destinazione effettiva a causa di BCC, inoltri o manipolazioni deliberate. Le condizioni di ricezione di SES usano i destinatari della busta SMTP, e AWS indica ai processori a valle di usare i destinatari presenti nella notifica SES per stabilire dove è stato consegnato il messaggio (concetti di ricezione di AWS).

Questa distinzione previene un bug tra tenant. Se reply+tenant-a@inbound.example.com riceve un messaggio il cui To visibile indica tenant-b@example.com, instradalo usando la mappatura applicativa autenticata dell'indirizzo della busta, mai l'intestazione visualizzata.

Usa un token di risposta casuale e non indovinabile quando un indirizzo identifica un cliente o una conversazione. Salva l'hash del token, fallo scadere quando opportuno e rifiuta gli indirizzi che non corrispondono a un workspace attivo. Una parte locale prevedibile come ticket-42 è un invito a iniettare messaggi nel thread di un altro utente.

Tratta il MIME come input ostile

L'email è un formato di input annidato e vecchio di decenni. RFC 5322 definisce intestazioni e corpo del messaggio, mentre MIME aggiunge contenuti multipart e codifiche di trasferimento (RFC 5322, RFC 2045). Usa un parser mantenuto invece di dividere tu il testo su righe vuote o boundary.

Applica dei limiti prima di rendere il contenuto disponibile al prodotto:

  • Limita i byte decodificati totali, il numero di allegati, la dimensione di ogni allegato, la profondità di annidamento MIME e il tempo di parsing.
  • Archivia gli allegati in modo privato con nomi di oggetto generati. Non usare mai il nome file del mittente come percorso.
  • Tratta il content type e il nome file dichiarati come semplici indizi. Dove possibile, rileva il tipo dal contenuto.
  • Non eseguire mai il contenuto degli allegati. Scansiona o metti in quarantena gli allegati prima del download.
  • Sanifica l'HTML con una allowlist rigorosa, blocca le immagini remote per impostazione predefinita e renderizzalo in un contesto isolato. Per l'analisi automatizzata preferisci il testo semplice.
  • Non inserire corpi grezzi, indirizzi, token o contenuto degli allegati nei normali log dell'applicazione.

SES può riportare i verdetti SPF, DKIM, DMARC, spam e virus, ma AWS precisa che SES espone questi risultati senza applicare automaticamente la tua policy di business. Decidi se i fallimenti vanno rifiutati, messi in quarantena o mostrati con un avviso. Un esito positivo dell'autenticazione identifica un dominio secondo un meccanismo specifico; non rende sicuro il contenuto né dimostra che l'abbia scritto una persona.

Rendi i nuovi tentativi noiosi

L'invocazione asincrona di Lambda può ritentare le funzioni fallite, e AWS avverte che la consegna duplicata è possibile anche quando la funzione non restituisce un errore. Configura una destinazione on-failure o una dead-letter queue e imposta allarmi sugli errori di elaborazione (comportamento dei nuovi tentativi di AWS Lambda).

L'idempotenza dovrebbe coprire ogni effetto collaterale a valle:

  1. Inserisci l'ID del messaggio SES con un vincolo di unicità.
  2. Salva il contenuto analizzato e i collegamenti ai thread in un'unica transazione, dove possibile.
  3. Metti notifiche, creazione di ticket o lavoro degli agenti in una outbox con chiave composta da ID del messaggio e tipo di azione.
  4. Segna il record come completato solo dopo che le scritture durevoli sono riuscite.
  5. Rielabora a partire dall'oggetto S3 originale, non da una voce di log con perdita di informazioni.

Se attivi l'elaborazione dalle notifiche S3 invece che da un'azione Lambda di SES, vale la stessa regola. Le notifiche di Amazon S3 sono progettate per una consegna at-least-once e non è garantito che arrivino in ordine (notifiche di eventi di AWS S3).

Organizza i messaggi in thread senza fidarti dell'oggetto

Usa i campi Message-ID, In-Reply-To e References analizzati per proporre una corrispondenza di thread. Non raggruppare in thread basandoti solo su un oggetto che inizia con Re:. Prima di collegare qualsiasi cosa, verifica anche che l'indirizzo della busta o il token di risposta appartengano allo stesso workspace e alla stessa conversazione.

Le risposte automatiche richiedono una policy a parte. Rileva segnali come Auto-Submitted ed evita di generare loop di risposte. RFC 3834 raccomanda un'identificazione chiara e un comportamento prudente per le risposte automatiche (RFC 3834). Se un agente AI redige una risposta, mantieni l'invio come effetto collaterale esplicito e idempotente. Richiedi l'approvazione dell'utente per destinatari inattesi, contenuti sensibili o azioni al di fuori del flusso originale di supporto o di prodotto. Ricevere un messaggio non equivale a un consenso generale per marketing non correlato.

Checklist per la produzione

  • La regione di ricezione supporta le email in entrata di SES.
  • L'identità del dominio è verificata e il record MX viene risolto correttamente.
  • La regola di ricezione ha una condizione sul destinatario ristretta e il set di regole previsto è attivo.
  • L'azione S3 viene eseguita prima dell'elaborazione asincrona di Lambda.
  • Il bucket è privato, l'accesso segue il privilegio minimo e la conservazione è documentata.
  • L'ID messaggio SES ha un vincolo di unicità nel database.
  • L'instradamento usa i destinatari della busta, non le intestazioni visibili To o Cc.
  • MIME, HTML, link e allegati vengono trattati come input non attendibili.
  • Gli eventi non riusciti raggiungono una destinazione monitorata e possono essere riprodotti.
  • Le corrispondenze dei thread applicano i vincoli di appartenenza al workspace.
  • Le risposte automatiche dispongono di prevenzione dei loop, vincoli di consenso e idempotenza di invio.
  • Un test controllato copre testo semplice, HTML, BCC, consegna duplicata, allegati di grandi dimensioni, MIME non valido ed errore del parser.

SES diretto è una buona scelta quando il tuo team vuole un controllo nativo AWS ed è pronto a gestire DNS, IAM, parsing MIME, isolamento dei tenant, conservazione, gestione dei nuovi tentativi e avvisi operativi. Se vuoi queste primitive applicative dietro un'API email più essenziale, SendHQ offre indirizzi in entrata, messaggi conservati, thread e accesso con ambito limitato al workspace, insieme alle email transazionali in uscita. In ogni caso, mantieni recuperabile il messaggio grezzo e rendi ogni azione a valle sicura da rieseguire.