Correo entrante · 22 de septiembre de 2026
Cómo recibir correo con Amazon SES: S3 y Lambda
Cree un canal de correo entrante listo para producción con Amazon SES, S3 y Lambda, con seguridad MIME, enrutamiento por inquilino, idempotencia, reintentos e hilos.
La vía confiable más corta para recibir correo con Amazon SES es: verificar un dominio, apuntar un registro MX a un endpoint de recepción de SES, guardar en S3 cada mensaje aceptado e invocar Lambda de forma asíncrona para analizarlo y persistirlo. Coloque la acción de S3 antes que la de Lambda. Trate el ID de mensaje de SES como clave de idempotencia, use el destinatario del sobre SMTP para el enrutamiento y ponga en cuarentena el contenido inseguro en lugar de confiar en los encabezados o los adjuntos.
La arquitectura que debe construir
Use un subdominio dedicado como inbound.example.com, salvo que SES deba recibir todo el correo de su dominio raíz. Así el correo de la aplicación queda separado de las bandejas de entrada de los empleados, y revertir el cambio es cuestión de DNS y no de una migración de correo.
El flujo en producción es:
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
Las reglas de recepción de Amazon SES ejecutan sus acciones en orden. AWS documenta específicamente el patrón S3 primero y Lambda después cuando el código necesita el cuerpo del mensaje. Una acción de Lambda directa recibe metadatos y algunos encabezados, no el cuerpo completo. El cuerpo permanece como objeto MIME sin procesar en S3 (conceptos de recepción de AWS SES).
Esta separación es útil. El paso expuesto a SMTP almacena rápidamente el mensaje original, mientras que el análisis, la indexación, las notificaciones y la lógica de negocio ocurren después de la aceptación. Un fallo temporal de la base de datos no debería obligar al servidor de correo del remitente a repetir la transacción SMTP.
1. Elija una región compatible y verifique el dominio
La recepción de correo de SES solo está disponible en determinadas regiones de AWS. Elija una de la lista actual de endpoints de recepción de SES y mantenga los recursos de SES, Lambda, SNS y KMS en esa región, salvo que la documentación de AWS correspondiente permita explícitamente otra cosa.
Cree una identidad de dominio de SES para el dominio raíz o subdominio exacto que recibirá el correo. La verificación del dominio requiere publicar los registros DNS que proporciona SES. La verificación para recepción demuestra que controla el dominio; es independiente de configurar la ruta MX que envía el tráfico entrante a SES (guía de verificación de dominios de AWS).
Para un subdominio dedicado, los registros DNS se ven conceptualmente así:
inbound.example.com. MX 10 inbound-smtp.us-east-1.amazonaws.com.
Sustituya us-east-1 por la región que haya elegido. AWS documenta el valor MX como 10 inbound-smtp.<region>.amazonaws.com (guía del registro MX de AWS). No apunte su dominio raíz a SES si todavía hay personas que reciben correo en él a través de Google Workspace, Microsoft 365 u otro proveedor de buzón.
Verifique el registro publicado desde más de un resolvedor antes de hacer pruebas:
dig MX inbound.example.com +short
Que el DNS sea visible solo demuestra que la ruta está publicada. Envíe un mensaje controlado a una dirección de prueba y confirme que SES lo almacenó antes de dar la configuración por terminada.
2. Almacene el mensaje sin procesar antes de procesarlo
Cree un bucket de S3 privado con el acceso público bloqueado, una política de ciclo de vida y los permisos de IAM más restringidos que sea práctico. Después, cree una regla de recepción de SES cuya condición de destinatario coincida con su dominio de entrada o con direcciones concretas.
La primera acción debe entregar el mensaje sin procesar a S3. Un prefijo de objeto como inbound/ facilita acotar las reglas de retención y las políticas de acceso. SES almacena el contenido MIME sin procesar y sin modificar. Actualmente AWS documenta un máximo predeterminado de 40 MB cuando los mensajes se guardan en S3, mientras que la acción de SNS que incluye el mensaje completo tiene un máximo mucho menor, de 150 KB (acción de recepción de S3 en AWS). Esa diferencia de tamaño es la razón por la que S3 es la opción predeterminada más segura para respuestas y adjuntos reales.
Si activa la opción de KMS de SES en la acción de recepción, lea con atención los detalles del cifrado. SES usa cifrado del lado del cliente para esa función, no el cifrado del lado del servidor habitual de S3, por lo que su lector debe descifrar el objeto con un cliente compatible. No la active a la ligera para descubrir en pleno incidente que su analizador de Node no puede leer los bytes almacenados.
Conceda a SES permiso para escribir solo en el bucket y el prefijo previstos. Conceda a Lambda s3:GetObject solo para esa misma ubicación. La función no necesita permisos de administración del bucket.
3. Agregue una acción de Lambda asíncrona
Coloque la acción de Lambda después de la acción de S3 en la regla de recepción y use la invocación asíncrona, salvo que la función deba decidir si SES continúa evaluando la regla. AWS recomienda la ejecución asíncrona para el procesamiento normal y reserva la ejecución síncrona para las decisiones sobre el flujo del correo (acción de recepción de Lambda en AWS).
El mail.messageId asignado por SES es también la clave del objeto en S3 cuando no hay ningún prefijo configurado. Si hay prefijo, antepóngalo. El siguiente esqueleto en Node.js obtiene el mensaje sin procesar y lo analiza. Empaquete @aws-sdk/client-s3 y un analizador MIME mantenido, como mailparser, con el artefacto de despliegue, y fije sus versiones.
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;
}
}
}
Las funciones de marcador de posición representan el almacenamiento propio de la aplicación, pero su contrato importa. claimOnce debe usar una restricción de unicidad de la base de datos o una escritura condicional sobre el ID de mensaje del proveedor. Una lectura seguida de una inserción provoca condiciones de carrera. Almacene el estado del procesamiento para que un operador pueda distinguir processing, complete, quarantined y failed.
Enrute con el destinatario del sobre, no con el encabezado To
Los campos visibles To y Cc son contenido del mensaje proporcionado por el remitente. Pueden omitir el destino real por BCC, reenvíos o manipulación deliberada. Las condiciones de recepción de SES usan los destinatarios del sobre SMTP, y AWS indica a los procesadores posteriores que usen los destinatarios de la notificación de SES para determinar dónde se entregó el mensaje (conceptos de recepción de AWS).
Esa distinción evita un error entre inquilinos. Si reply+tenant-a@inbound.example.com recibe un mensaje cuyo To visible dice tenant-b@example.com, enrútelo según la asignación autenticada de la aplicación para la dirección del sobre, nunca según el encabezado visible.
Use un token de respuesta aleatorio e imposible de adivinar cuando una dirección identifique a un cliente o una conversación. Guarde el token con hash, hágalo caducar cuando corresponda y rechace las direcciones que no correspondan a un espacio de trabajo activo. Una parte local predecible como ticket-42 es una invitación a inyectar mensajes en el hilo de otro usuario.
Analice el MIME como una entrada hostil
El correo es un formato de entrada anidado y con décadas de antigüedad. RFC 5322 define los encabezados y el cuerpo de los mensajes, mientras que MIME agrega contenido multiparte y codificaciones de transferencia (RFC 5322, RFC 2045). Use un analizador mantenido en lugar de dividir usted mismo por líneas en blanco o delimitadores.
Aplique límites antes de poner el contenido a disposición del producto:
- Limite el total de bytes decodificados, el número de adjuntos, el tamaño de cada adjunto, la profundidad de anidamiento MIME y el tiempo de análisis.
- Almacene los adjuntos de forma privada con nombres de objeto generados. Nunca use el nombre de archivo del remitente como ruta.
- Trate el tipo de contenido y el nombre de archivo declarados como indicios. Detecte el tipo a partir del contenido siempre que sea posible.
- Nunca ejecute el contenido de los adjuntos. Analice o ponga en cuarentena los adjuntos antes de permitir su descarga.
- Sanee el HTML con una lista de permitidos estricta, bloquee las imágenes remotas de forma predeterminada y renderícelo en un contexto aislado. Para el análisis automatizado, prefiera el texto sin formato.
- No incluya cuerpos sin procesar, direcciones, tokens ni contenido de adjuntos en los registros habituales de la aplicación.
SES puede informar veredictos de SPF, DKIM, DMARC, spam y virus, pero AWS aclara que SES expone estos resultados sin aplicar automáticamente su política de negocio. Decida si los fallos deben rechazarse, ponerse en cuarentena o mostrarse con una advertencia. Superar la autenticación identifica un dominio con un mecanismo concreto; no hace que el contenido sea seguro ni demuestra que lo haya escrito una persona.
Haga que los reintentos sean aburridos
La invocación asíncrona de Lambda puede reintentar las funciones fallidas, y AWS advierte que es posible una entrega duplicada incluso cuando la función no devuelve ningún error. Configure un destino en caso de fallo o una cola de mensajes fallidos (dead-letter queue) y cree alarmas para los fallos de procesamiento (comportamiento de reintentos de AWS Lambda).
La idempotencia debe cubrir todos los efectos secundarios posteriores:
- Inserte el ID de mensaje de SES con una restricción de unicidad.
- Persista el contenido analizado y los vínculos de hilo en una sola transacción siempre que sea posible.
- Coloque las notificaciones, la creación de tickets o el trabajo de los agentes en una bandeja de salida con clave formada por el ID de mensaje más el tipo de acción.
- Marque el registro como completo solo después de que las escrituras duraderas se hayan realizado correctamente.
- Vuelva a procesar a partir del objeto original de S3, no de una entrada de registro con pérdida de información.
Si activa el procesamiento mediante notificaciones de S3 en lugar de una acción de Lambda de SES, se aplica la misma regla. Las notificaciones de Amazon S3 están diseñadas para una entrega al menos una vez y no se garantiza que lleguen en orden (notificaciones de eventos de AWS S3).
Agrupe los mensajes en hilos sin fiarse del asunto
Use los campos analizados Message-ID, In-Reply-To y References para proponer una coincidencia de hilo. No agrupe en hilos basándose solo en un asunto que empiece por Re:. Confirme también que la dirección del sobre o el token de respuesta pertenecen al mismo espacio de trabajo y a la misma conversación antes de vincular nada.
Las respuestas automáticas necesitan una política propia. Detecte señales como Auto-Submitted y evite generar bucles de respuesta. RFC 3834 recomienda una identificación clara y un comportamiento conservador para las respuestas automáticas (RFC 3834). Si un agente de IA redacta una respuesta, mantenga el envío como un efecto secundario explícito e idempotente. Exija la aprobación del usuario para destinatarios inesperados, contenido sensible o acciones ajenas al flujo original de soporte o de producto. Recibir un mensaje no equivale a un consentimiento general para marketing no relacionado.
Lista de comprobación para producción
- La región de recepción admite correo entrante de SES.
- La identidad del dominio está verificada y el registro MX se resuelve correctamente.
- La regla de recepción tiene una condición de destinatario limitada y está activo el conjunto de reglas previsto.
- La acción de S3 se ejecuta antes del procesamiento asíncrono de Lambda.
- El bucket es privado, el acceso sigue el principio de privilegio mínimo y la retención está documentada.
- El ID de mensaje de SES tiene una restricción de unicidad en la base de datos.
- El enrutamiento usa destinatarios del sobre, no los encabezados visibles
ToniCc. - MIME, HTML, los enlaces y los adjuntos se tratan como entradas no confiables.
- Los eventos fallidos llegan a un destino monitoreado y se pueden reproducir.
- La coincidencia de hilos aplica la propiedad del espacio de trabajo.
- Las respuestas automáticas cuentan con prevención de bucles, límites de consentimiento e idempotencia de envío.
- Una prueba controlada cubre texto sin formato, HTML, BCC, entrega duplicada, adjuntos grandes, MIME malformado y fallos del analizador.
Usar SES directamente es una buena opción cuando su equipo quiere control nativo de AWS y está dispuesto a encargarse del DNS, IAM, el análisis MIME, el aislamiento entre inquilinos, la retención, la gestión de reintentos y las alertas operativas. Si prefiere tener esas primitivas de aplicación detrás de una API de correo más acotada, SendHQ ofrece direcciones de entrada, mensajes retenidos, hilos y acceso con alcance por espacio de trabajo, junto con correo transaccional saliente. En cualquier caso, mantenga el mensaje sin procesar recuperable y haga que cada acción posterior pueda reproducirse de forma segura.