Case study
Migrating from Resend to a custom-built email platform on AWS SES, Next.js, SQS, and PostgreSQL, designed and shipped with AI assistance.
Cost reduction
87%
Per 1,000 emails
$0.10
Build and ship
4 wks
Our organization was sending a mix of transactional and marketing emails through Resend. Password resets, welcome sequences, course enrollment confirmations, digest emails. The product was growing, and with it, the email bill.
Resend is a genuinely good product. Clean API, React Email integration, excellent developer experience. But as our monthly volume crossed the 100k mark, we were sitting on the Scale plan at $90 per month, heading toward the $160 and $250 tiers. For the volume we were sending, the per-email math stopped making sense.
At 100,000 emails per month, Resend costs $90. AWS SES costs $10. That $80 gap compounds to nearly $1,000 per year at a single volume tier, and we were growing fast.
The conversation in our team shifted from “which plan do we upgrade to” to “why are we paying a platform tax for something we could own.” That question became my project.
Before the migration, the email stack was simple: every part of the application called Resend's SDK directly, passing HTML or React Email templates. No queue, no retry logic, no centralized tracking. It worked fine at small scale. At higher volume, we started seeing the limitations.
I designed and built a centralized internal email platform with four components: SES for delivery, SQS for queueing, Next.js for the API and admin interface, and PostgreSQL (via Prisma) for state, tracking, and suppression management.
Any service in our product (course platform, auth, notifications) calls a single internal API endpoint to send an email. That request is validated, the email is rendered server-side using our template engine, and a job is pushed to SQS. A worker process polls SQS, calls SES, and writes the result to PostgreSQL.
SES bounce and complaint notifications flow back through SNS into a webhook endpoint, which updates our suppression list and delivery records in the database.
Simplified send API (Next.js route)
POST /api/email/send
{
"to": "user@example.com",
"template": "course-enrolled",
"data": { "courseName": "Intro to AI", "userName": "Prathmesh" },
"type": "transactional" // or "marketing"
}Worker polls SQS, calls SES, records result
const result = await ses.sendEmail({
Source: process.env.FROM_EMAIL,
Destination: { ToAddresses: [job.to] },
Message: {
Subject: { Data: rendered.subject },
Body: { Html: { Data: rendered.html } }
}
});
await db.emailLog.create({
data: {
messageId: result.MessageId,
to: job.to,
template: job.template,
status: "sent",
sentAt: new Date()
}
});One thing Resend handles for you automatically is bounce and complaint suppression. On SES, you own this. We built a simple suppression table in PostgreSQL. Every bounce and complaint event from SNS hits our webhook, which writes the address to a suppressed_emails table. Every send request checks this table before enqueuing. Hard bounces are permanent, soft bounces after three failures are added too.
Templates are stored as TypeScript files, rendered using a lightweight custom renderer. No external template service, no drag-and-drop builder. Developers define the template schema with Zod, and the API validates the data payload before rendering. This keeps templates version-controlled alongside the codebase and eliminates an entire category of runtime errors.
I used AI throughout the design and implementation. Not just for code generation, but for system design reasoning, API schema design, edge case discovery, and writing the internal documentation.
The result was a four-week build that delivered a production-grade internal platform. Without AI assistance on the boilerplate, infrastructure scaffolding, and documentation, a realistic estimate would have been eight to ten weeks for the same scope.
| Volume (emails/mo) | Resend (before) | SES platform (after) | Monthly savings |
|---|---|---|---|
| 50,000 | $20.00 | $5.00 | $15 |
| 100,000 | $90.00 | $10.00 | $80 |
| 250,000 | $160.00 | $25.00 | $135 |
| 500,000 | ~$400.00 | ~$50.00 | ~$350 |
| 1,000,000 | ~$700.00 | ~$100.00 | ~$600 |
At our current volume of 150,000 emails per month: we moved from roughly $120/month on Resend to approximately $15 on SES, saving over $1,200 per year. As volume grows, that gap widens significantly.
The SES numbers above include only the per-email charge. Even adding attachment data transfer (we have minimal attachments) and the SNS notification costs, the total stays well under $20 per month at our current volume.
Week 1
System design and AWS setup
Designed the architecture, set up SES domain verification, DKIM, and SPF records. Created SQS queues with dead-letter configuration. Drafted Prisma schema for email state tracking.
Week 2
Core API and worker build
Built the internal Next.js API routes for send, status, and webhook ingestion. Implemented the SQS polling worker with retry logic. Wired SNS notifications to the suppression table.
Week 3
Template migration and admin UI
Migrated all email templates from Resend/React Email format to our internal renderer. Built a lightweight admin dashboard to view send logs, delivery rates, bounces, and suppression list management.
Week 4
Staging tests and production cutover
Ran parallel sending in staging, validated delivery rates and bounce handling. Cutover production traffic with a feature flag. Monitored for 48 hours before decommissioning Resend subscription.
For a team with engineering capacity and volumes above 50,000 emails per month, the economics become compelling enough to absorb these tradeoffs. Below that volume, the build cost may outweigh the savings.
If I were starting this project again, I would spend more time upfront on the admin observability layer. The core send pipeline was solid from day one, but the dashboard for visualizing delivery rates and bounce trends by template was an afterthought.
I would also set up dedicated sending IPs earlier. We started on shared pools, which is fine for transactional email, but for marketing sends at scale, a dedicated IP with a proper warmup schedule gives you better control over reputation.
Moving from Resend to a custom AWS SES platform was one of the highest-leverage infrastructure projects I have shipped. The ongoing cost reduction is significant, but the larger gain is ownership. We now have full visibility into every email we send, complete control over the delivery pipeline, and a platform we can extend as our product scales.
The combination of AI-assisted system design and development meant a solo engineering effort delivered something that would previously have required a team sprint. That is the workflow I will carry into every infrastructure project going forward.
TLDR
We replaced Resend with a custom platform using AWS SES, SQS, Next.js, and PostgreSQL. Monthly email costs dropped by over 87%. Build time was four weeks, accelerated significantly by AI-assisted design and coding. At scale, the savings compound into thousands of dollars per year.
Prathmesh
Full-stack engineer, edtech and AI
Built with Next.js, AWS SES, SQS, Prisma, and PostgreSQL. System designed and shipped with AI.