E-mail de entrada · 22 de setembro de 2026

Como receber e-mails com o Amazon SES: S3 e Lambda

Construa um pipeline de e-mail de entrada pronto para produção com Amazon SES, S3 e Lambda, incluindo segurança de MIME, roteamento por tenant, idempotência, novas tentativas e threads.

O caminho confiável mais curto para receber e-mails no Amazon SES é: verificar um domínio, apontar um registro MX para um endpoint de recebimento do SES, salvar cada mensagem aceita no S3 e então invocar o Lambda de forma assíncrona para fazer o parsing e persistir a mensagem. Coloque a ação do S3 antes da ação do Lambda. Trate o ID de mensagem do SES como chave de idempotência, use o destinatário do envelope SMTP para o roteamento e coloque em quarentena o conteúdo inseguro, em vez de confiar em cabeçalhos ou anexos.

A arquitetura a construir

Use um subdomínio dedicado, como inbound.example.com, a menos que o SES deva receber todos os e-mails do seu domínio raiz. Isso mantém os e-mails da aplicação separados das caixas de entrada dos funcionários e transforma um rollback em uma alteração de DNS, e não em uma migração de e-mail.

O fluxo em produção é:

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

As regras de recebimento do Amazon SES executam suas ações em ordem. A AWS documenta especificamente o padrão S3 primeiro, Lambda depois, para quando o código precisa do corpo da mensagem. Uma ação direta do Lambda recebe metadados e alguns cabeçalhos, não o corpo completo. O corpo continua sendo o objeto MIME bruto no S3 (conceitos de recebimento do AWS SES).

Essa separação é útil. A etapa voltada ao SMTP armazena a mensagem original rapidamente, enquanto o parsing, a indexação, as notificações e a lógica de negócio acontecem depois da aceitação. Uma falha temporária no banco de dados não deve obrigar o servidor de e-mail do remetente a repetir a transação SMTP.

1. Escolha uma região compatível e verifique o domínio

O recebimento de e-mails no SES está disponível apenas em algumas regiões da AWS. Escolha uma na lista atual de endpoints de recebimento do SES e mantenha os recursos de SES, Lambda, SNS e KMS nessa mesma região, a menos que a documentação da AWS correspondente permita explicitamente o contrário.

Crie uma identidade de domínio no SES para o domínio raiz ou subdomínio exato que vai receber os e-mails. A verificação do domínio exige publicar os registros DNS que o SES fornece. A verificação para recebimento comprova o controle do domínio; ela é separada da configuração da rota MX que envia o tráfego de entrada para o SES (guia de verificação de domínio da AWS).

Para um subdomínio dedicado, os registros DNS ficam, conceitualmente, assim:

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

Substitua us-east-1 pela região que você escolheu. A AWS documenta o valor MX como 10 inbound-smtp.<region>.amazonaws.com (guia de registros MX da AWS). Não aponte seu domínio raiz para o SES se as pessoas ainda recebem e-mails nele pelo Google Workspace, Microsoft 365 ou outro provedor de caixa de entrada.

Confira o registro publicado em mais de um resolvedor antes de testar:

dig MX inbound.example.com +short

A visibilidade no DNS só comprova que a rota foi publicada. Envie uma mensagem controlada para um endereço de teste e confirme que o SES a armazenou antes de dar a configuração por concluída.

2. Armazene a mensagem bruta antes de processá-la

Crie um bucket S3 privado, com acesso público bloqueado, uma política de ciclo de vida e as permissões de IAM mais restritas possíveis na prática. Em seguida, crie uma regra de recebimento no SES cuja condição de destinatário corresponda ao seu domínio de entrada ou a endereços específicos.

A primeira ação deve entregar a mensagem bruta ao S3. Um prefixo de objeto como inbound/ facilita delimitar regras de retenção e políticas de acesso. O SES armazena o conteúdo MIME bruto, sem modificações. Atualmente, a AWS documenta um máximo padrão de 40 MB quando as mensagens são salvas no S3, enquanto a ação do SNS que inclui a mensagem completa tem um máximo bem menor, de 150 KB (ação de recebimento S3 da AWS). Essa diferença de tamanho é o motivo de o S3 ser o padrão mais seguro para respostas e anexos do mundo real.

Se você ativar a configuração opcional de KMS do SES na ação de recebimento, leia os detalhes de criptografia com atenção. O SES usa criptografia do lado do cliente nesse recurso, não a criptografia comum do lado do servidor do S3, então seu leitor precisa descriptografar o objeto com um cliente compatível. Não ative isso sem pensar para depois descobrir, durante um incidente, que seu parser em Node não consegue ler os bytes armazenados.

Dê ao SES permissão para gravar apenas no bucket e prefixo pretendidos. Dê ao Lambda s3:GetObject somente nesse mesmo local. A função não precisa de permissões de administração do bucket.

3. Adicione uma ação assíncrona do Lambda

Coloque a ação do Lambda depois da ação do S3 na regra de recebimento e use invocação assíncrona, a menos que a função precise decidir se o SES deve continuar avaliando a regra. A AWS recomenda execução assíncrona para o processamento normal e reserva a execução síncrona para decisões sobre o fluxo de e-mail (ação de recebimento Lambda da AWS).

O mail.messageId atribuído pelo SES também é a chave do objeto no S3 quando nenhum prefixo está configurado. Com prefixo, adicione-o no início. O esqueleto em Node.js a seguir busca a mensagem bruta e faz o parsing. Empacote @aws-sdk/client-s3 e um parser MIME mantido, como o mailparser, junto com o artefato de deploy, e fixe suas versões.

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; } } }

As funções de placeholder representam o armazenamento específico da aplicação, mas o contrato delas importa. claimOnce precisa usar uma restrição de unicidade no banco de dados ou uma escrita condicional sobre o ID de mensagem do provedor. Uma leitura seguida de um insert está sujeita a condição de corrida. Armazene o estado do processamento para que um operador consiga distinguir processing, complete, quarantined e failed.

Roteie pelo destinatário do envelope, não pelo cabeçalho To

Os campos visíveis To e Cc são conteúdo da mensagem fornecido pelo remetente. Eles podem omitir o destino real por causa de BCC, encaminhamento ou manipulação deliberada. As condições de recebimento do SES usam os destinatários do envelope SMTP, e a AWS orienta os processadores downstream a usar os destinatários da notificação do SES para decidir para onde a mensagem foi entregue (conceitos de recebimento da AWS).

Essa distinção evita um bug entre tenants. Se reply+tenant-a@inbound.example.com receber uma mensagem cujo To visível diz tenant-b@example.com, roteie-a usando o mapeamento autenticado da aplicação para o endereço do envelope, nunca o cabeçalho exibido.

Use um token de resposta aleatório e impossível de adivinhar quando um endereço identificar um cliente ou uma conversa. Armazene o hash do token, faça-o expirar quando for apropriado e rejeite endereços que não correspondam a um workspace ativo. Uma parte local previsível, como ticket-42, é um convite para injetar mensagens na thread de outro usuário.

Faça o parsing do MIME como entrada hostil

E-mail é um formato de entrada aninhado, com décadas de idade. A RFC 5322 define cabeçalhos e corpos de mensagem, enquanto o MIME adiciona conteúdo multipart e codificações de transferência (RFC 5322, RFC 2045). Use um parser mantido em vez de dividir o conteúdo por linhas em branco ou boundaries por conta própria.

Aplique limites antes de disponibilizar o conteúdo para o produto:

  • Limite o total de bytes decodificados, a quantidade de anexos, o tamanho de cada anexo, a profundidade de aninhamento MIME e o tempo de parsing.
  • Armazene anexos de forma privada, com nomes de objeto gerados. Nunca use o nome de arquivo do remetente como caminho.
  • Trate o tipo de conteúdo e o nome de arquivo declarados como dicas. Detecte o tipo a partir do conteúdo sempre que possível.
  • Nunca execute o conteúdo de anexos. Faça a varredura ou coloque os anexos em quarentena antes do download.
  • Sanitize o HTML com uma allowlist rigorosa, bloqueie imagens remotas por padrão e renderize-o em um contexto isolado. Prefira texto simples para análise automatizada.
  • Não coloque corpos brutos, endereços, tokens ou conteúdo de anexos nos logs comuns da aplicação.

O SES pode informar veredictos de SPF, DKIM, DMARC, spam e vírus, mas a AWS observa que o SES expõe esses resultados em vez de aplicar automaticamente sua política de negócio. Decida se as falhas devem ser rejeitadas, colocadas em quarentena ou exibidas com um aviso. Uma aprovação na autenticação identifica um domínio sob um mecanismo específico; ela não torna o conteúdo seguro nem prova que um humano o escreveu.

Torne as novas tentativas previsíveis

A invocação assíncrona do Lambda pode tentar novamente funções que falharam, e a AWS alerta que a entrega duplicada é possível mesmo quando a função não retorna erro. Configure um destino em caso de falha ou uma dead-letter queue e crie alarmes para falhas de processamento (comportamento de novas tentativas do AWS Lambda).

A idempotência deve cobrir todos os efeitos colaterais downstream:

  1. Insira o ID de mensagem do SES sob uma restrição de unicidade.
  2. Persista o conteúdo processado e os vínculos de thread em uma única transação sempre que possível.
  3. Coloque notificações, criação de tickets ou trabalho de agentes em uma outbox indexada pelo ID da mensagem mais o tipo de ação.
  4. Marque o registro como concluído somente depois que as escritas duráveis forem bem-sucedidas.
  5. Reprocesse a partir do objeto original no S3, não de uma entrada de log com perda de informação.

Se você acionar o processamento a partir de notificações do S3 em vez de uma ação do Lambda no SES, a mesma regra vale. As notificações do Amazon S3 são projetadas para entrega pelo menos uma vez e não têm garantia de chegar em ordem (notificações de eventos do AWS S3).

Agrupe mensagens em threads sem confiar no assunto

Use os campos Message-ID, In-Reply-To e References já processados para propor uma correspondência de thread. Não agrupe em threads apenas com base em um assunto que começa com Re:. Confirme também que o endereço do envelope ou o token de resposta pertence ao mesmo workspace e à mesma conversa antes de vincular qualquer coisa.

Respostas automáticas precisam de uma política própria. Detecte sinais como Auto-Submitted e evite gerar loops de resposta. A RFC 3834 recomenda identificação clara e comportamento conservador para respostas automáticas (RFC 3834). Se um agente de IA redigir uma resposta, mantenha o envio como um efeito colateral explícito e idempotente. Exija aprovação do usuário para destinatários inesperados, conteúdo sensível ou ações fora do fluxo original de suporte ou do produto. Receber uma mensagem não é consentimento geral para marketing sem relação com ela.

Lista de verificação para produção

  • A região de recebimento oferece suporte a e-mail de entrada do SES.
  • A identidade do domínio está verificada e o registro MX é resolvido corretamente.
  • A regra de recebimento tem uma condição de destinatário restrita e o conjunto de regras pretendido está ativo.
  • A ação do S3 é executada antes do processamento assíncrono do Lambda.
  • O bucket é privado, o acesso segue o privilégio mínimo e a retenção está documentada.
  • O ID da mensagem do SES tem uma restrição de unicidade no banco de dados.
  • O roteamento usa destinatários do envelope, não os cabeçalhos visíveis To ou Cc.
  • MIME, HTML, links e anexos são tratados como entrada não confiável.
  • Eventos com falha chegam a um destino monitorado e podem ser reproduzidos.
  • As correspondências de thread garantem a propriedade do workspace.
  • Respostas automatizadas têm prevenção de loop, limites de consentimento e idempotência de envio.
  • Um teste controlado abrange texto simples, HTML, BCC, entrega duplicada, anexos grandes, MIME malformado e falha do parser.

Usar o SES diretamente é uma boa opção quando sua equipe quer controle nativo da AWS e está preparada para cuidar de DNS, IAM, parsing de MIME, isolamento de tenants, retenção, tratamento de novas tentativas e alertas operacionais. Se você prefere ter esses primitivos de aplicação por trás de uma API de e-mail mais enxuta, o SendHQ oferece endereços de entrada, mensagens retidas, threads e acesso com escopo por workspace, junto com o e-mail transacional de saída. Em qualquer caso, mantenha a mensagem bruta recuperável e torne cada ação downstream segura para reprocessamento.