Web Development · Email
Transactional Email in 2026: Resend, SES, and Getting Deliverability Right
A practical guide to transactional email for web apps in 2026 — comparing Resend, Amazon SES, and SendGrid, with a focus on SPF/DKIM/DMARC setup and the common deliverability mistakes teams make.
Anurag Verma
7 min read
Sponsored
Transactional email is the part of web development that everyone does wrong at least once. Teams ship a product, watch password resets land in spam, and spend a week figuring out why.
The short answer is almost always the same: the sending domain is not properly authenticated. Here is how to avoid that and which providers make it easiest.
The Provider Landscape
Resend is the new entrant that has gained significant adoption since 2023. Clean API, excellent documentation, React Email integration for template authoring, and a free tier that covers most early-stage products. Built by a team that previously worked on authentication tooling, so the developer experience shows.
Amazon SES is the lowest-cost option at serious scale. $0.10 per 1,000 emails sent, $0.12 per GB of attachments. No free tier after the first 62,000 emails/month from EC2. The setup is more involved than Resend, and the developer experience is noticeably less polished, but at high volumes the economics are hard to argue with.
SendGrid is the incumbent. Widely used, solid deliverability, good client libraries, but more expensive than SES and the UI has gotten cluttered over the years. The free tier gives you 100 emails/day permanently.
Postmark sits between Resend and SendGrid — excellent deliverability reputation, focused specifically on transactional (not marketing) email, more expensive per send but preferred in regulated industries.
For most new projects: Resend for development velocity, SES if you’re already deep in AWS and send more than a few thousand emails daily.
Authentication: SPF, DKIM, DMARC
Email deliverability depends on three DNS records. Without them, your email lands in spam on Gmail, Outlook, and Yahoo regardless of which provider you use.
SPF (Sender Policy Framework) declares which mail servers are allowed to send email on behalf of your domain. A TXT record on your domain.
DKIM (DomainKeys Identified Mail) adds a cryptographic signature to outgoing emails so recipients can verify they haven’t been tampered with. Requires adding a CNAME or TXT record that your provider generates for you.
DMARC (Domain-based Message Authentication, Reporting, and Conformance) tells receiving mail servers what to do when SPF or DKIM checks fail. Start with p=none (monitor only) and tighten to p=quarantine or p=reject once you’ve verified your setup is clean.
Setting up DMARC for your domain:
; TXT record on _dmarc.yourdomain.com
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; sp=none; adkim=r; aspf=r
The rua address receives aggregate reports. Free tools like dmarcian or Postmark’s DMARC Digests parse these reports into readable summaries.
After a week of p=none with no issues in your reports, move to p=quarantine:
v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com; pct=100
This quarantines (usually to spam folder) emails that fail DMARC. Once you’re confident, switch to p=reject.
Resend Setup
Resend’s setup takes about 10 minutes. Verify your domain, add the DKIM records they provide, and you’re sending.
Install the SDK:
npm install resend
Send a basic transactional email:
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
await resend.emails.send({
from: 'Your Product <noreply@yourproduct.com>',
to: user.email,
subject: 'Confirm your email address',
html: `<p>Click <a href="${confirmationUrl}">here</a> to confirm your email.</p>`,
})
For anything more than one-off strings, use React Email to author templates as React components:
npm install react-email @react-email/components
Create a template:
// emails/welcome.tsx
import {
Body, Container, Head, Heading, Html,
Link, Preview, Text
} from '@react-email/components'
interface WelcomeEmailProps {
name: string
loginUrl: string
}
export function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to Your Product, {name}</Preview>
<Body style={{ backgroundColor: '#ffffff', fontFamily: 'sans-serif' }}>
<Container style={{ margin: '0 auto', padding: '20px' }}>
<Heading>Welcome, {name}</Heading>
<Text>
Your account is ready. Log in to get started.
</Text>
<Link href={loginUrl}>Log in to Your Product</Link>
</Container>
</Body>
</Html>
)
}
Use it with Resend:
import { render } from '@react-email/render'
import { WelcomeEmail } from './emails/welcome'
const html = render(<WelcomeEmail name={user.name} loginUrl={loginUrl} />)
await resend.emails.send({
from: 'Your Product <noreply@yourproduct.com>',
to: user.email,
subject: 'Welcome to Your Product',
html,
})
React Email’s preview server (npm run email) lets you see exactly what the email looks like across clients while developing.
Amazon SES Setup
SES requires more initial work. You need to:
- Verify your sending domain in the SES console
- Add the DKIM CNAME records SES provides
- Request production access (SES starts in sandbox mode — only verified emails can receive)
Once out of sandbox, configure sending in your app using the AWS SDK:
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'
const ses = new SESv2Client({ region: 'us-east-1' })
await ses.send(new SendEmailCommand({
FromEmailAddress: 'noreply@yourproduct.com',
Destination: {
ToAddresses: [user.email],
},
Content: {
Simple: {
Subject: { Data: 'Confirm your email address' },
Body: {
Html: { Data: htmlContent },
Text: { Data: textContent },
},
},
},
}))
Alternatively, configure SES as an SMTP relay and use any existing nodemailer or SMTP-based setup:
import nodemailer from 'nodemailer'
const transporter = nodemailer.createTransport({
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
secure: false,
auth: {
user: process.env.SES_SMTP_USERNAME,
pass: process.env.SES_SMTP_PASSWORD,
},
})
The SMTP credentials are generated separately from IAM credentials — find them under “SMTP settings” in the SES console.
Common Deliverability Problems
Sending from a subdomain without SPF is the most common mistake. If you send from noreply@mail.yourproduct.com but your SPF record is on yourproduct.com, the check fails. Add SPF to the subdomain too.
Missing or mismatched DKIM alignment happens when the DKIM signing domain doesn’t match the From domain. DMARC alignment requires either SPF or DKIM to pass with a domain that matches the From address.
Sending too fast from a new domain triggers spam filters. IP warming — starting with low volumes and gradually increasing — matters more for bulk email, but even transactional senders on shared IPs can be affected. Resend handles this automatically on their shared infrastructure; if you’re using SES with a dedicated IP, you need to warm it manually.
Plain text alternatives are missing. Every HTML email should include a plain text part. Some email clients, spam filters, and corporate firewalls handle emails better when both are present.
No List-Unsubscribe header on marketing-adjacent transactional email. Gmail and Yahoo now require this header on bulk senders. Add it to any email with optional notification content:
await resend.emails.send({
from: 'noreply@yourproduct.com',
to: user.email,
subject: 'Your weekly digest',
html: content,
headers: {
'List-Unsubscribe': `<mailto:unsubscribe@yourproduct.com?subject=unsubscribe>, <${unsubscribeUrl}>`,
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
},
})
Testing Before You Ship
Use mail testing services to verify your setup before going live:
- mail-tester.com sends you a unique address to email and scores your deliverability from 1-10. Shows specific issues with SPF, DKIM, content, and blacklists.
- MXToolbox validates your DNS records directly.
- Email Preview by Litmus or Email on Acid renders your HTML email across 70+ email clients — Gmail, Outlook, Apple Mail, mobile clients — to catch rendering issues.
At minimum, send a test email to a Gmail and an Outlook account and check the raw message headers to verify DKIM passes.
The Non-Technical Part
Great technical setup still fails if your sending reputation is poor. Reputation comes from engagement: do people open your emails or immediately delete them?
Transactional emails have high engagement by nature (people want their receipts and confirmations). Guard that reputation by keeping transactional and marketing sends separate — ideally on separate subdomains (mail.yourproduct.com for transactional, news.yourproduct.com for marketing). A spam complaint on your newsletter shouldn’t affect password reset deliverability.
The setup is a one-time investment. Get the DNS records right, test with mail-tester.com, and you’ll rarely have to think about this again.
Sponsored
More from this category
More from Web Development
Sponsored
The dispatch
Working notes from
the studio.
A short letter twice a month — what we shipped, what broke, and the AI tools earning their keep.
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored