Antigravity Apps
AWSSESEmail InfrastructureAWS ServerlessArchitecture

Why AWS SES Is Still Our Favorite

7 min readBy Dhaval Nagar

Every new email project we see in 2026 reaches for Resend or SendGrid first. The DX is friendlier, the documentation is simple and the “send your first email in 60 seconds” demo actually works in 60 seconds. For products where email is one-way — transactional notifications, marketing blasts, password resets — that's the right call.

We're still on AWS SES. Not because we're AWS-loyal 😅 — for Triage and most of what we build, four things line up that don't line up on Resend or SendGrid: it lives inside the IAM permissions boundary we already work with, the inbound capability is a first-class primitive, it stays linearly priced as we scale, and the configuration surface is deeper because that's what a foundational infrastructure service is supposed to do.

AWS-native — one permissions boundary

Every other email service makes you create an API key, store it as a secret, manage rotation, and reason about an auth model your team doesn't otherwise touch. SES is just another AWS resource. The Lambda that processes inbound already has an IAM role. Add ses:SendEmail and ses:SendRawEmail to that role and it sends. No new credentials, no new vault, no new rotation policy. Everything sits inside the IAM permissions boundary you already audit.

It may sound trivial, but its very effective for AWS-native apps. For example, for multi-tenant apps like Triage it means every Lambda that sends has scoped permissions on which SES identities it can use, and the KMS encryption context that gates per-tenant secrets is the same set of guardrails that gates outbound. One permissions boundary, one audit surface, one set of policies. That's value Resend can't easily provide because their identity model lives outside your AWS infrastructure.

Email receiving as a first-class primitive

Both Resend (since November 2025) and SendGrid offer inbound — via webhook. You stand up a public HTTP endpoint, you write the receiver, you reason about an auth model parallel to your AWS infrastructure. SES treats inbound as a native AWS primitive: a receipt rule writes the raw .eml to S3, an SNS topic notifies you, you fan out to SQS, you process in Lambda. The same domain identity sends and receives. The same DNS records authenticate outbound and route inbound. The same Lambda role processes both halves of the conversation.

That matters because the products that justify their own email service today — AI inbox operators, support automation, customer-feedback loops, anything with reply-threading — all need the inbound half. With Resend or SendGrid, that's a second integration: a webhook endpoint you operate, an auth model parallel to your AWS posture, separate rate limits, separate failure modes. With SES, it's the same identity, the same IAM role, the same Lambda already processing the rest of your pipeline.

What it looks like in Triage

Triage is a multi-tenant AI inbox operator. Each tenant gets a subdomain like support@acme.inbox.appgambit.com. The architecture is exactly the AWS-primitive stack SES is built for:

  • One SES domain identity for inbox.appgambit.com covers every tenant subdomain. One configuration; unlimited tenants. We didn't need to provision a new sender identity per tenant — we just route on the recipient prefix.
  • A single catch-all receipt rule matches both *@inbox.appgambit.com and *@<anything>.inbox.appgambit.com, drops the raw .eml to S3, and publishes to SNS. SQS fans out to a classifier Lambda that derives the tenant from the recipient.
  • Outbound uses the SES v2 SendEmailCommand with explicit RFC headers — In-Reply-To, References — for threading, plus a custom X-Triage-Outbound-Tenant header for loop detection. If a tenant's Gmail forwards our BCC'd reply back to the inbound subdomain, the classifier sees the header and short-circuits before re-processing.
  • Per-tenant sender verification is two lines of SDK code. CreateEmailIdentityCommand registers a tenant's preferred sender (e.g. support@theircompany.com); SES automatically emails them the verification link. Idempotent. We never built a custom verification UI.

None of this is exotic. It's the AWS primitives stack the team already runs — IAM, S3, SNS, SQS, Lambda. SES drops in as one more primitive. Resend would have meant standing up a parallel inbound system. SendGrid's Inbound Parse would have meant a webhook receiver, a public endpoint, and a different auth model than everything else we run.

Cost-effective at scale

SES is pay-per-email with no monthly minimum and no per-domain charge. Roughly $0.10 per 1,000 emails outbound, same for inbound. Resend's plans start with a monthly fee and step up at fixed volume bands. SendGrid is similar. At low volume — under ~50K emails/month — the absolute difference is small enough that the DX gap is worth paying for. At high volume the gap turns into an order of magnitude.

A product sending a million emails a month pays around $100 on SES. The equivalent Resend or SendGrid tier is five to ten times that, plus per-domain or sub-user fees if you're multi-tenant. None of it matters until it does — and when it does, Picking the steady-pricing primitive on day one means you don't have to migrate when usage scales.

Configuration Overhead

Every list of “SES annoyances” has common points: more configuration than Resend and sandbox-to-production is a nightmare for many. That's true, but SES is a foundational AWS service, not a product. You configure sending identities. You configure DKIM and SPF and DMARC. You configure receipt rules. You configure your IP pool when reputation matters. So yes, it demands more setup than an API-key-only service, but that setup is still just configuring AWS resources — the same primitives you're already managing.

  • DNS configurations. MX, DKIM CNAMEs, SPF, DMARC. The records aren't hard, but you copy-paste them into a DNS provider after every deploy unless you bring DNS into IaC.
  • SNS notifications capped at 150 KB. Larger emails (any with a screenshot attachment) silently drop their inline payload. We work around with a parallel S3 ObjectCreated event — both paths fire, the classifier dedups. It works.
  • Sandbox to production is an exercise. You request production access, you justify your use case, you may go a couple of rounds. It's not difficult if you've implemented bounce/complaint handling and explained the workload properly — and it's the same diligence any responsible email setup needs anyway.
  • Docs are reference-style, not tutorial-style. Engineers ramp slower on SES than on Resend. The Resend docs are a small masterpiece; the SES docs are a service manual.

When we'd pick something else

In all fairness SES isn't the right answer for every email use case, for example - We'd reach for Resend when:

  • Email is purely one-way. Signup confirmations, password resets, transactional notifications, marketing. You'll never use the inbound half, and Resend's DX is real value for the team velocity.
  • You're not on AWS. SES forces you into IAM + SNS + SQS + S3 plumbing. If you're on a different cloud or no cloud, that's a heavy tax for the email layer alone.
  • You need analytics dashboards out of the box. SendGrid's deliverability UI, suppression management, and tracking is genuinely better than SES's for teams that don't want to build their own.

Pick the primitive that matches the shape of the work and focus of the business. For most SaaS products sending emails are just a communication utility and not a core feature. And in some use cases like Triage, emails are the protocol not just the communication utility. Both are legitimate; the “obvious choice” only hurts when the shape doesn't match. For Triage, email is the protocol — and the primitive that does both halves on one identity, in the AWS stack we already run, with no monthly minimum and no per-domain charge, is still SES.

Liked this? Get the next one in your inbox.

Short engineering posts — new SDE patterns, AI tools in practice, honest mistakes. A couple a week. No spam, unsubscribe any time.

Want more?