Skip to content
Chapter 48Lesson 3

The transactional subdomain split

How to isolate sender reputation by routing transactional and marketing email through separate subdomains.

Your app sends three kinds of email: a password reset when someone is locked out, a receipt when a subscription charges, and a monthly newsletter about new features. All three are “email,” so the natural instinct is to send them the same way, through one domain, one API key, and one from address. That instinct quietly breaks your product. When everything shares a domain, the spam complaints a marketing campaign collects can push your password-reset email into the spam folder. Nothing reports the failure, so to your product team it just looks like signups mysteriously dropped.

Every decision in this lesson depends on which of two categories a message falls into.

Transactional mail is triggered by a specific action the user just took, and it carries information they expect and need to operate their account: a verification code at signup, a password-reset link, a receipt for a charge, a “new sign-in from an unrecognized device” alert. The user did something, and the email is the system responding.

Marketing mail goes out on the sender’s schedule, not the user’s. It’s promotional or informational content the user opted into at some point but didn’t trigger today: the monthly newsletter, a drip campaign welcoming new trial users over their first week, a “we’ve shipped a new feature” announcement to your whole user base, a “we miss you” win-back to someone who’s gone quiet. You decided to send these, not them.

The line is operational, not aesthetic: it changes how you’re allowed to send, not just how the email looks.

There’s a legal dimension. Anti-spam law (CAN-SPAM in the US, and its cousins elsewhere) requires marketing mail to carry a working one-click unsubscribe. Transactional mail is exempt, for a sound reason: a user can’t “unsubscribe” from password resets and keep a working account. The email is part of the account’s machinery, not a campaign. So your transactional subdomain carries no unsubscribe header. Unsubscribe lives on the marketing side of the line.

There’s also a deliverability dimension, which is the one this whole lesson turns on. Mailbox providers, Gmail, Yahoo, and Microsoft, track the reputation of these two streams separately, because the recipient’s expectation pattern differs so sharply between them.

The following exercise gives you eight real sends to sort. A few are deliberately on the edge, because the edges are where this skill gets tested. For each one, ask: did the user trigger it, and do they need it to run their account?

Sort each send by whether the user *triggered* it and *needs* it to operate their account. Drag each item into the bucket it belongs to, then press Check.

Transactional Triggered by the user, needed to operate the account
Marketing Sent on your schedule, opted into but not triggered
Email-verification code at signup
Password-reset link
Receipt for a subscription charge
Sign-in from a new device alert
Monthly product newsletter
“We miss you” win-back after 30 days inactive
Announcement of a new feature to all users
“Your free trial ends in 3 days — upgrade now”

If the trial-ending email gave you pause, that’s the trap. It feels transactional: it’s about the user’s specific account, and the deadline is real. But look at what triggers it: not the user, a timer. And look at what it’s for: not “here’s the state of your account” but “upgrade now.” A scheduled send whose purpose is to sell is marketing, no matter how account-specific the body reads, and it’s exactly the kind of case the decision tree later in this lesson is built to resolve.

Why the two streams earn different reputations

Section titled “Why the two streams earn different reputations”

Why do mailbox providers score these two streams separately, and why does keeping them apart matter so much? The answer is a short cause-and-effect chain.

Start with how a provider like Gmail decides where your mail goes. It doesn’t read your email and judge the writing; it watches signals: what fraction of recipients open your mail, how many mark it as spam, how many delete it unread, and whether you send to addresses that don’t exist. From those signals it computes a reputation score for your sending domain. A high score lands your mail in the inbox; a low score lands it in spam or gets it rejected outright.

Now look at what each stream does to that score.

Transactional mail earns a strong, stable reputation almost for free. People open password resets, because they asked for them thirty seconds ago. Almost nobody marks a receipt or verification code as spam, since it’s mail they were waiting for. And you only send it to someone who just took an action, so there’s no blast-to-a-list volume. High opens, near-zero complaints, and no junk sends add up to a clean, excellent reputation.

Marketing mail earns a weaker, more volatile reputation, by the nature of the stream. Open rates are lower, because not everyone cares about this month’s newsletter. Complaints are materially higher, often from people who forgot they opted in three years ago and reach for “mark as spam” as a faster unsubscribe. And every campaign sheds some list churn.

Here is the link that breaks your product. When both streams send from the same domain, they share one reputation score, so the marketing complaints drag down the single number Gmail keeps for that domain. When that number dips, Gmail doesn’t surgically spam-folder only your newsletters; it spam-folders the whole domain. So your verification email, the one a brand-new user is watching their inbox for, lands in spam. The user gives up, and your signup funnel quietly loses conversions. The cause stays invisible to the people who could fix it: the product team sees “signups are down,” not “Gmail is spam-foldering our verification mail because last week’s campaign drew too many complaints.”

The fix follows from the diagnosis: give each stream its own domain, so each carries its own score. The diagram below makes the contrast concrete. The first tab shows the trap, both streams pouring into one reputation meter; the second shows the fix, the same two streams feeding two independent meters. Watch where the verification email lands in each.

Your sends
Transactional sends high opens · ~0 complaints
Marketing sends complaint churn
One reputation
yourapp.com reputation: at risk
marketing complaints drag the shared score down
Gmail decides
Inbox
Spam verification

One domain, one score. Marketing complaints drag the shared meter into the danger zone, and the verification email, which did nothing wrong, lands in spam.

So you split, into three zones. The names below are conventions, not requirements, but you’ll see them across most production apps.

yourapp.com apex · humans
send.yourapp.com transactional · the app
marketing.yourapp.com marketing · campaigns (only if you send marketing)
One apex for humans, two sending subdomains beneath it: transactional on `send.`, marketing on `marketing.` (stood up only once you actually send marketing).

The apex domain , yourapp.com with nothing in front of it, is for humans: employee mailboxes, the founder replying to a customer, support typed by a person. Often it does no automated sending at all, and that is the point. The moment your app blasts automated mail from the apex, your founders’ own outbound rides the same reputation as your app’s volume. One bad deliverability week for the app, and the CEO’s email starts landing in spam too. Keep the humans’ domain decoupled from the app’s incidents.

The send.yourapp.com subdomain (you’ll also see mail.) is the transactional workhorse: every automated send from Resend goes out from here. This is the subdomain whose SPF and DKIM records you published in the previous lesson, with DMARC inherited from the apex, and it’s the only sending subdomain the project builds.

The marketing.yourapp.com subdomain (or news.) is the marketing stream’s home, stood up only once you actually send marketing mail. It’s a separate verified domain in Resend with its own DKIM and SPF, and that separation is exactly what gives it a separate reputation. The project sends transactional only, so you won’t build this one, but it’s named here so you don’t conflate it with send. later.

Two things to nail down. First, those names are convention, not protocol: no email standard requires the word “send”, and mail. or tx. would work too. What matters is that you pick names and keep them identical across your dev, preview, and production environments, so a from address doesn’t silently change shape between deploys.

Second, a watch-out that ties back to the previous lesson. If you add marketing. later, you’ll walk it through the same DMARC none → quarantine → reject progression you did for send.. The subtle part: once your apex DMARC policy is at reject, any new subdomain that isn’t yet aligned through its own DKIM gets hard-bounced, because subdomains inherit the apex policy by default. A new marketing subdomain isn’t a five-minute DNS edit you can do the morning of a campaign; it’s a warmup project, so plan it ahead.

One bit of vocabulary you’ll see throughout the email world: an ESP is the service that actually puts your mail on the wire, and Resend is yours. “Use a separate ESP for marketing” means route that stream through a different provider account — a heavier separation than just a separate subdomain.

“Use separate subdomains” raises the question: separate how? There are two thresholds, and the difference matters.

Mailbox providers track reputation per domain and sending IP. Full separation means two genuinely independent identities on the wire: different IP ranges, different DKIM selectors, different DMARC report streams. Route both streams through the same provider on its same shared IP pool and you’ve separated the domains but not the IPs, so the separation is partial.

Partial separation is the early-stage default: one Resend account, two verified subdomains, both sending over Resend’s shared IP pool. Separate subdomains with separate DKIM keys buy you roughly 80% of the reputation isolation of a full split, with zero IP-management overhead. The shared pool works in your favor because Resend keeps it warm and clean across all its customers, so your low volume rides on a reputation far better than you could build alone.

The remaining 20% is dedicated IPs, separate provider accounts, and message-stream isolation. It only earns its weight at real volume, north of roughly 50,000 emails a month.

This is the one piece of concrete syntax in the lesson. Recall the anatomy of a from string: 'Display Name <local-part@verified.subdomain>'. The display name and subdomain were covered in the first lesson; the part worth a rule here is the local part , the bit before the @.

Name the local part for the intent the user reads off the From line, not for the system that sent it. A user glancing at their inbox reads meaning from those few characters. Addresses like notifications@, billing@, and security@ tell them what kind of mail this is and let their filters route it. An address like auth-service-prod@ tells them nothing and leaks your internal architecture into their inbox.

Here is the convention table for a transactional setup. Note where each address lives: the automated ones on send., the genuinely human ones back on the apex.

AddressUsed forReplyable?
noreply@send.yourapp.comVerification codes, magic-links: automated, no reply expectedNo. Must pair with a replyTo to a monitored inbox
billing@send.yourapp.comInvoices, dunning Yes. Replies go to billing
security@send.yourapp.comSecurity alerts, the kind users filter to high priorityYes. Replies go to a security/support inbox
support@yourapp.com / hello@yourapp.comGenuinely human conversationYes. A real Google Workspace mailbox on the apex

The noreply@ row carries a trap. Someone will hit reply on a receipt or a security alert, and with no replyTo set their message vanishes: bad UX, and a faint negative deliverability signal too, since Gmail trusts senders who are never replied to slightly less. The fix is to keep the bot identity in from and point replyTo at a mailbox a human reads. The two variants below show the wrong shape and the right one, reusing the sendEmail call from the first lesson.

await sendEmail({
to: customer.email,
subject: 'Your receipt',
react: <Receipt invoice={invoice} />,
});

No replyTo set. The from defaults to the noreply@ identity, and a user who hits Reply on this receipt reaches no one.

Make this routine: every transactional send either sets replyTo to a monitored mailbox or uses a from address that is itself monitored.

Most sends classify themselves at a glance: a password reset is transactional, a newsletter is marketing. The decision tree is for the ambiguous ones, like the trial-ending upsell from the earlier exercise, a billing-flavored newsletter, or an opted-in weekly digest. For those, walk the questions in the order an experienced engineer asks them; the order is the whole point.

The tree below is that order, made clickable. Follow your send down the branches to the subdomain it should send from. Try it with the trial-ending upsell and watch where it lands.

Transactional or marketing?

Every path ends at a subdomain, not just a label: the classification resolves directly into the from domain the send uses. The tie-breaker drives the rest. When you can’t decide, you default to marketing, because one wrong answer is expensive and the other is free.

In B2B SaaS your customer is a company, say Acme Corp at acme.com, and it feels natural to send their billing receipts and invitations from acme.com. Don’t. In a multi-tenant SaaS, all mail flows from your verified domain, send.yourapp.com, with the tenant’s content inside the message: a user at Acme gets an email from send.yourapp.com that talks about Acme. Sending genuinely from the tenant’s own domain (per-tenant CNAME records and DKIM delegation, so Acme’s domain authorizes your servers to sign as them) is a separate, heavyweight feature, rarely worth building until a paying customer makes it a deal-breaker.

Splitting transactional and marketing onto separate subdomains is nearly free on day one and brutally expensive later.

Doing it now costs almost nothing: two DNS verifications instead of one, and two from addresses instead of one. Pay that before you send a single email.

If you want to go deeper, these three pair well with this lesson: the provider’s own take on structuring sending domains, a fuller treatment of the transactional-versus-marketing line, and the authoritative source for the bulk-sender rules referenced throughout.