Amazon SES

AWS SES Email Not Received

AWS SES returned a Message ID but email never arrived. Diagnose template errors, sandbox limits, suppression-list, and bounces with SES event publishing.

Updated Jul 1, 2026

The short answer

"AWS SES email not received" means SES accepted your SendEmail/SendTemplatedEmail call and returned a Message ID, but the message never reached the inbox. The usual causes are a template RenderingFailure (missing template variable), the sandbox requiring verified recipients, the recipient being on a suppression list, a hard bounce, or ISP spam filtering. Enable SES event publishing to see exactly where it dropped.

A successful Amazon SES API call is not a guarantee of delivery. SendEmail and SendTemplatedEmail return a MessageId as soon as SES accepts the request for processing — what happens afterward (rendering, suppression checks, the SMTP handoff to the recipient's mail server, and the receiving ISP's own filtering) can still silently drop the message. That is why an email can show as "sent" in your logs yet never appear in the recipient's inbox.

What causes "AWS SES email not received"?

There is no single cause — it's a symptom. The common, AWS-documented reasons, roughly in the order worth checking:

  • Template RenderingFailure (missing variable). If you use SendTemplatedEmail and the TemplateData you pass is missing a variable the template references, SES accepts the call and returns a Message ID but then fails to render and never sends the email, with no error in the API response. SES emits a RenderingFailure event with a message like Attribute 'firstName' is not present in the rendering data. This is the cause the original version of this page described — it is real, but it is only one of several. (AWS re:Post)
  • Sandbox restrictions. A new SES account is in the sandbox, where you can only send to verified email addresses or domains (plus the mailbox simulator). Mail to any unverified recipient is rejected. (AWS docs)
  • Suppression list. If the recipient is on a suppression list, SES accepts the message but does not deliver it, and emits a bounce event with bounceType: Permanent. The exact bounceSubType tells you which list: OnAccountSuppressionList for your account-level suppression list (and OnTenantSuppressionList for a tenant-level list), or Suppressed for the global SES suppression list (an address with a recent history of producing hard bounces). Bounces suppressed via your account-level or tenant-level list (bounceSubType OnAccountSuppressionList / OnTenantSuppressionList) do not count toward your bounce-rate metric. Bounces suppressed via the global SES list (bounceSubType Suppressed) do count toward your account's bounce rate — so a chronically hard-bouncing address should still be removed from your mailing list even though SES "accepted" the send. (AWS docs)
  • Hard or soft bounce. A nonexistent mailbox produces a hard bounce (Permanent, not retried); a full mailbox or temporary server issue produces a soft bounce (Transient, retried for a period before SES gives up). The address shows as accepted but is rejected at the recipient's server.
  • ISP spam filtering. The recipient's provider accepted the message (a Delivery event with a 250 SMTP response) but routed it to spam/junk — often due to missing SPF/DKIM/DMARC alignment or poor sender reputation.

How do I fix "AWS SES email not received"?

The fix depends on the cause, so diagnose first by turning on SES event tracking, then act:

  1. Enable SES event publishing. Attach a configuration set to your sends and publish events to SNS, CloudWatch, or EventBridge. Track Send, RenderingFailure, Reject, Bounce, Complaint, and Delivery. The first event missing from a given Message ID tells you where it stopped.
  2. If you see RenderingFailure: read the SNS notification — it names the missing variable (Attribute 'x' is not present in the rendering data). Add that key to your TemplateData, or make the template tolerant of missing values. Verify the JSON keys match the template variable names exactly (case-sensitive).
  3. If you're in the sandbox: either verify the recipient identity, or request production access to send to arbitrary recipients.
  4. If the address is suppressed: check it via GetSuppressedDestination, and if it's a known-good address, remove it from your account-level list with DeleteSuppressedDestination after confirming the original bounce was transient. You can't interact with the global list directly — you override it by adding the address to (or keeping it off of) your account-level list.
  5. If you see a Delivery event but the user still can't find it: it reached the ISP — have the recipient check spam/junk, and confirm SPF, DKIM (via SES Easy DKIM), and a DMARC record are all aligned for your sending domain.

Note that identity verification status is per-AWS-Region — an identity verified in us-east-1 is not verified in eu-west-1. Sending from a region where the From/Source/Return-Path identity isn't verified is rejected.

If you send through Courier with SES as the provider, surface these same SES events in your provider configuration and check the message logs for the underlying SES Message ID, Bounce, and RenderingFailure details.

FAQ

Common questions

A returned Message ID only means SES accepted the request, not that it sent the mail. If the template references a variable that's missing from your TemplateData, SES fails rendering and silently drops the email, emitting a RenderingFailure event like "Attribute 'x' is not present in the rendering data." Enable event publishing to SNS to see the exact missing variable, then add it to TemplateData.

One API, every provider

Stop debugging raw provider errors

Courier connects to your email, SMS, and push providers, handles retries and failover, and surfaces delivery errors in plain language.

Last reviewed Jul 1, 2026. Courier is not affiliated with third-party providers; error behavior may vary by implementation.