DEV Community

Libme
Libme

Posted on

Your Password Reset Emails Are Going to Spam: Choosing Between Resend, Postmark, and Amazon SES

If your transactional email is landing in spam, the provider is almost never the first thing to fix — misaligned SPF/DKIM/DMARC records and a shared sending domain are. Once DNS is correct, the choice comes down to what you want to operate: Amazon SES is the cheapest per message and the most work, Postmark buys you strict stream separation and deliverability support, and Resend buys you the fastest path from npm install to a sent email. Below is the failure mode, the DNS fix, and where each provider actually breaks down.

Why does "delivered" in the dashboard still mean spam folder?

This is the part that wastes an afternoon. Every provider's dashboard reports a message as Delivered when the receiving mail server returned 250 OK at the end of the SMTP transaction. That acknowledgement means Gmail accepted the message for processing. It says nothing about which folder Gmail then put it in — inbox, Promotions, or Spam. No sending API can see that, because the receiving side never tells you.

So the symptom looks like this: your logs are clean, the provider shows 100% delivery, and users keep saying the reset link never arrived. The next dead end most people try is rewriting the email copy — removing the word "free", shortening the subject line, stripping images. That occasionally helps at the margin, but it is downstream of the real problem, which is that the receiving domain cannot verify you are who your From header claims.

A 250 OK is proof of acceptance, not proof of inbox placement — treat provider "delivered" counts as a floor, not a result.

What DNS records do you actually need before blaming the provider?

Three records, and the one that trips people up is alignment rather than existence. SPF and DKIM can both pass while DMARC still fails, because DMARC requires the passing mechanism's domain to align with the domain in the visible From header. If you send as noreply@example.com but your SPF-authorized envelope sender is your provider's bounce domain and DKIM signs with a subdomain the provider owns, alignment fails and DMARC fails with it.

Check what you have published before changing anything:

# SPF — must exist exactly once on the sending domain
dig +short TXT mail.example.com | grep spf1

# DKIM — key selector varies per provider; check the one they gave you
dig +short TXT resend._domainkey.mail.example.com

# DMARC — always at _dmarc.<domain>
dig +short TXT _dmarc.example.com
Enter fullscreen mode Exit fullscreen mode

A workable starting DMARC record is v=DMARC1; p=none; rua=mailto:dmarc@example.com — report-only, so you can read aggregate reports for a week and confirm alignment before moving to p=quarantine. Publishing p=reject first is how people silently kill their own mail.

Two more things that are non-negotiable as of mid-2026: since Google and Yahoo tightened their bulk sender requirements in February 2024, senders at volume need SPF, DKIM, and DMARC, plus one-click unsubscribe on marketing mail and a spam complaint rate held well below their published threshold. And transactional mail should leave from a dedicated subdomain (mail.example.com), so a bad marketing campaign cannot poison the reputation that carries your password resets.

Fix alignment and subdomain separation first; switching providers to solve a DNS problem just moves the problem.

How do Resend, Postmark, and SES actually differ?

Amazon SES Postmark Resend
Pricing model Per thousand messages, cheapest at volume; data transfer and attachment size billed separately Per message, tiered by monthly volume; premium relative to SES Per message with a free developer tier, tiered plans above it
Setup friction Highest — sandbox by default, production access is a request form Low — domain verification, then send Lowest — API key and a verified domain
Bounce/complaint handling You wire SNS or EventBridge to a configuration set and process events yourself Built-in suppression plus a searchable activity view Webhook events for delivery, bounce, and complaint
Stream separation Configuration sets, self-managed First-class: transactional and broadcast streams are enforced Supported via separate domains/audiences, less opinionated
Deliverability support AWS Support ticket, general-purpose Deliverability-specialist support is the product Younger track record; support scales with plan
Best when You have volume and infra people Reset/receipt mail must not fail You want to ship this afternoon

The three are genuinely different products that happen to share an API shape:

// Amazon SES v2 — cheap, explicit, and you own the event plumbing
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";

const ses = new SESv2Client({ region: "us-east-1" });

await ses.send(new SendEmailCommand({
  FromEmailAddress: "Acme <noreply@mail.example.com>",
  Destination: { ToAddresses: [user.email] },
  ConfigurationSetName: "transactional", // routes bounce/complaint events
  Content: {
    Simple: {
      Subject: { Data: "Reset your password" },
      Body: { Text: { Data: resetBody } },
    },
  },
}));
Enter fullscreen mode Exit fullscreen mode
// Postmark — message streams are a required argument, not an afterthought
import { ServerClient } from "postmark";

const postmark = new ServerClient(process.env.POSTMARK_TOKEN);

await postmark.sendEmail({
  From: "Acme <noreply@mail.example.com>",
  To: user.email,
  Subject: "Reset your password",
  TextBody: resetBody,
  MessageStream: "outbound", // 'broadcast' mail must use a different stream
});
Enter fullscreen mode Exit fullscreen mode
// Resend — the least ceremony between you and a sent message
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

await resend.emails.send({
  from: "Acme <noreply@mail.example.com>",
  to: [user.email],
  subject: "Reset your password",
  text: resetBody,
});
Enter fullscreen mode Exit fullscreen mode

The API call is the easy part in all three; what differs is who operates bounce handling, reputation, and stream hygiene — you, or the vendor.

When is SES the wrong default despite the price?

SES is unbeatable on unit cost and it is the correct answer for high-volume senders who already run AWS. The trap is that new accounts start in a sandbox: you can only send to verified addresses, under a low daily quota (200 messages per 24 hours and one message per second at the time of writing), until you file for production access and get approved. Discovering that on launch day is a genuinely bad afternoon, and the approval is not instant.

The second cost is operational. SES gives you an account-level suppression list and event streams, but the loop that turns a hard bounce into "stop emailing this address and mark it invalid in our users table" is yours to build:

// SNS -> your endpoint. Hard bounces and complaints must reach your own suppression table.
export async function handleSesEvent(req) {
  const event = JSON.parse(JSON.parse(req.body).Message);

  if (event.eventType === "Bounce" && event.bounce.bounceType === "Permanent") {
    for (const r of event.bounce.bouncedRecipients) {
      await db.suppress(r.emailAddress, "hard_bounce");
    }
  }
  if (event.eventType === "Complaint") {
    for (const r of event.complaint.complainedRecipients) {
      await db.suppress(r.emailAddress, "complaint");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If you want managed sending where bounce suppression, stream separation, and a human deliverability team come with the bill instead of the backlog, Postmark is the one that treats transactional reputation as the product rather than a feature. Its honest drawback: it is priced well above SES per message, it deliberately does not want to be your bulk marketing platform, and message content is retained only for a limited window, so your own logs still have to be the system of record.

SES saves money per message and spends it back in engineering hours — budget the bounce pipeline as part of the decision, not after it.

Where does Resend fit for a small team?

Resend's pitch is developer experience, and it delivers: domain verification is quick, the SDKs are clean, and templating with React Email removes the worst part of HTML email if you already write React. For a solo developer or an early team, it is the shortest distance from zero to a correctly authenticated sending domain.

The honest caveat is track record. Deliverability at shared-IP providers is partly a function of how aggressively the vendor polices its other customers, and that reputation compounds over years. Resend is the newest of the three, so if you are sending high-stakes mail at meaningful volume, plan to monitor Google Postmaster Tools yourself rather than assuming the platform reputation carries you. If your priority is getting authenticated transactional mail out this week with the least configuration, Resend is the one that gets you there fastest.

Pick Resend for speed of setup, but instrument your own deliverability monitoring regardless of provider.

How do you migrate without burning your domain?

Do not repoint an established sending domain at a new provider all at once. Verify the new provider on a fresh subdomain, send a low-stakes category of mail through it first (receipts, not password resets), watch complaint and bounce rates for a couple of weeks, then move the rest. If you are moving to a dedicated IP, warming is mandatory — a cold IP suddenly emitting thousands of messages looks exactly like a compromised host to every receiver, because usually it is one.

Migrate one mail category at a time on a new subdomain; a big-bang cutover risks the one email your product cannot afford to lose.

FAQ

Why do my emails go to spam even though SPF and DKIM pass?
Because DMARC requires alignment, not just passing checks: the domain that passes SPF or DKIM must match the domain in the visible From header. Publish a p=none DMARC record with a rua address and read the aggregate reports — they will name the mechanism that is failing alignment.

Is Amazon SES cheaper than Postmark or Resend?
Per message, yes, and by a wide margin at volume. The cost moves into engineering: SES starts in a sandbox that requires an approval request, and you build bounce, complaint, and suppression handling yourself from SNS or EventBridge events.

Should transactional and marketing email use the same domain?
No. Send transactional mail from a dedicated subdomain and marketing from a different one, so a campaign that draws spam complaints cannot damage the reputation delivering your password resets.

Bottom line

If you are on AWS, sending at volume, and have someone who will own the bounce pipeline, SES is the right economics. If password resets and receipts are business-critical and you would rather pay than operate, Postmark is worth the premium for enforced stream separation and deliverability support. If you are a small team optimizing for time-to-first-correct-send, start with Resend and add your own monitoring. Whichever you pick, fix DNS alignment and split transactional onto its own subdomain first — that single change fixes more spam-folder problems than any migration.

Related reading

Top comments (0)