Skip to content

Email with SES

2 min read · main part only; folded sections are optional

After this you can answer: How does the confirmation email get sent, and what happens when sending fails?

flowchart TB
W["worker Lambda"] -->|"SendEmail<br/>From: no-reply@…"| S["SES"]
S -->|"signed with DKIM"| G["receiver<br/>(Gmail, …)"]
G -->|"look up the public key"| D["DNS: 3 CNAMEs<br/>at Cloudflare"]
  1. Prove the domain. The SES domain identity uses Easy DKIM (RSA 2048): AWS makes and rotates the keys. We add three CNAME records once, at Cloudflare, with the proxy off.
  2. SES checks the records on its own, for up to 72 hours. Until the identity is verified, every send fails.
  3. The worker sends a plain-text email with SendEmail. The worker role allows it only for our identities and only when ses:FromAddress is no-reply@minitickets.theversehub.in.
  4. The receiver checks the signature against the public key in our DNS. Signed mail is far less likely to land in spam.
  5. A refusal throws. The message is reported as failed, retried with backoff, and after 3 receives sits in the DLQ.

The account is in the SES sandbox, where every new account starts: 200 emails per 24 hours, 1 per second, and only TO verified addresses. That is why one test address (sesTestRecipient) is verified as well. A booking by any other address fails with “Email address is not verified” and ends in the DLQ, a live example of the failing-email flow. Leaving the sandbox is a request to AWS, reviewed by a person.

The identities are always on: they are free, and the DNS records stay valid only while the identity exists. The worker logs only the recipient’s domain, never the address.

From → toWhat happensWhat allows it
SES email identities → Public DNSreceiving mail servers check the DKIM signature against our DNS
  • dns 3 CNAMEs <token>._domainkey.minitickets.theversehub.in → <token>.dkim.amazonses.com (stack output sesDkimRecords)createEmail
Worker Lambda → SES email identitiesonly when computeEnabled = trueSendEmail from no-reply@minitickets.theversehub.in
  • iam worker role SendBookingEmails: ses:SendEmail on our identity ARNs, condition ses:FromAddress = no-reply@minitickets.theversehub.increateWorkerRole
  • config env MAILER ses and MAIL_FROM no-reply@minitickets.theversehub.increateWorkerLambda
See SES and its DNS records on the map →

Amazon SES → Identities → minitickets.theversehub.in (DKIM: Successful); Account dashboard for the sandbox status.

SymptomCauseWhere to look
MessageRejected: Email address is not verifiedSandbox: the recipient is not verifiedWorker log, minitickets-dev/errors
Every send fails; the identity stays pendingDKIM CNAMEs missing, or proxied at CloudflareSES → Identities → DKIM
AccessDenied on ses:SendEmailThe From address differs from the one in the conditionWorker role policy
What does DKIM prove, and what does it not?

It proves the email came from a server holding our domain’s key and was not changed on the way. It says nothing about whether the content is wanted. SPF with a custom MAIL FROM and DMARC are not built yet.

Does more Lambda concurrency send more email?

No. SES has its own sending rate (1 per second in the sandbox). That is one reason the worker runs at most 5 batches at once.

What should happen to an address that can never receive mail?

Today it retries 3 times and reaches the DLQ. In production you would use SES’s suppression list and bounce events, so permanent failures are not retried.

Advanced: Details: the DNS records

Each record is <token>._domainkey.minitickets.theversehub.in CNAME <token>.dkim.amazonses.com, from the stack output sesDkimRecords. They are public, not secrets. Our DNS lives at Cloudflare, not Route 53, so they are added by hand.

Answer these without scrolling up. If one feels hard, reread that section.

  1. What is an SES identity?
  2. What do the three CNAME records hold?
  3. Which two things does the worker role’s SES statement restrict?
  4. What are the three sandbox limits?
  5. What happens to a booking email sent to an unverified address?

Redraw it from memory: worker → SES (From: no-reply@…) → receiver → DNS DKIM CNAMEs (check signature)

Try this: on the map, select SES and follow its arrow to the public DNS; read what allows it

Read this book online: https://book.theversehub.in/